on scene selection OK + keyframe model
This commit is contained in:
1 parent
6931a777a2
commit
4e1f6886f2
23 files changed
+2107
-64
No files matched your search
@@ -15,6 +15,12 @@
|
||||
"uri": "/api/agent/{id}",
|
||||
"method": "GET"
|
||||
}
|
||||
},
|
||||
"/keyframes": {
|
||||
"save": {
|
||||
"uri": "/api/keyframes/{id}",
|
||||
"method": "PUT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
Vendored
+2
-1
@@ -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
|
||||
|
||||
Vendored
+1
@@ -1036,6 +1036,7 @@ article[eiccard] header button.collapser {
|
||||
align-self: center;
|
||||
justify-self: end;
|
||||
border: none;
|
||||
min-width: 2em !important;
|
||||
}
|
||||
|
||||
article[eiccard] section {
|
||||
|
||||
Vendored
+1
@@ -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
|
||||
Vendored
+1
@@ -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={"&":"&",">":">","<":"<",'"':""","'":"'"},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]*([^\\/#?]*?)(:|�*58|�*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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -39,7 +39,8 @@ class styleguideView extends WindozDomContent {
|
||||
}
|
||||
}
|
||||
|
||||
ui.eicfy(this.el);
|
||||
ui.eicfy(this.el)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user