on scene selection OK + keyframe model

This commit is contained in:
STEINNI committed 2025-10-25 20:23:43 +00:00
1 parent 6931a777a2
commit 4e1f6886f2
23 files changed
+2107 -64

No files matched your search

+6
View File
@@ -15,6 +15,12 @@
"uri": "/api/agent/{id}",
"method": "GET"
}
},
"/keyframes": {
"save": {
"uri": "/api/keyframes/{id}",
"method": "PUT"
}
}
}
}
+1 -1
View File
@@ -358,7 +358,7 @@ input{
div.window > section button[eicbutton][rounded] {
min-height: 2em;
padding: 0;
color: #DDD;
color: #222;
}
/* Customizations to buildoz*/
@@ -20,7 +20,8 @@
}
],
"models": [
"AgentsModel"
"AgentsModel",
"KeyframesModel"
],
"views": [
{"view":"editors/KeyframeView", "dependencies": ["editors/modules/agentPreview.module", "editors/modules/kfArena.module"]}
+7 -2
View File
@@ -32,17 +32,22 @@
"/thirdparty/beautify/beautify.html.min",
"/thirdparty/beautify/beautify.css.min",
"/thirdparty/highlight/highlight.min",
"/thirdparty/katex/katex.min",
"/helpers/basicDialogs"
],
"assets": {
"styles": [
{ "name":"FileUpload.css", "path":"/app/thirdparty/eicui/plugins/FileUpload/" },
{ "name":"Charts.css", "path":"/app/thirdparty/eicui/plugins/Charts/" },
{ "name":"monokai.min.css", "path":"/app/thirdparty/highlight/styles/" }
{ "name":"monokai.min.css", "path":"/app/thirdparty/highlight/styles/" },
{ "name":"katex.min.css", "path":"/app/thirdparty/katex/" }
],
"json": [
{ "id": "user-roles", "name": "global/user-roles.json" },
{ "id": "user-messages", "name": "system/user-messages.json" }
]
],
"html": [
{ "id":"toto", "name": "toto.html"}
]
}
}
+39 -8
View File
@@ -6,7 +6,7 @@ if(!app.helpers) app.helpers = {}
*/
app.helpers.formBuilder = {
fieldsFromJSON(fieldsObj, fieldsetLabel=null){
fieldsFromJSON(fieldsObj, fieldsValues, fieldsetLabel=null){
let allFields = []
for(const propName in fieldsObj){
const fieldRow = ui.create(`<div class="cols-2"><label>${fieldsObj[propName].label}</label></div>`)
@@ -18,34 +18,35 @@ app.helpers.formBuilder = {
component.setAttribute('type','number')
if('min' in fieldsObj[propName]) component.setAttribute('min', fieldsObj[propName].min)
if('max' in fieldsObj[propName]) component.setAttribute('max', fieldsObj[propName].max)
component.value = fieldsObj[propName].default
break
case 'string':
component = document.createElement('input')
component.setAttribute('name',propName)
component.setAttribute('type','text')
component.value = fieldsObj[propName].default
break
case 'boolean':
component = document.createElement('bz-toggler')
component.setAttribute('name',propName)
component.setAttribute('trueValue','1')
component.setAttribute('falseValue','0')
component.value = fieldsObj[propName].default
fieldRow.append(component.el)
break
case 'list':
component = document.createElement('bz-select')
component.setAttribute('name',propName)
component.setAttribute('name', propName)
component.fillOptions( fieldsObj[propName].choices.map(item => {
return({ markup: `${item}`, value: item})
}))
}))
break
default:
console.warn(`Unknown field type ${fieldsObj[propName].type}`)
}
fieldRow.append(component)
allFields.push(fieldRow)
if(component){
component.classList.add('formbuilder-field')
component.value = this.getPathInObj(fieldsValues, propName) || fieldsObj[propName].default
fieldRow.append(component)
allFields.push(fieldRow)
}
}
if(fieldsetLabel!==null){
const fieldset = document.createElement('fieldset')
@@ -60,4 +61,34 @@ app.helpers.formBuilder = {
return(allFields)
}
},
getPathInObj(obj, path){
const parts = path.split('.')
let target = obj
for (let i = 0; i < parts.length - 1; i++) {
const key = parts[i]
if(!target[parts[i]]) return(null)
target = target[parts[i]]
}
return(target[parts[parts.length - 1]] )
},
getFieldsValues(rootSel){
const result = {}
document.querySelectorAll(`${rootSel} .formbuilder-field`).forEach(el => {
const path = el.name.split('.')
let target = result
for (let i = 0; i < path.length - 1; i++) {
const key = path[i]
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {}
}
target = target[key]
}
target[path[path.length - 1]] = el.value
})
return(result)
},
}
+49
View File
@@ -1,4 +1,7 @@
import * as THREE from '/app/thirdparty/Three/three.module.js'
import { EffectComposer } from '/app/thirdparty/Three/postprocessing/EffectComposer.module.js'
import { RenderPass } from '/app/thirdparty/Three/postprocessing/RenderPass.module.js'
import { OutlinePass } from '/app/thirdparty/Three/postprocessing/OutlinePass.module.js'
if(!app.helpers) app.helpers = {}
/**
@@ -58,6 +61,26 @@ app.helpers.helpers3D = {
return obj
},
resizeRendererToDisplaySize() {
const width = this.canvasEl.clientWidth
const height = this.canvasEl.clientHeight
// Check if renderer size differs from displayed size
const needResize = this.canvasEl.width !== width || this.canvasEl.height !== height
if (needResize) {
// 1. Update renderer (base framebuffer)
this.renderer.setSize(width, height, false)
// 2️. Update camera aspect ratio
this.camera.aspect = width / height
this.camera.updateProjectionMatrix()
// 3️. Update postprocessing chain
if (this.composer) this.composer.setSize(width, height)
if (this.outlinePass) this.outlinePass.setSize(width, height)
}
return needResize
},
cameraAutoFrame(object, camera, offset = 1.5, controls) {
const box = new THREE.Box3().setFromObject(object)
const size = new THREE.Vector3()
@@ -87,6 +110,32 @@ app.helpers.helpers3D = {
return center
},
init3DHighlighter(options){
if (!this.composer) {
this.composer = new EffectComposer(this.renderer)
this.composer.addPass(new RenderPass(this.scene, this.camera))
}
if (!this.outlinePass) {
this.outlinePass = new OutlinePass(
new THREE.Vector2(this.canvasEl.innerWidth, this.canvasEl.innerHeight),
this.scene, this.camera
)
this.outlinePass.edgeStrength = options.edgeStrength || 3
this.outlinePass.visibleEdgeColor.set(options.visibleEdgeColor || 0xffffff)
this.outlinePass.edgeGlow = options.edgeGlow || 0
this.outlinePass.edgeThickness = options.edgeThickness || 1
this.outlinePass.pulsePeriod = options.pulsePeriod || 0
this.composer.addPass(this.outlinePass)
this.highlighted3DObjects = []
this.outlinePass.selectedObjects = this.highlighted3DObjects
}
},
makePivotAtGeomCenter(object, scene) {
object.updateWorldMatrix(true, true)
const box = new THREE.Box3().setFromObject(object)
+28
View File
@@ -0,0 +1,28 @@
class KeyframesModel extends WindozModel {
constructor() {
super()
this.ressource = '/keyframes'
}
async save(data) {
const kfData = Object.keys(data).map(aid => {
const { position, speed, ...storeValues} = data[aid].values
return({
aid: aid,
storeValues: storeValues,
gpsValues: { position: data[aid].values.position, speed: data[aid].values.speed }
})
})
let endpoint = {...app.config.api[this.ressource].getTypes}
endpoint.uri = endpoint.uri.replace('{family}', data.kfId || '')
const aid = Object.keys(x)[data]
return (
this.request(endpoint.uri, endpoint.method, kfData)
)
}
}
app.registerClass('KeyframesModel', KeyframesModel);
@@ -0,0 +1,363 @@
import {
Clock,
HalfFloatType,
NoBlending,
Vector2,
WebGLRenderTarget
} from '../three.module.js'
import { CopyShader } from '../shaders/CopyShader.module.js'
import { ShaderPass } from './ShaderPass.module.js'
import { ClearMaskPass, MaskPass } from './MaskPass.module.js'
/**
* Used to implement post-processing effects in three.js.
* The class manages a chain of post-processing passes to produce the final visual result.
* Post-processing passes are executed in order of their addition/insertion.
* The last pass is automatically rendered to screen.
*
* This module can only be used with {@link WebGLRenderer}.
*
* ```js
* const composer = new EffectComposer( renderer );
*
* // adding some passes
* const renderPass = new RenderPass( scene, camera );
* composer.addPass( renderPass );
*
* const glitchPass = new GlitchPass();
* composer.addPass( glitchPass );
*
* const outputPass = new OutputPass()
* composer.addPass( outputPass );
*
* function animate() {
*
* composer.render(); // instead of renderer.render()
*
* }
* ```
*
* @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
*/
class EffectComposer {
/**
* Constructs a new effect composer.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will
* be used as the internal read and write buffers. If not given, the composer creates
* the buffers automatically.
*/
constructor( renderer, renderTarget ) {
/**
* The renderer.
*
* @type {WebGLRenderer}
*/
this.renderer = renderer;
this._pixelRatio = renderer.getPixelRatio();
if ( renderTarget === undefined ) {
const size = renderer.getSize( new Vector2() );
this._width = size.width;
this._height = size.height;
renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
renderTarget.texture.name = 'EffectComposer.rt1';
} else {
this._width = renderTarget.width;
this._height = renderTarget.height;
}
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.renderTarget2.texture.name = 'EffectComposer.rt2';
/**
* A reference to the internal write buffer. Passes usually write
* their result into this buffer.
*
* @type {WebGLRenderTarget}
*/
this.writeBuffer = this.renderTarget1;
/**
* A reference to the internal read buffer. Passes usually read
* the previous render result from this buffer.
*
* @type {WebGLRenderTarget}
*/
this.readBuffer = this.renderTarget2;
/**
* Whether the final pass is rendered to the screen (default framebuffer) or not.
*
* @type {boolean}
* @default true
*/
this.renderToScreen = true;
/**
* An array representing the (ordered) chain of post-processing passes.
*
* @type {Array<Pass>}
*/
this.passes = [];
/**
* A copy pass used for internal swap operations.
*
* @private
* @type {ShaderPass}
*/
this.copyPass = new ShaderPass( CopyShader );
this.copyPass.material.blending = NoBlending;
/**
* The internal clock for managing time data.
*
* @private
* @type {Clock}
*/
this.clock = new Clock();
}
/**
* Swaps the internal read/write buffers.
*/
swapBuffers() {
const tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
}
/**
* Adds the given pass to the pass chain.
*
* @param {Pass} pass - The pass to add.
*/
addPass( pass ) {
this.passes.push( pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
/**
* Inserts the given pass at a given index.
*
* @param {Pass} pass - The pass to insert.
* @param {number} index - The index into the pass chain.
*/
insertPass( pass, index ) {
this.passes.splice( index, 0, pass );
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
/**
* Removes the given pass from the pass chain.
*
* @param {Pass} pass - The pass to remove.
*/
removePass( pass ) {
const index = this.passes.indexOf( pass );
if ( index !== - 1 ) {
this.passes.splice( index, 1 );
}
}
/**
* Returns `true` if the pass for the given index is the last enabled pass in the pass chain.
*
* @param {number} passIndex - The pass index.
* @return {boolean} Whether the pass for the given index is the last pass in the pass chain.
*/
isLastEnabledPass( passIndex ) {
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
if ( this.passes[ i ].enabled ) {
return false;
}
}
return true;
}
/**
* Executes all enabled post-processing passes in order to produce the final frame.
*
* @param {number} deltaTime - The delta time in seconds. If not given, the composer computes
* its own time delta value.
*/
render( deltaTime ) {
// deltaTime value is in seconds
if ( deltaTime === undefined ) {
deltaTime = this.clock.getDelta();
}
const currentRenderTarget = this.renderer.getRenderTarget();
let maskActive = false;
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
const pass = this.passes[ i ];
if ( pass.enabled === false ) continue;
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
if ( pass.needsSwap ) {
if ( maskActive ) {
const context = this.renderer.getContext();
const stencil = this.renderer.state.buffers.stencil;
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
}
this.swapBuffers();
}
if ( MaskPass !== undefined ) {
if ( pass instanceof MaskPass ) {
maskActive = true;
} else if ( pass instanceof ClearMaskPass ) {
maskActive = false;
}
}
}
this.renderer.setRenderTarget( currentRenderTarget );
}
/**
* Resets the internal state of the EffectComposer.
*
* @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like
* the one from the constructor. If set, it is used to setup the read and write buffers.
*/
reset( renderTarget ) {
if ( renderTarget === undefined ) {
const size = this.renderer.getSize( new Vector2() );
this._pixelRatio = this.renderer.getPixelRatio();
this._width = size.width;
this._height = size.height;
renderTarget = this.renderTarget1.clone();
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
}
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
}
/**
* Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize},
* this method honors the current pixel ration.
*
* @param {number} width - The width in logical pixels.
* @param {number} height - The height in logical pixels.
*/
setSize( width, height ) {
this._width = width;
this._height = height;
const effectiveWidth = this._width * this._pixelRatio;
const effectiveHeight = this._height * this._pixelRatio;
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
for ( let i = 0; i < this.passes.length; i ++ ) {
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
}
}
/**
* Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output.
* Setting the pixel ratio will automatically resize the composer.
*
* @param {number} pixelRatio - The pixel ratio to set.
*/
setPixelRatio( pixelRatio ) {
this._pixelRatio = pixelRatio;
this.setSize( this._width, this._height );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the composer is no longer used in your app.
*/
dispose() {
this.renderTarget1.dispose();
this.renderTarget2.dispose();
this.copyPass.dispose();
}
}
export { EffectComposer };
+195
View File
@@ -0,0 +1,195 @@
import { Pass } from './Pass.module.js'
/**
* This pass can be used to define a mask during post processing.
* Meaning only areas of subsequent post processing are affected
* which lie in the masking area of this pass. Internally, the masking
* is implemented with the stencil buffer.
*
* ```js
* const maskPass = new MaskPass( scene, camera );
* composer.addPass( maskPass );
* ```
*
* @augments Pass
* @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js';
*/
class MaskPass extends Pass {
/**
* Constructs a new mask pass.
*
* @param {Scene} scene - The 3D objects in this scene will define the mask.
* @param {Camera} camera - The camera.
*/
constructor( scene, camera ) {
super();
/**
* The scene that defines the mask.
*
* @type {Scene}
*/
this.scene = scene;
/**
* The camera.
*
* @type {Camera}
*/
this.camera = camera;
/**
* Overwritten to perform a clear operation by default.
*
* @type {boolean}
* @default true
*/
this.clear = true;
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
/**
* Whether to inverse the mask or not.
*
* @type {boolean}
* @default false
*/
this.inverse = false;
}
/**
* Performs a mask pass with the configured scene and camera.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const context = renderer.getContext();
const state = renderer.state;
// don't update color or depth
state.buffers.color.setMask( false );
state.buffers.depth.setMask( false );
// lock buffers
state.buffers.color.setLocked( true );
state.buffers.depth.setLocked( true );
// set up stencil
let writeValue, clearValue;
if ( this.inverse ) {
writeValue = 0;
clearValue = 1;
} else {
writeValue = 1;
clearValue = 0;
}
state.buffers.stencil.setTest( true );
state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE );
state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff );
state.buffers.stencil.setClear( clearValue );
state.buffers.stencil.setLocked( true );
// draw into the stencil buffer
renderer.setRenderTarget( readBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear();
renderer.render( this.scene, this.camera );
// unlock color and depth buffer and make them writable for subsequent rendering/clearing
state.buffers.color.setLocked( false );
state.buffers.depth.setLocked( false );
state.buffers.color.setMask( true );
state.buffers.depth.setMask( true );
// only render where stencil is set to 1
state.buffers.stencil.setLocked( false );
state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP );
state.buffers.stencil.setLocked( true );
}
}
/**
* This pass can be used to clear a mask previously defined with {@link MaskPass}.
*
* ```js
* const clearPass = new ClearMaskPass();
* composer.addPass( clearPass );
* ```
*
* @augments Pass
*/
class ClearMaskPass extends Pass {
/**
* Constructs a new clear mask pass.
*/
constructor() {
super();
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
}
/**
* Performs the clear of the currently defined mask.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
renderer.state.buffers.stencil.setLocked( false );
renderer.state.buffers.stencil.setTest( false );
}
}
export { MaskPass, ClearMaskPass };
@@ -0,0 +1,776 @@
import {
AdditiveBlending,
Color,
DoubleSide,
HalfFloatType,
Matrix4,
MeshDepthMaterial,
NoBlending,
RGBADepthPacking,
ShaderMaterial,
UniformsUtils,
Vector2,
Vector3,
WebGLRenderTarget
} from '../three.module.js'
import { Pass, FullScreenQuad } from './Pass.module.js'
import { CopyShader } from '../shaders/CopyShader.module.js'
/**
* A pass for rendering outlines around selected objects.
*
* ```js
* const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight );
* const outlinePass = new OutlinePass( resolution, scene, camera );
* composer.addPass( outlinePass );
* ```
*
* @augments Pass
* @three_import import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
*/
class OutlinePass extends Pass {
/**
* Constructs a new outline pass.
*
* @param {Vector2} [resolution] - The effect's resolution.
* @param {Scene} scene - The scene to render.
* @param {Camera} camera - The camera.
* @param {Array<Object3D>} [selectedObjects] - The selected 3D objects that should receive an outline.
*
*/
constructor( resolution, scene, camera, selectedObjects ) {
super();
/**
* The scene to render.
*
* @type {Object}
*/
this.renderScene = scene;
/**
* The camera.
*
* @type {Object}
*/
this.renderCamera = camera;
/**
* The selected 3D objects that should receive an outline.
*
* @type {Array<Object3D>}
*/
this.selectedObjects = selectedObjects !== undefined ? selectedObjects : [];
/**
* The visible edge color.
*
* @type {Color}
* @default (1,1,1)
*/
this.visibleEdgeColor = new Color( 1, 1, 1 );
/**
* The hidden edge color.
*
* @type {Color}
* @default (0.1,0.04,0.02)
*/
this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 );
/**
* Can be used for an animated glow/pulse effect.
*
* @type {number}
* @default 0
*/
this.edgeGlow = 0.0;
/**
* Whether to use a pattern texture for to highlight selected
* 3D objects or not.
*
* @type {boolean}
* @default false
*/
this.usePatternTexture = false;
/**
* Can be used to highlight selected 3D objects. Requires to set
* {@link OutlinePass#usePatternTexture} to `true`.
*
* @type {?Texture}
* @default null
*/
this.patternTexture = null;
/**
* The edge thickness.
*
* @type {number}
* @default 1
*/
this.edgeThickness = 1.0;
/**
* The edge strength.
*
* @type {number}
* @default 3
*/
this.edgeStrength = 3.0;
/**
* The downsample ratio. The effect can be rendered in a much
* lower resolution than the beauty pass.
*
* @type {number}
* @default 2
*/
this.downSampleRatio = 2;
/**
* The pulse period.
*
* @type {number}
* @default 0
*/
this.pulsePeriod = 0;
this._visibilityCache = new Map();
this._selectionCache = new Set();
/**
* The effect's resolution.
*
* @type {Vector2}
* @default (256,256)
*/
this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 );
const resx = Math.round( this.resolution.x / this.downSampleRatio );
const resy = Math.round( this.resolution.y / this.downSampleRatio );
this.renderTargetMaskBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y );
this.renderTargetMaskBuffer.texture.name = 'OutlinePass.mask';
this.renderTargetMaskBuffer.texture.generateMipmaps = false;
this.depthMaterial = new MeshDepthMaterial();
this.depthMaterial.side = DoubleSide;
this.depthMaterial.depthPacking = RGBADepthPacking;
this.depthMaterial.blending = NoBlending;
this.prepareMaskMaterial = this._getPrepareMaskMaterial();
this.prepareMaskMaterial.side = DoubleSide;
this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera );
this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } );
this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth';
this.renderTargetDepthBuffer.texture.generateMipmaps = false;
this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample';
this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false;
this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1';
this.renderTargetBlurBuffer1.texture.generateMipmaps = false;
this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } );
this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2';
this.renderTargetBlurBuffer2.texture.generateMipmaps = false;
this.edgeDetectionMaterial = this._getEdgeDetectionMaterial();
this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1';
this.renderTargetEdgeBuffer1.texture.generateMipmaps = false;
this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } );
this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2';
this.renderTargetEdgeBuffer2.texture.generateMipmaps = false;
const MAX_EDGE_THICKNESS = 4;
const MAX_EDGE_GLOW = 4;
this.separableBlurMaterial1 = this._getSeparableBlurMaterial( MAX_EDGE_THICKNESS );
this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy );
this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1;
this.separableBlurMaterial2 = this._getSeparableBlurMaterial( MAX_EDGE_GLOW );
this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) );
this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW;
// Overlay material
this.overlayMaterial = this._getOverlayMaterial();
// copy material
const copyShader = CopyShader;
this.copyUniforms = UniformsUtils.clone( copyShader.uniforms );
this.materialCopy = new ShaderMaterial( {
uniforms: this.copyUniforms,
vertexShader: copyShader.vertexShader,
fragmentShader: copyShader.fragmentShader,
blending: NoBlending,
depthTest: false,
depthWrite: false
} );
this.enabled = true;
this.needsSwap = false;
this._oldClearColor = new Color();
this.oldClearAlpha = 1;
this._fsQuad = new FullScreenQuad( null );
this.tempPulseColor1 = new Color();
this.tempPulseColor2 = new Color();
this.textureMatrix = new Matrix4();
function replaceDepthToViewZ( string, camera ) {
const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic';
return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' );
}
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*/
dispose() {
this.renderTargetMaskBuffer.dispose();
this.renderTargetDepthBuffer.dispose();
this.renderTargetMaskDownSampleBuffer.dispose();
this.renderTargetBlurBuffer1.dispose();
this.renderTargetBlurBuffer2.dispose();
this.renderTargetEdgeBuffer1.dispose();
this.renderTargetEdgeBuffer2.dispose();
this.depthMaterial.dispose();
this.prepareMaskMaterial.dispose();
this.edgeDetectionMaterial.dispose();
this.separableBlurMaterial1.dispose();
this.separableBlurMaterial2.dispose();
this.overlayMaterial.dispose();
this.materialCopy.dispose();
this._fsQuad.dispose();
}
/**
* Sets the size of the pass.
*
* @param {number} width - The width to set.
* @param {number} height - The height to set.
*/
setSize( width, height ) {
this.renderTargetMaskBuffer.setSize( width, height );
this.renderTargetDepthBuffer.setSize( width, height );
let resx = Math.round( width / this.downSampleRatio );
let resy = Math.round( height / this.downSampleRatio );
this.renderTargetMaskDownSampleBuffer.setSize( resx, resy );
this.renderTargetBlurBuffer1.setSize( resx, resy );
this.renderTargetEdgeBuffer1.setSize( resx, resy );
this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy );
resx = Math.round( resx / 2 );
resy = Math.round( resy / 2 );
this.renderTargetBlurBuffer2.setSize( resx, resy );
this.renderTargetEdgeBuffer2.setSize( resx, resy );
this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( resx, resy );
}
/**
* Performs the Outline pass.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
if ( this.selectedObjects.length > 0 ) {
renderer.getClearColor( this._oldClearColor );
this.oldClearAlpha = renderer.getClearAlpha();
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
if ( maskActive ) renderer.state.buffers.stencil.setTest( false );
renderer.setClearColor( 0xffffff, 1 );
this._updateSelectionCache();
// Make selected objects invisible
this._changeVisibilityOfSelectedObjects( false );
const currentBackground = this.renderScene.background;
const currentOverrideMaterial = this.renderScene.overrideMaterial;
this.renderScene.background = null;
// 1. Draw Non Selected objects in the depth buffer
this.renderScene.overrideMaterial = this.depthMaterial;
renderer.setRenderTarget( this.renderTargetDepthBuffer );
renderer.clear();
renderer.render( this.renderScene, this.renderCamera );
// Make selected objects visible
this._changeVisibilityOfSelectedObjects( true );
this._visibilityCache.clear();
// Update Texture Matrix for Depth compare
this._updateTextureMatrix();
// Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects
this._changeVisibilityOfNonSelectedObjects( false );
this.renderScene.overrideMaterial = this.prepareMaskMaterial;
this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far );
this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture;
this.prepareMaskMaterial.uniforms[ 'textureMatrix' ].value = this.textureMatrix;
renderer.setRenderTarget( this.renderTargetMaskBuffer );
renderer.clear();
renderer.render( this.renderScene, this.renderCamera );
this._changeVisibilityOfNonSelectedObjects( true );
this._visibilityCache.clear();
this._selectionCache.clear();
this.renderScene.background = currentBackground;
this.renderScene.overrideMaterial = currentOverrideMaterial;
// 2. Downsample to Half resolution
this._fsQuad.material = this.materialCopy;
this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture;
renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer );
renderer.clear();
this._fsQuad.render( renderer );
this.tempPulseColor1.copy( this.visibleEdgeColor );
this.tempPulseColor2.copy( this.hiddenEdgeColor );
if ( this.pulsePeriod > 0 ) {
const scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2;
this.tempPulseColor1.multiplyScalar( scalar );
this.tempPulseColor2.multiplyScalar( scalar );
}
// 3. Apply Edge Detection Pass
this._fsQuad.material = this.edgeDetectionMaterial;
this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture;
this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height );
this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1;
this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2;
renderer.setRenderTarget( this.renderTargetEdgeBuffer1 );
renderer.clear();
this._fsQuad.render( renderer );
// 4. Apply Blur on Half res
this._fsQuad.material = this.separableBlurMaterial1;
this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture;
this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX;
this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness;
renderer.setRenderTarget( this.renderTargetBlurBuffer1 );
renderer.clear();
this._fsQuad.render( renderer );
this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture;
this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY;
renderer.setRenderTarget( this.renderTargetEdgeBuffer1 );
renderer.clear();
this._fsQuad.render( renderer );
// Apply Blur on quarter res
this._fsQuad.material = this.separableBlurMaterial2;
this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture;
this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX;
renderer.setRenderTarget( this.renderTargetBlurBuffer2 );
renderer.clear();
this._fsQuad.render( renderer );
this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture;
this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY;
renderer.setRenderTarget( this.renderTargetEdgeBuffer2 );
renderer.clear();
this._fsQuad.render( renderer );
// Blend it additively over the input texture
this._fsQuad.material = this.overlayMaterial;
this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture;
this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture;
this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture;
this.overlayMaterial.uniforms[ 'patternTexture' ].value = this.patternTexture;
this.overlayMaterial.uniforms[ 'edgeStrength' ].value = this.edgeStrength;
this.overlayMaterial.uniforms[ 'edgeGlow' ].value = this.edgeGlow;
this.overlayMaterial.uniforms[ 'usePatternTexture' ].value = this.usePatternTexture;
if ( maskActive ) renderer.state.buffers.stencil.setTest( true );
renderer.setRenderTarget( readBuffer );
this._fsQuad.render( renderer );
renderer.setClearColor( this._oldClearColor, this.oldClearAlpha );
renderer.autoClear = oldAutoClear;
}
if ( this.renderToScreen ) {
this._fsQuad.material = this.materialCopy;
this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture;
renderer.setRenderTarget( null );
this._fsQuad.render( renderer );
}
}
// internals
_updateSelectionCache() {
const cache = this._selectionCache;
function gatherSelectedMeshesCallBack( object ) {
if ( object.isMesh ) cache.add( object );
}
cache.clear();
for ( let i = 0; i < this.selectedObjects.length; i ++ ) {
const selectedObject = this.selectedObjects[ i ];
selectedObject.traverse( gatherSelectedMeshesCallBack );
}
}
_changeVisibilityOfSelectedObjects( bVisible ) {
const cache = this._visibilityCache;
for ( const mesh of this._selectionCache ) {
if ( bVisible === true ) {
mesh.visible = cache.get( mesh );
} else {
cache.set( mesh, mesh.visible );
mesh.visible = bVisible;
}
}
}
_changeVisibilityOfNonSelectedObjects( bVisible ) {
const visibilityCache = this._visibilityCache;
const selectionCache = this._selectionCache;
function VisibilityChangeCallBack( object ) {
if ( object.isPoints || object.isLine || object.isLine2 ) {
// the visibility of points and lines is always set to false in order to
// not affect the outline computation
if ( bVisible === true ) {
object.visible = visibilityCache.get( object ); // restore
} else {
visibilityCache.set( object, object.visible );
object.visible = bVisible;
}
} else if ( object.isMesh || object.isSprite ) {
// only meshes and sprites are supported by OutlinePass
if ( ! selectionCache.has( object ) ) {
const visibility = object.visible;
if ( bVisible === false || visibilityCache.get( object ) === true ) {
object.visible = bVisible;
}
visibilityCache.set( object, visibility );
}
}
}
this.renderScene.traverse( VisibilityChangeCallBack );
}
_updateTextureMatrix() {
this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0 );
this.textureMatrix.multiply( this.renderCamera.projectionMatrix );
this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse );
}
_getPrepareMaskMaterial() {
return new ShaderMaterial( {
uniforms: {
'depthTexture': { value: null },
'cameraNearFar': { value: new Vector2( 0.5, 0.5 ) },
'textureMatrix': { value: null }
},
vertexShader:
`#include <batching_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
varying vec4 projTexCoord;
varying vec4 vPosition;
uniform mat4 textureMatrix;
void main() {
#include <batching_vertex>
#include <skinbase_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
vPosition = mvPosition;
vec4 worldPosition = vec4( transformed, 1.0 );
#ifdef USE_INSTANCING
worldPosition = instanceMatrix * worldPosition;
#endif
worldPosition = modelMatrix * worldPosition;
projTexCoord = textureMatrix * worldPosition;
}`,
fragmentShader:
`#include <packing>
varying vec4 vPosition;
varying vec4 projTexCoord;
uniform sampler2D depthTexture;
uniform vec2 cameraNearFar;
void main() {
float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord ));
float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y );
float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0;
gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0);
}`
} );
}
_getEdgeDetectionMaterial() {
return new ShaderMaterial( {
uniforms: {
'maskTexture': { value: null },
'texSize': { value: new Vector2( 0.5, 0.5 ) },
'visibleEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) },
'hiddenEdgeColor': { value: new Vector3( 1.0, 1.0, 1.0 ) },
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`varying vec2 vUv;
uniform sampler2D maskTexture;
uniform vec2 texSize;
uniform vec3 visibleEdgeColor;
uniform vec3 hiddenEdgeColor;
void main() {
vec2 invSize = 1.0 / texSize;
vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);
vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);
vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);
vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);
vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);
float diff1 = (c1.r - c2.r)*0.5;
float diff2 = (c3.r - c4.r)*0.5;
float d = length( vec2(diff1, diff2) );
float a1 = min(c1.g, c2.g);
float a2 = min(c3.g, c4.g);
float visibilityFactor = min(a1, a2);
vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor;
gl_FragColor = vec4(edgeColor, 1.0) * vec4(d);
}`
} );
}
_getSeparableBlurMaterial( maxRadius ) {
return new ShaderMaterial( {
defines: {
'MAX_RADIUS': maxRadius,
},
uniforms: {
'colorTexture': { value: null },
'texSize': { value: new Vector2( 0.5, 0.5 ) },
'direction': { value: new Vector2( 0.5, 0.5 ) },
'kernelRadius': { value: 1.0 }
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`#include <common>
varying vec2 vUv;
uniform sampler2D colorTexture;
uniform vec2 texSize;
uniform vec2 direction;
uniform float kernelRadius;
float gaussianPdf(in float x, in float sigma) {
return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;
}
void main() {
vec2 invSize = 1.0 / texSize;
float sigma = kernelRadius/2.0;
float weightSum = gaussianPdf(0.0, sigma);
vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum;
vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);
vec2 uvOffset = delta;
for( int i = 1; i <= MAX_RADIUS; i ++ ) {
float x = kernelRadius * float(i) / float(MAX_RADIUS);
float w = gaussianPdf(x, sigma);
vec4 sample1 = texture2D( colorTexture, vUv + uvOffset);
vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);
diffuseSum += ((sample1 + sample2) * w);
weightSum += (2.0 * w);
uvOffset += delta;
}
gl_FragColor = diffuseSum/weightSum;
}`
} );
}
_getOverlayMaterial() {
return new ShaderMaterial( {
uniforms: {
'maskTexture': { value: null },
'edgeTexture1': { value: null },
'edgeTexture2': { value: null },
'patternTexture': { value: null },
'edgeStrength': { value: 1.0 },
'edgeGlow': { value: 1.0 },
'usePatternTexture': { value: 0.0 }
},
vertexShader:
`varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
`varying vec2 vUv;
uniform sampler2D maskTexture;
uniform sampler2D edgeTexture1;
uniform sampler2D edgeTexture2;
uniform sampler2D patternTexture;
uniform float edgeStrength;
uniform float edgeGlow;
uniform bool usePatternTexture;
void main() {
vec4 edgeValue1 = texture2D(edgeTexture1, vUv);
vec4 edgeValue2 = texture2D(edgeTexture2, vUv);
vec4 maskColor = texture2D(maskTexture, vUv);
vec4 patternColor = texture2D(patternTexture, 6.0 * vUv);
float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5;
vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow;
vec4 finalColor = edgeStrength * maskColor.r * edgeValue;
if(usePatternTexture)
finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r);
gl_FragColor = finalColor;
}`,
blending: AdditiveBlending,
depthTest: false,
depthWrite: false,
transparent: true
} );
}
}
OutlinePass.BlurDirectionX = new Vector2( 1.0, 0.0 );
OutlinePass.BlurDirectionY = new Vector2( 0.0, 1.0 );
export { OutlinePass };
+191
View File
@@ -0,0 +1,191 @@
import {
BufferGeometry,
Float32BufferAttribute,
OrthographicCamera,
Mesh
} from '../three.module.js'
/**
* Abstract base class for all post processing passes.
*
* This module is only relevant for post processing with {@link WebGLRenderer}.
*
* @abstract
* @three_import import { Pass } from 'three/addons/postprocessing/Pass.js';
*/
class Pass {
/**
* Constructs a new pass.
*/
constructor() {
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isPass = true;
/**
* If set to `true`, the pass is processed by the composer.
*
* @type {boolean}
* @default true
*/
this.enabled = true;
/**
* If set to `true`, the pass indicates to swap read and write buffer after rendering.
*
* @type {boolean}
* @default true
*/
this.needsSwap = true;
/**
* If set to `true`, the pass clears its buffer before rendering
*
* @type {boolean}
* @default false
*/
this.clear = false;
/**
* If set to `true`, the result of the pass is rendered to screen. The last pass in the composers
* pass chain gets automatically rendered to screen, no matter how this property is configured.
*
* @type {boolean}
* @default false
*/
this.renderToScreen = false;
}
/**
* Sets the size of the pass.
*
* @abstract
* @param {number} width - The width to set.
* @param {number} height - The height to set.
*/
setSize( /* width, height */ ) {}
/**
* This method holds the render logic of a pass. It must be implemented in all derived classes.
*
* @abstract
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*
* @abstract
*/
dispose() {}
}
// Helper for passes that need to fill the viewport with a single quad.
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
// https://github.com/mrdoob/three.js/pull/21358
class FullscreenTriangleGeometry extends BufferGeometry {
constructor() {
super();
this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
}
}
const _geometry = new FullscreenTriangleGeometry();
/**
* This module is a helper for passes which need to render a full
* screen effect which is quite common in context of post processing.
*
* The intended usage is to reuse a single full screen quad for rendering
* subsequent passes by just reassigning the `material` reference.
*
* This module can only be used with {@link WebGLRenderer}.
*
* @augments Mesh
* @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
*/
class FullScreenQuad {
/**
* Constructs a new full screen quad.
*
* @param {?Material} material - The material to render te full screen quad with.
*/
constructor( material ) {
this._mesh = new Mesh( _geometry, material );
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the instance is no longer used in your app.
*/
dispose() {
this._mesh.geometry.dispose();
}
/**
* Renders the full screen quad.
*
* @param {WebGLRenderer} renderer - The renderer.
*/
render( renderer ) {
renderer.render( this._mesh, _camera );
}
/**
* The quad's material.
*
* @type {?Material}
*/
get material() {
return this._mesh.material;
}
set material( value ) {
this._mesh.material = value;
}
}
export { Pass, FullScreenQuad };
+183
View File
@@ -0,0 +1,183 @@
import {
Color
} from '../three.module.js'
import { Pass } from './Pass.module.js'
/**
* This class represents a render pass. It takes a camera and a scene and produces
* a beauty pass for subsequent post processing effects.
*
* ```js
* const renderPass = new RenderPass( scene, camera );
* composer.addPass( renderPass );
* ```
*
* @augments Pass
* @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
*/
class RenderPass extends Pass {
/**
* Constructs a new render pass.
*
* @param {Scene} scene - The scene to render.
* @param {Camera} camera - The camera.
* @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used
* for all objects in the scene.
* @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass.
* @param {?number} [clearAlpha=null] - The clear alpha of the render pass.
*/
constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) {
super();
/**
* The scene to render.
*
* @type {Scene}
*/
this.scene = scene;
/**
* The camera.
*
* @type {Camera}
*/
this.camera = camera;
/**
* The override material. If set, this material is used
* for all objects in the scene.
*
* @type {?Material}
* @default null
*/
this.overrideMaterial = overrideMaterial;
/**
* The clear color of the render pass.
*
* @type {?(number|Color|string)}
* @default null
*/
this.clearColor = clearColor;
/**
* The clear alpha of the render pass.
*
* @type {?number}
* @default null
*/
this.clearAlpha = clearAlpha;
/**
* Overwritten to perform a clear operation by default.
*
* @type {boolean}
* @default true
*/
this.clear = true;
/**
* If set to `true`, only the depth can be cleared when `clear` is to `false`.
*
* @type {boolean}
* @default false
*/
this.clearDepth = false;
/**
* Overwritten to disable the swap.
*
* @type {boolean}
* @default false
*/
this.needsSwap = false;
this._oldClearColor = new Color();
}
/**
* Performs a beauty pass with the configured scene and camera.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
let oldClearAlpha, oldOverrideMaterial;
if ( this.overrideMaterial !== null ) {
oldOverrideMaterial = this.scene.overrideMaterial;
this.scene.overrideMaterial = this.overrideMaterial;
}
if ( this.clearColor !== null ) {
renderer.getClearColor( this._oldClearColor );
renderer.setClearColor( this.clearColor, renderer.getClearAlpha() );
}
if ( this.clearAlpha !== null ) {
oldClearAlpha = renderer.getClearAlpha();
renderer.setClearAlpha( this.clearAlpha );
}
if ( this.clearDepth == true ) {
renderer.clearDepth();
}
renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );
if ( this.clear === true ) {
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
}
renderer.render( this.scene, this.camera );
// restore
if ( this.clearColor !== null ) {
renderer.setClearColor( this._oldClearColor );
}
if ( this.clearAlpha !== null ) {
renderer.setClearAlpha( oldClearAlpha );
}
if ( this.overrideMaterial !== null ) {
this.scene.overrideMaterial = oldOverrideMaterial;
}
renderer.autoClear = oldAutoClear;
}
}
export { RenderPass };
+135
View File
@@ -0,0 +1,135 @@
import {
ShaderMaterial,
UniformsUtils
} from '../three.module.js'
import { Pass, FullScreenQuad } from './Pass.module.js'
/**
* This pass can be used to create a post processing effect
* with a raw GLSL shader object. Useful for implementing custom
* effects.
*
* ```js
* const fxaaPass = new ShaderPass( FXAAShader );
* composer.addPass( fxaaPass );
* ```
*
* @augments Pass
* @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
*/
class ShaderPass extends Pass {
/**
* Constructs a new shader pass.
*
* @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as
* defines and uniforms. It's also valid to pass a custom shader material.
* @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample
* the read buffer.
*/
constructor( shader, textureID = 'tDiffuse' ) {
super();
/**
* The name of the texture uniform that should sample the read buffer.
*
* @type {string}
* @default 'tDiffuse'
*/
this.textureID = textureID;
/**
* The pass uniforms.
*
* @type {?Object}
*/
this.uniforms = null;
/**
* The pass material.
*
* @type {?ShaderMaterial}
*/
this.material = null;
if ( shader instanceof ShaderMaterial ) {
this.uniforms = shader.uniforms;
this.material = shader;
} else if ( shader ) {
this.uniforms = UniformsUtils.clone( shader.uniforms );
this.material = new ShaderMaterial( {
name: ( shader.name !== undefined ) ? shader.name : 'unspecified',
defines: Object.assign( {}, shader.defines ),
uniforms: this.uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
}
// internals
this._fsQuad = new FullScreenQuad( this.material );
}
/**
* Performs the shader pass.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
* destination for the pass.
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
* previous pass from this buffer.
* @param {number} deltaTime - The delta time in seconds.
* @param {boolean} maskActive - Whether masking is active or not.
*/
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
if ( this.uniforms[ this.textureID ] ) {
this.uniforms[ this.textureID ].value = readBuffer.texture;
}
this._fsQuad.material = this.material;
if ( this.renderToScreen ) {
renderer.setRenderTarget( null );
this._fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
this._fsQuad.render( renderer );
}
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever the pass is no longer used in your app.
*/
dispose() {
this.material.dispose();
this._fsQuad.dispose();
}
}
export { ShaderPass };
+52
View File
@@ -0,0 +1,52 @@
/**
* @module CopyShader
* @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js';
*/
/**
* Full-screen copy shader pass.
*
* @constant
* @type {ShaderMaterial~Shader}
*/
const CopyShader = {
name: 'CopyShader',
uniforms: {
'tDiffuse': { value: null },
'opacity': { value: 1.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
gl_FragColor = opacity * texel;
}`
};
export { CopyShader };
+2 -1
View File
@@ -10,7 +10,7 @@ class Buildoz extends HTMLElement {
// anything added here will be observed for all buildoz tags
// in your child, add you local 'color' observable attr with :
// return([...super.observedAttributes, 'color'])
return([])
return(['name'])
}
static define(name, cls){
@@ -31,6 +31,7 @@ class Buildoz extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
this.attrs[name] = newValue
if(name=='name') this.name = newValue
}
getBZAttribute(attrName){ // Little helper for defaults
+1
View File
@@ -1036,6 +1036,7 @@ article[eiccard] header button.collapser {
align-self: center;
justify-self: end;
border: none;
min-width: 2em !important;
}
article[eiccard] section {
+1
View File
@@ -0,0 +1 @@
@font-face{font-display:block;font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-ReguLine truncated
+1
View File
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.katex=t():e.katex=t()}("undefined"!=typeof self?self:this,(function(){return function(){"use strict";var e={d:function(t,r){for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},t={};e.d(t,{default:function(){return Wn}});class r{constructor(e,t){this.name=void 0,this.position=void 0,this.length=void 0,this.rawMessage=void 0;let n,o,s="KaTeX parse error: "+e;const i=t&&t.loc;if(i&&i.start<=i.end){const e=i.lexer.input;n=i.start,o=i.end,n===e.length?s+=" at end of input: ":s+=" at position "+(n+1)+": ";const t=e.slice(n,o).replace(/[^]/g,"$&\u0332");let r,a;r=n>15?"\u2026"+e.slice(n-15,n):e.slice(0,n),a=o+15<e.length?e.slice(o,o+15)+"\u2026":e.slice(o),s+=r+t+a}const a=new Error(s);return a.name="ParseError",a.__proto__=r.prototype,a.position=n,null!=n&&null!=o&&(a.length=o-n),a.rawMessage=e,a}}r.prototype.__proto__=Error.prototype;var n=r;const o=/([A-Z])/g,s={"&":"&amp;",">":"&gt;","<":"&lt;",'"':"&quot;","'":"&#x27;"},i=/[&><"']/g;const a=function(e){return"ordgroup"===e.type||"color"===e.type?1===e.body.length?a(e.body[0]):e:"font"===e.type?a(e.body):e};var l={deflt:function(e,t){return void 0===e?t:e},escape:function(e){return String(e).replace(i,(e=>s[e]))},hyphenate:function(e){return e.replace(o,"-$1").toLowerCase()},getBaseElem:a,isCharacterBox:function(e){const t=a(e);return"mathord"===t.type||"textord"===t.type||"atom"===t.type},protocolFromUrl:function(e){const t=/^[\x00-\x20]*([^\\/#?]*?)(:|&#0*58|&#x0*3a|&colon)/i.exec(e);return t?":"!==t[2]?null:/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(t[1])?t[1].toLowerCase():null:"_relative"}};const h={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.",cli:"-d, --display-mode"},output:{type:{enum:["htmlAndMathml","html","mathml"]},description:"Determines the markup language of the output.",cli:"-F, --format <type>"},leqno:{type:"boolean",description:"Render display math in leqno style (left-justified tags)."},fleqn:{type:"boolean",description:"Render display math flush left."},throwOnError:{type:"boolean",default:!0,cli:"-t, --no-throw-on-error",cliDescription:"Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error."},errorColor:{type:"string",default:"#cc0000",cli:"-c, --error-color <color>",cliDescription:"A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.",cliProcessor:e=>"#"+e},macros:{type:"object",cli:"-m, --macro <def>",cliDescription:"Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).",cliDefault:[],cliProcessor:(e,t)=>(t.push(e),t)},minRuleThickness:{type:"number",description:"Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.",processor:e=>Math.max(0,e),cli:"--min-rule-thickness <size>",cliProcessor:parseFloat},colorIsTextColor:{type:"boolean",description:"Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.",cli:"-b, --color-is-text-color"},strict:{type:[{enum:["warn","ignore","error"]},"boolean","function"],description:"Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.",cli:"-S, --strict",cliDefault:!1},trust:{type:["boolean","function"],description:"Trust the input, enabling all HTML features such as \\url.",cli:"-T, --trust"},maxSize:{type:"number",default:1/0,description:"If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large",processor:e=>Math.max(0,e),cli:"-s, --max-size <n>",cliProcessor:parseInt},maxExpand:{type:"number",default:1e3,description:"Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.",processor:e=>Math.max(0,e),cli:"-e, --max-expand <n>",cliProcessor:e=>"Infinity"===e?1/0:parseInt(e)},globalGroup:{type:"boolean",cli:!1}};function c(e){if(e.default)return e.default;const t=e.type,r=Array.isArray(t)?t[0]:t;if("string"!=typeof r)return r.enum[0];switch(r){case"boolean":return!1;case"string":return"";case"number":return 0;case"object":return{}}}class m{constructor(e){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.Line truncated
+36 -20
View File
@@ -29,7 +29,7 @@ class KeyframeView extends WindozDomContent {
this.agentSprites = sprites
this.agentTypes = types
this.currentAgentType = null
this.outputs.agentsSelector.fillOptions( this.agentTypes.map(item => {
return({ markup: `<i class="icon-${item.atp_hascode ? 'bug' : 'atom1'}"></i>${item.atp_name}`, value: item.atp_id})
@@ -48,44 +48,59 @@ class KeyframeView extends WindozDomContent {
this.outputs.btnAddAgent.disabled = true
this.outputs.btnRemoveAgent.disabled = true
this.outputs.btnSaveKF.disabled = true
this.currentlySelectedAid = null
}
async onChangeAgent(event){
if(this.outputs.agentsSelector.value) this.agentPreview.setAgent(this.outputs.agentsSelector.value)
if(!this.outputs.agentsSelector.value) return
const agent = await this.models.agents.getProperties(this.outputs.agentsSelector.value)
this.fillAgentProperties('', agent.atp_props)
if(this.notUserChange) {
} else {
this.currentAgentType = await this.models.agents.getProperties(this.outputs.agentsSelector.value)
this.fillAgentProperties('', this.currentAgentType.atp_props)
// Deselect any on-scene selection
this.kfArena.highlighted3DObjects.length = 0
this.currentlySelectedAid = null
}
}
onclickAgent(aid){
console.log('Agent clicked:', aid)
onclickAgent(obj3D){
const aid = obj3D.name
this.kfArena.highlighted3DObjects.length = 0 //truncate but keep the ref !
if(this.currentlySelectedAid == aid){ // Deselect
this.currentlySelectedAid = null
} else { // Select
this.currentlySelectedAid = aid
this.kfArena.highlighted3DObjects.push(obj3D)
this.fillAgentProperties(aid, this.kfArena.agents[aid].props, this.kfArena.agents[aid].values)
this.notUserChange = true
this.outputs.agentsSelector.value = this.kfArena.agents[aid].type
this.notUserChange = false
}
this.updateKfButtons()
}
onAddAgent(event){
//TODO prevent collisions !
const aid = crypto.randomUUIDv7()
this.output('agentId', `ID: ${aid}`)
this.kfArena.addAgent(this.outputs.agentsSelector.value, aid, {
position: {
x: document.querySelector('[name="position.x"]').value,
y: document.querySelector('[name="position.y"]').value,
z: document.querySelector('[name="position.z"]').value,
}
})
const AgentValues = this.getFieldsValues('div[data-output="agentProperties"]')
this.kfArena.addAgent(this.outputs.agentsSelector.value, aid, this.currentAgentType.atp_props , AgentValues)
this.updateKfButtons()
}
updateKfButtons(){
if(this.kfArena.agents.length > 0) this.outputs.btnSaveKF.disabled = false
if(Object.keys(this.kfArena.agents).length > 0) this.outputs.btnSaveKF.disabled = false
}
fillAgentProperties(aid, agentProps){
fillAgentProperties(aid, agentProps, agentValues = {}){
this.outputs.agentProperties.innerHTML = `
<div data-output="agentId">ID: ${aid}</div>
`
this.outputs.agentProperties.append(...this.fieldsFromJSON(agentProps, 'Internal properties'))
this.outputs.agentProperties.append(...this.fieldsFromJSON(agentProps, agentValues, 'Internal properties'))
this.outputs.agentProperties.append(...this.fieldsFromJSON({
"position.x": {
label: "Position X",
@@ -102,7 +117,7 @@ class KeyframeView extends WindozDomContent {
type: "number",
default: "0"
},
}, 'Coordinates'))
}, agentValues, 'Coordinates'))
this.outputs.agentProperties.append(...this.fieldsFromJSON({
"speed.x": {
label: "Speed X",
@@ -119,13 +134,14 @@ class KeyframeView extends WindozDomContent {
type: "number",
default: "0"
},
}, 'Speed vector'))
}, agentValues, 'Speed vector'))
this.outputs.btnAddAgent.disabled = false
this.setupRefs()
}
onSaveKF(evt){
console.log('SAVE:',this.kfArena.agents)
async onSaveKF(evt){
console.log('SAVE:',this.kfArena.agents)
await this.models.keyframe.save(this.kfArena.agents)
}
}
+20 -14
View File
@@ -14,7 +14,7 @@ export class kfArena{
this.sceneSize = app.Assets.Store.json.arenaConfig.arenaSize
this.initScene()
this.raycaster = new THREE.Raycaster()
this.agents = []
this.agents = {}
this.onclickAgent = null
}
@@ -58,6 +58,13 @@ export class kfArena{
this.scene.add(this.axes)
this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl })
this.init3DHighlighter({
edgeStrength: 3,
visibleEdgeColor: 0xffff00,
edgeGlow: 1,
edgeThickness: 4,
pulsePeriod: 2,
})
this.canvasEl.addEventListener('click', this.onSceneClick.bind(this))
}
@@ -68,11 +75,9 @@ export class kfArena{
render() {
TWEEN.update()
if(this.resizeRendererToDisplaySize()) {
this.camera.aspect = this.renderer.domElement.clientWidth / this.canvasEl.clientHeight
this.camera.updateProjectionMatrix()
}
this.renderer.render(this.scene, this.camera)
this.resizeRendererToDisplaySize()
if(this.composer) this.composer.render()
else this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render.bind(this))
}
@@ -114,30 +119,31 @@ export class kfArena{
if (intersects.length > 0) {
const hit = this.getNamedParent(intersects[0].object)
if(hit) this.onclickAgent(hit.name)
if(hit) this.onclickAgent(hit)
}
}
addAgent(typeId, aid, properties){
addAgent(typeId, aid, properties, values){
const agentSprite = this.agentSprites.find(item => item.atp_id==typeId)
if(!agentSprite) return
const agentObj = this.agentFromJSON(aid, agentSprite.asp_3d)
agentObj.position.set(properties.position.x, properties.position.z, properties.position.y )
agentObj.position.set(values.position.x, values.position.z, values.position.y )
//TODO Speed vector
this.scene.add(agentObj)
this.agents.push({
aid: aid,
this.agents[aid] = {
type: typeId,
props: properties,
})
values: values,
}
}
removeAgent(aid){
const obj3d = scene.getObjectByName(aid)
this.scene.remove(obj3d)
this.agents = this.agents.filter(a => a.aid !== aid)
if(aid in this.agents) delete(this.agents[aid])
}
// getAllAgents(){
+14 -13
View File
@@ -1,10 +1,10 @@
<style type="text/css">
.icon-list {
align-items: center;
display: flex;
flex-wrap: wrap;
article.styleguide .icon-list {
align-items: center;
display: flex;
flex-wrap: wrap;
}
.icon-list li {
article.styleguide .icon-list li {
list-style: none;
display: grid;
justify-items: center;
@@ -12,7 +12,7 @@
font-family: var(--eicui-base-font-family);
width: 100px;
}
.icon-list li i {
article.styleguide .icon-list li i {
padding: 10px 10px;
margin: 3px;
border: 1px solid gray;
@@ -22,13 +22,14 @@
justify-content: center;
font-size: var(--eicui-base-font-size-2xl);
}
.icon-list li label { font-size: var(--eicui-base-font-size-xs); }
.styleguide > header { background: url('/app/assets/images/cards/styleguide.jpg'); }
.grid-hilite span {
display: block;
min-height: 30px;
border: 1px solid var(--eicui-base-color-primary-100);
}
article.styleguide .icon-list li label { font-size: var(--eicui-base-font-size-xs); }
article.styleguide .styleguide > header { background: url('/app/assets/images/cards/styleguide.jpg'); }
article.styleguide .grid-hilite span {
display: block;
min-height: 30px;
border: 1px solid var(--eicui-base-color-primary-100);
}
article.styleguide article[eiccard] > header h1 { text-align: left; }
</style>
<article eiccard media class="styleguide">
<header>
+2 -1
View File
@@ -39,7 +39,8 @@ class styleguideView extends WindozDomContent {
}
}
ui.eicfy(this.el);
ui.eicfy(this.el)
}
}
+3 -3
View File
@@ -126,8 +126,8 @@ class Controller {
static processTemplate(name, html, templateData) {
// Define a function inside of which templateData keys will become local variables
// AND where templateData values will become thos variable values ;-)
// Drawback is that complex epression
// AND where templateData values will become those variable values ;-)
// Drawback is that complex expression
function evalaluateTemplate(tpl, params) {
var interpretor = Function(...Object.keys(params), 'return('+tpl+');' );
return interpretor(...Object.values(params));
@@ -163,7 +163,7 @@ class Controller {
}
}
// Now that all remaining expressions are OK, evaluate the template via backtits !
return(evalaluateTemplate('`' + html + '`', templateData));
}