Procházet zdrojové kódy

NodeEditor: Preview (#23508)

* add ViewHelper to examples

* fix type conversion

* cleanup

* fix initial value

* fix toggle flipY

* add PreviewEditor

* fix change parent connections
sunag před 3 roky
rodič
revize
813daf1f56

+ 5 - 282
editor/js/Viewport.ViewHelper.js

@@ -1,15 +1,12 @@
 import { UIPanel } from './libs/ui.js';
 
-import * as THREE from 'three';
+import { ViewHelper as ViewHelperBase } from '../../examples/jsm/helpers/ViewHelper.js';
 
-class ViewHelper extends THREE.Object3D {
+class ViewHelper extends ViewHelperBase {
 
 	constructor( editorCamera, container ) {
 
-		super();
-
-		this.animating = false;
-		this.controls = null;
+		super( editorCamera, container.dom );
 
 		const panel = new UIPanel();
 		panel.setId( 'viewHelper' );
@@ -19,13 +16,11 @@ class ViewHelper extends THREE.Object3D {
 		panel.setHeight( '128px' );
 		panel.setWidth( '128px' );
 
-		const scope = this;
-
-		panel.dom.addEventListener( 'pointerup', function ( event ) {
+		panel.dom.addEventListener( 'pointerup', ( event ) => {
 
 			event.stopPropagation();
 
-			scope.handleClick( event );
+			this.handleClick( event );
 
 		} );
 
@@ -37,280 +32,8 @@ class ViewHelper extends THREE.Object3D {
 
 		container.add( panel );
 
-		const color1 = new THREE.Color( '#ff3653' );
-		const color2 = new THREE.Color( '#8adb00' );
-		const color3 = new THREE.Color( '#2c8fff' );
-
-		const interactiveObjects = [];
-		const raycaster = new THREE.Raycaster();
-		const mouse = new THREE.Vector2();
-		const dummy = new THREE.Object3D();
-
-		const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 );
-		camera.position.set( 0, 0, 2 );
-
-		const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 );
-
-		const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) );
-		const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) );
-		const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) );
-
-		yAxis.rotation.z = Math.PI / 2;
-		zAxis.rotation.y = - Math.PI / 2;
-
-		this.add( xAxis );
-		this.add( zAxis );
-		this.add( yAxis );
-
-		const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) );
-		posXAxisHelper.userData.type = 'posX';
-		const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) );
-		posYAxisHelper.userData.type = 'posY';
-		const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) );
-		posZAxisHelper.userData.type = 'posZ';
-		const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) );
-		negXAxisHelper.userData.type = 'negX';
-		const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) );
-		negYAxisHelper.userData.type = 'negY';
-		const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) );
-		negZAxisHelper.userData.type = 'negZ';
-
-		posXAxisHelper.position.x = 1;
-		posYAxisHelper.position.y = 1;
-		posZAxisHelper.position.z = 1;
-		negXAxisHelper.position.x = - 1;
-		negXAxisHelper.scale.setScalar( 0.8 );
-		negYAxisHelper.position.y = - 1;
-		negYAxisHelper.scale.setScalar( 0.8 );
-		negZAxisHelper.position.z = - 1;
-		negZAxisHelper.scale.setScalar( 0.8 );
-
-		this.add( posXAxisHelper );
-		this.add( posYAxisHelper );
-		this.add( posZAxisHelper );
-		this.add( negXAxisHelper );
-		this.add( negYAxisHelper );
-		this.add( negZAxisHelper );
-
-		interactiveObjects.push( posXAxisHelper );
-		interactiveObjects.push( posYAxisHelper );
-		interactiveObjects.push( posZAxisHelper );
-		interactiveObjects.push( negXAxisHelper );
-		interactiveObjects.push( negYAxisHelper );
-		interactiveObjects.push( negZAxisHelper );
-
-		const point = new THREE.Vector3();
-		const dim = 128;
-		const turnRate = 2 * Math.PI; // turn rate in angles per second
-
-		this.render = function ( renderer ) {
-
-			this.quaternion.copy( editorCamera.quaternion ).invert();
-			this.updateMatrixWorld();
-
-			point.set( 0, 0, 1 );
-			point.applyQuaternion( editorCamera.quaternion );
-
-			if ( point.x >= 0 ) {
-
-				posXAxisHelper.material.opacity = 1;
-				negXAxisHelper.material.opacity = 0.5;
-
-			} else {
-
-				posXAxisHelper.material.opacity = 0.5;
-				negXAxisHelper.material.opacity = 1;
-
-			}
-
-			if ( point.y >= 0 ) {
-
-				posYAxisHelper.material.opacity = 1;
-				negYAxisHelper.material.opacity = 0.5;
-
-			} else {
-
-				posYAxisHelper.material.opacity = 0.5;
-				negYAxisHelper.material.opacity = 1;
-
-			}
-
-			if ( point.z >= 0 ) {
-
-				posZAxisHelper.material.opacity = 1;
-				negZAxisHelper.material.opacity = 0.5;
-
-			} else {
-
-				posZAxisHelper.material.opacity = 0.5;
-				negZAxisHelper.material.opacity = 1;
-
-			}
-
-			//
-
-			const x = container.dom.offsetWidth - dim;
-
-			renderer.clearDepth();
-			renderer.setViewport( x, 0, dim, dim );
-			renderer.render( this, camera );
-
-		};
-
-		const targetPosition = new THREE.Vector3();
-		const targetQuaternion = new THREE.Quaternion();
-
-		const q1 = new THREE.Quaternion();
-		const q2 = new THREE.Quaternion();
-		let radius = 0;
-
-		this.handleClick = function ( event ) {
-
-			if ( this.animating === true ) return false;
-
-			const rect = container.dom.getBoundingClientRect();
-			const offsetX = rect.left + ( container.dom.offsetWidth - dim );
-			const offsetY = rect.top + ( container.dom.offsetHeight - dim );
-			mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1;
-			mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1;
-
-			raycaster.setFromCamera( mouse, camera );
-
-			const intersects = raycaster.intersectObjects( interactiveObjects );
-
-			if ( intersects.length > 0 ) {
-
-				const intersection = intersects[ 0 ];
-				const object = intersection.object;
-
-				prepareAnimationData( object, this.controls.center );
-
-				this.animating = true;
-
-				return true;
-
-			} else {
-
-				return false;
-
-			}
-
-		};
-
-		this.update = function ( delta ) {
-
-			const step = delta * turnRate;
-			const focusPoint = this.controls.center;
-
-			// animate position by doing a slerp and then scaling the position on the unit sphere
-
-			q1.rotateTowards( q2, step );
-			editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint );
-
-			// animate orientation
-
-			editorCamera.quaternion.rotateTowards( targetQuaternion, step );
-
-			if ( q1.angleTo( q2 ) === 0 ) {
-
-				this.animating = false;
-
-			}
-
-		};
-
-		function prepareAnimationData( object, focusPoint ) {
-
-			switch ( object.userData.type ) {
-
-				case 'posX':
-					targetPosition.set( 1, 0, 0 );
-					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) );
-					break;
-
-				case 'posY':
-					targetPosition.set( 0, 1, 0 );
-					targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) );
-					break;
-
-				case 'posZ':
-					targetPosition.set( 0, 0, 1 );
-					targetQuaternion.setFromEuler( new THREE.Euler() );
-					break;
-
-				case 'negX':
-					targetPosition.set( - 1, 0, 0 );
-					targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) );
-					break;
-
-				case 'negY':
-					targetPosition.set( 0, - 1, 0 );
-					targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) );
-					break;
-
-				case 'negZ':
-					targetPosition.set( 0, 0, - 1 );
-					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
-					break;
-
-				default:
-					console.error( 'ViewHelper: Invalid axis.' );
-
-			}
-
-			//
-
-			radius = editorCamera.position.distanceTo( focusPoint );
-			targetPosition.multiplyScalar( radius ).add( focusPoint );
-
-			dummy.position.copy( focusPoint );
-
-			dummy.lookAt( editorCamera.position );
-			q1.copy( dummy.quaternion );
-
-			dummy.lookAt( targetPosition );
-			q2.copy( dummy.quaternion );
-
-		}
-
-		function getAxisMaterial( color ) {
-
-			return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } );
-
-		}
-
-		function getSpriteMaterial( color, text = null ) {
-
-			const canvas = document.createElement( 'canvas' );
-			canvas.width = 64;
-			canvas.height = 64;
-
-			const context = canvas.getContext( '2d' );
-			context.beginPath();
-			context.arc( 32, 32, 16, 0, 2 * Math.PI );
-			context.closePath();
-			context.fillStyle = color.getStyle();
-			context.fill();
-
-			if ( text !== null ) {
-
-				context.font = '24px Arial';
-				context.textAlign = 'center';
-				context.fillStyle = '#000000';
-				context.fillText( text, 32, 41 );
-
-			}
-
-			const texture = new THREE.CanvasTexture( canvas );
-
-			return new THREE.SpriteMaterial( { map: texture, toneMapped: false } );
-
-		}
-
 	}
 
 }
 
-ViewHelper.prototype.isViewHelper = true;
-
 export { ViewHelper };

+ 295 - 0
examples/jsm/helpers/ViewHelper.js

@@ -0,0 +1,295 @@
+import * as THREE from 'three';
+
+const vpTemp = new THREE.Vector4();
+
+class ViewHelper extends THREE.Object3D {
+
+	constructor( editorCamera, dom ) {
+
+		super();
+
+		this.animating = false;
+		this.controls = null;
+
+		const color1 = new THREE.Color( '#ff3653' );
+		const color2 = new THREE.Color( '#8adb00' );
+		const color3 = new THREE.Color( '#2c8fff' );
+
+		const interactiveObjects = [];
+		const raycaster = new THREE.Raycaster();
+		const mouse = new THREE.Vector2();
+		const dummy = new THREE.Object3D();
+
+		const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 );
+		camera.position.set( 0, 0, 2 );
+
+		const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 );
+
+		const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) );
+		const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) );
+		const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) );
+
+		yAxis.rotation.z = Math.PI / 2;
+		zAxis.rotation.y = - Math.PI / 2;
+
+		this.add( xAxis );
+		this.add( zAxis );
+		this.add( yAxis );
+
+		const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) );
+		posXAxisHelper.userData.type = 'posX';
+		const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) );
+		posYAxisHelper.userData.type = 'posY';
+		const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) );
+		posZAxisHelper.userData.type = 'posZ';
+		const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) );
+		negXAxisHelper.userData.type = 'negX';
+		const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) );
+		negYAxisHelper.userData.type = 'negY';
+		const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) );
+		negZAxisHelper.userData.type = 'negZ';
+
+		posXAxisHelper.position.x = 1;
+		posYAxisHelper.position.y = 1;
+		posZAxisHelper.position.z = 1;
+		negXAxisHelper.position.x = - 1;
+		negXAxisHelper.scale.setScalar( 0.8 );
+		negYAxisHelper.position.y = - 1;
+		negYAxisHelper.scale.setScalar( 0.8 );
+		negZAxisHelper.position.z = - 1;
+		negZAxisHelper.scale.setScalar( 0.8 );
+
+		this.add( posXAxisHelper );
+		this.add( posYAxisHelper );
+		this.add( posZAxisHelper );
+		this.add( negXAxisHelper );
+		this.add( negYAxisHelper );
+		this.add( negZAxisHelper );
+
+		interactiveObjects.push( posXAxisHelper );
+		interactiveObjects.push( posYAxisHelper );
+		interactiveObjects.push( posZAxisHelper );
+		interactiveObjects.push( negXAxisHelper );
+		interactiveObjects.push( negYAxisHelper );
+		interactiveObjects.push( negZAxisHelper );
+
+		const point = new THREE.Vector3();
+		const dim = 128;
+		const turnRate = 2 * Math.PI; // turn rate in angles per second
+
+		this.render = function ( renderer ) {
+
+			this.quaternion.copy( editorCamera.quaternion ).invert();
+			this.updateMatrixWorld();
+
+			point.set( 0, 0, 1 );
+			point.applyQuaternion( editorCamera.quaternion );
+
+			if ( point.x >= 0 ) {
+
+				posXAxisHelper.material.opacity = 1;
+				negXAxisHelper.material.opacity = 0.5;
+
+			} else {
+
+				posXAxisHelper.material.opacity = 0.5;
+				negXAxisHelper.material.opacity = 1;
+
+			}
+
+			if ( point.y >= 0 ) {
+
+				posYAxisHelper.material.opacity = 1;
+				negYAxisHelper.material.opacity = 0.5;
+
+			} else {
+
+				posYAxisHelper.material.opacity = 0.5;
+				negYAxisHelper.material.opacity = 1;
+
+			}
+
+			if ( point.z >= 0 ) {
+
+				posZAxisHelper.material.opacity = 1;
+				negZAxisHelper.material.opacity = 0.5;
+
+			} else {
+
+				posZAxisHelper.material.opacity = 0.5;
+				negZAxisHelper.material.opacity = 1;
+
+			}
+
+			//
+
+			const x = dom.offsetWidth - dim;
+
+			renderer.clearDepth();
+
+			renderer.getViewport( vpTemp );
+			renderer.setViewport( x, 0, dim, dim );
+
+			renderer.render( this, camera );
+
+			renderer.setViewport( vpTemp.x, vpTemp.y, vpTemp.z, vpTemp.w );
+
+		};
+
+		const targetPosition = new THREE.Vector3();
+		const targetQuaternion = new THREE.Quaternion();
+
+		const q1 = new THREE.Quaternion();
+		const q2 = new THREE.Quaternion();
+		let radius = 0;
+
+		this.handleClick = function ( event ) {
+
+			if ( this.animating === true ) return false;
+
+			const rect = dom.getBoundingClientRect();
+			const offsetX = rect.left + ( container.dom.offsetWidth - dim );
+			const offsetY = rect.top + ( container.dom.offsetHeight - dim );
+			mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1;
+			mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1;
+
+			raycaster.setFromCamera( mouse, camera );
+
+			const intersects = raycaster.intersectObjects( interactiveObjects );
+
+			if ( intersects.length > 0 ) {
+
+				const intersection = intersects[ 0 ];
+				const object = intersection.object;
+
+				prepareAnimationData( object, this.controls.center );
+
+				this.animating = true;
+
+				return true;
+
+			} else {
+
+				return false;
+
+			}
+
+		};
+
+		this.update = function ( delta ) {
+
+			const step = delta * turnRate;
+			const focusPoint = this.controls.center;
+
+			// animate position by doing a slerp and then scaling the position on the unit sphere
+
+			q1.rotateTowards( q2, step );
+			editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint );
+
+			// animate orientation
+
+			editorCamera.quaternion.rotateTowards( targetQuaternion, step );
+
+			if ( q1.angleTo( q2 ) === 0 ) {
+
+				this.animating = false;
+
+			}
+
+		};
+
+		function prepareAnimationData( object, focusPoint ) {
+
+			switch ( object.userData.type ) {
+
+				case 'posX':
+					targetPosition.set( 1, 0, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) );
+					break;
+
+				case 'posY':
+					targetPosition.set( 0, 1, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) );
+					break;
+
+				case 'posZ':
+					targetPosition.set( 0, 0, 1 );
+					targetQuaternion.setFromEuler( new THREE.Euler() );
+					break;
+
+				case 'negX':
+					targetPosition.set( - 1, 0, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) );
+					break;
+
+				case 'negY':
+					targetPosition.set( 0, - 1, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) );
+					break;
+
+				case 'negZ':
+					targetPosition.set( 0, 0, - 1 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
+					break;
+
+				default:
+					console.error( 'ViewHelper: Invalid axis.' );
+
+			}
+
+			//
+
+			radius = editorCamera.position.distanceTo( focusPoint );
+			targetPosition.multiplyScalar( radius ).add( focusPoint );
+
+			dummy.position.copy( focusPoint );
+
+			dummy.lookAt( editorCamera.position );
+			q1.copy( dummy.quaternion );
+
+			dummy.lookAt( targetPosition );
+			q2.copy( dummy.quaternion );
+
+		}
+
+		function getAxisMaterial( color ) {
+
+			return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } );
+
+		}
+
+		function getSpriteMaterial( color, text = null ) {
+
+			const canvas = document.createElement( 'canvas' );
+			canvas.width = 64;
+			canvas.height = 64;
+
+			const context = canvas.getContext( '2d' );
+			context.beginPath();
+			context.arc( 32, 32, 16, 0, 2 * Math.PI );
+			context.closePath();
+			context.fillStyle = color.getStyle();
+			context.fill();
+
+			if ( text !== null ) {
+
+				context.font = '24px Arial';
+				context.textAlign = 'center';
+				context.fillStyle = '#000000';
+				context.fillText( text, 32, 41 );
+
+			}
+
+			const texture = new THREE.CanvasTexture( canvas );
+
+			return new THREE.SpriteMaterial( { map: texture, toneMapped: false } );
+
+		}
+
+	}
+
+}
+
+ViewHelper.prototype.isViewHelper = true;
+
+export { ViewHelper };

+ 6 - 0
examples/jsm/node-editor/NodeEditor.js

@@ -22,6 +22,7 @@ import { NormalMapEditor } from './display/NormalMapEditor.js';
 import { UVEditor } from './accessors/UVEditor.js';
 import { PositionEditor } from './accessors/PositionEditor.js';
 import { NormalEditor } from './accessors/NormalEditor.js';
+import { PreviewEditor } from './utils/PreviewEditor.js';
 import { TimerEditor } from './utils/TimerEditor.js';
 import { OscillatorEditor } from './utils/OscillatorEditor.js';
 import { SplitEditor } from './utils/SplitEditor.js';
@@ -178,6 +179,11 @@ export const NodeList = [
 		name: 'Utils',
 		icon: 'apps',
 		children: [
+			{
+				name: 'Preview',
+				icon: 'square-check',
+				nodeClass: PreviewEditor
+			},
 			{
 				name: 'Timer',
 				icon: 'clock',

+ 1 - 1
examples/jsm/node-editor/inputs/TextureEditor.js

@@ -134,7 +134,7 @@ export class TextureEditor extends BaseNode {
 			texture.wrapS = Number( this.wrapSInput.getValue() );
 			texture.wrapT = Number( this.wrapTInput.getValue() );
 			texture.flipY = this.flipYInput.getValue();
-			texture.needsUpdate = true;
+			texture.dispose();
 
 			this.invalidate();
 

+ 1 - 1
examples/jsm/node-editor/inputs/Vector4Editor.js

@@ -22,7 +22,7 @@ export class Vector4Editor extends BaseNode {
 		const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
 		const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
 		const fieldZ = new NumberInput().setTagColor( 'blue' ).onChange( onUpdate );
-		const fieldW = new NumberInput().setTagColor( 'white' ).onChange( onUpdate );
+		const fieldW = new NumberInput( 1 ).setTagColor( 'white' ).onChange( onUpdate );
 
 		this.add( new LabelElement( 'XYZW' )
 			.add( fieldX )

+ 2 - 2
examples/jsm/node-editor/materials/BasicMaterialEditor.js

@@ -1,7 +1,7 @@
 import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
 import { BaseNode } from '../core/BaseNode.js';
 import { MeshBasicNodeMaterial } from '../../renderers/nodes/Nodes.js';
-import * as THREE from 'three';
+import { MathUtils } from 'three';
 
 export class BasicMaterialEditor extends BaseNode {
 
@@ -68,7 +68,7 @@ export class BasicMaterialEditor extends BaseNode {
 		// TODO: Fix on NodeMaterial System
 		material.customProgramCacheKey = () => {
 
-			return THREE.MathUtils.generateUUID();
+			return MathUtils.generateUUID();
 
 		};
 

+ 166 - 0
examples/jsm/node-editor/utils/PreviewEditor.js

@@ -0,0 +1,166 @@
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ViewHelper } from 'three/addons/helpers/ViewHelper.js';
+import { Element, LabelElement, SelectInput } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
+import { MeshBasicNodeMaterial, FloatNode } from '../../renderers/nodes/Nodes.js';
+import { WebGLRenderer, PerspectiveCamera, Scene, Mesh, DoubleSide, SphereGeometry, BoxGeometry, PlaneGeometry, TorusKnotGeometry } from 'three';
+
+const nullValue = new FloatNode().setConst( true );
+
+const sceneDict = {};
+
+const getScene = ( name ) => {
+
+	let scene = sceneDict[ name ];
+
+	if ( scene === undefined ) {
+
+		scene = new Scene();
+
+		if ( name === 'box' ) {
+
+			const box = new Mesh( new BoxGeometry( 1.3, 1.3, 1.3 ) );
+			scene.add( box );
+
+		} else if ( name === 'sphere' ) {
+
+			const sphere = new Mesh( new SphereGeometry( 1, 32, 16 ) );
+			scene.add( sphere );
+
+		} else if ( name === 'plane' || name === 'sprite' ) {
+
+			const plane = new Mesh( new PlaneGeometry( 2, 2 ) );
+			scene.add( plane );
+
+
+		} else if ( name === 'torus' ) {
+
+			const torus = new Mesh( new TorusKnotGeometry( .7, .1, 100, 16 ) );
+			scene.add( torus );
+
+		}
+
+		sceneDict[ name ] = scene;
+
+	}
+
+	return scene;
+
+};
+
+export class PreviewEditor extends BaseNode {
+
+	constructor() {
+
+		const width = 300;
+		const height = 300;
+
+		super( 'Preview', 0, null, height );
+
+		const material = new MeshBasicNodeMaterial();
+		material.colorNode = nullValue;
+		material.side = DoubleSide;
+		material.transparent = true;
+
+		const previewElement = new Element();
+		previewElement.dom.style[ 'padding-top' ] = 0;
+		previewElement.dom.style[ 'padding-bottom' ] = 0;
+
+		const sceneInput = new SelectInput( [
+			{ name: 'Box', value: 'box' },
+			{ name: 'Sphere', value: 'sphere' },
+			{ name: 'Plane', value: 'plane' },
+			{ name: 'Sprite', value: 'sprite' },
+			{ name: 'Torus', value: 'torus' }
+		], 'box' );
+
+		const inputElement = new LabelElement( 'Input' ).setInput( 4 ).onConnect( () => {
+
+			material.colorNode = inputElement.getLinkedObject() || nullValue;
+			material.dispose();
+
+		}, true );
+
+		const canvas = document.createElement( 'canvas' );
+		canvas.style.position = 'absolute';
+		previewElement.dom.append( canvas );
+		previewElement.setHeight( height );
+
+		const renderer = new WebGLRenderer( {
+			canvas,
+			alpha: true
+		} );
+
+		renderer.autoClear = false;
+		renderer.setSize( width, height, true );
+		renderer.setPixelRatio( window.devicePixelRatio );
+
+		const camera = new PerspectiveCamera( 45, width / height, 0.1, 100 );
+		camera.aspect = width / height;
+		camera.updateProjectionMatrix();
+		camera.position.set( - 2, 2, 2 );
+		camera.lookAt( 0, 0, 0 );
+
+		const controls = new OrbitControls( camera, previewElement.dom );
+		controls.enableKeys = false;
+		controls.update();
+
+		const viewHelper = new ViewHelper( camera, previewElement.dom );
+
+		this.sceneInput = sceneInput;
+		this.viewHelper = viewHelper;
+		this.material = material;
+		this.camera = camera;
+		this.renderer = renderer;
+
+		this.add( inputElement )
+			.add( new LabelElement( 'Object' ).add( sceneInput ) )
+			.add( previewElement );
+
+	}
+
+	setEditor( editor ) {
+
+		super.setEditor( editor );
+
+		this.updateAnimationRequest();
+
+	}
+
+	updateAnimationRequest() {
+
+		if ( this.editor !== null ) {
+
+			requestAnimationFrame( () => this.update() );
+
+		}
+
+	}
+
+	update() {
+
+		const { viewHelper, material, renderer, camera, sceneInput } = this;
+
+		this.updateAnimationRequest();
+
+		const sceneName = sceneInput.getValue();
+
+		const scene = getScene( sceneName );
+		const mesh = scene.children[ 0 ];
+
+		mesh.material = material;
+
+		if ( sceneName === 'sprite' ) {
+
+			mesh.lookAt( camera.position );
+
+		}
+
+		renderer.clear();
+		renderer.render( scene, camera );
+
+		viewHelper.render( renderer );
+
+	}
+
+}

+ 3 - 3
examples/jsm/renderers/nodes/math/MathNode.js

@@ -164,15 +164,15 @@ class MathNode extends TempNode {
 
 		} else if ( method === MathNode.SATURATE ) {
 
-			return `clamp( ${ a.build( builder, inputType ) }, 0.0, 1.0 )`;
+			return builder.format( `clamp( ${ a.build( builder, inputType ) }, 0.0, 1.0 )`, type, output );
 
 		} else if ( method === MathNode.NEGATE ) {
 
-			return '( -' + a.build( builder, inputType ) + ' )';
+			return builder.format( '( -' + a.build( builder, inputType ) + ' )', type, output );
 
 		} else if ( method === MathNode.INVERT ) {
 
-			return '( 1.0 - ' + a.build( builder, inputType ) + ' )';
+			return builder.format( '( 1.0 - ' + a.build( builder, inputType ) + ' )', type, output );
 
 		} else {
 

+ 4 - 4
examples/jsm/renderers/nodes/math/OperatorNode.js

@@ -149,21 +149,21 @@ class OperatorNode extends TempNode {
 
 			} else if ( op === '>' && outputLength > 1 ) {
 
-				return `${ builder.getMethod( 'greaterThan' ) }( ${a}, ${b} )`;
+				return builder.format( `${ builder.getMethod( 'greaterThan' ) }( ${a}, ${b} )`, type, output );
 
 			} else if ( op === '<=' && outputLength > 1 ) {
 
-				return `${ builder.getMethod( 'lessThanEqual' ) }( ${a}, ${b} )`;
+				return builder.format( `${ builder.getMethod( 'lessThanEqual' ) }( ${a}, ${b} )`, type, output );
 
 			} else {
 
-				return `( ${a} ${this.op} ${b} )`;
+				return builder.format( `( ${a} ${this.op} ${b} )`, type, output );
 
 			}
 
 		} else if ( typeA !== 'void' ) {
 
-			return `${a} ${this.op} ${b}`;
+			return builder.format( `${a} ${this.op} ${b}`, type, output );
 
 		}
 

+ 2 - 1
examples/webgl_nodes_playground.html

@@ -42,7 +42,8 @@
 	<script type="importmap">
 	{
 		"imports": {
-			"three": "../build/three.module.js"
+			"three": "../build/three.module.js",
+			"three/addons/": "./jsm/"
 		}
 	}
 	</script>