浏览代码

NodeMaterial: Playground r2 (#22868)

* update lib -> many improvements

* add examples

* Add OscNode: Sine, Square, Triangle, Sawtooth

* fix SplitNode if input is a float

* Add, PI2, PI_HALF, RECIPROCAL_PI2, abs, fract, round, sin, cos nodes

* Add OscNode to lib

* node-editor r2

* fix circular reference and mobile context menu

* update lib

* update lib

* update lib (2)
sunag 3 年之前
父节点
当前提交
14a49c2829
共有 32 个文件被更改,包括 949 次插入116 次删除
  1. 0 0
      examples/jsm/libs/flow.module.js
  2. 248 39
      examples/jsm/node-editor/NodeEditor.js
  3. 2 3
      examples/jsm/node-editor/accessors/NormalEditor.js
  4. 2 3
      examples/jsm/node-editor/accessors/PositionEditor.js
  5. 2 3
      examples/jsm/node-editor/accessors/UVEditor.js
  6. 0 34
      examples/jsm/node-editor/core/ObjectNode.js
  7. 43 0
      examples/jsm/node-editor/display/BlendEditor.js
  8. 0 0
      examples/jsm/node-editor/examples/animate-uv.json
  9. 1 0
      examples/jsm/node-editor/examples/fake-top-light.json
  10. 0 0
      examples/jsm/node-editor/examples/oscillator-color.json
  11. 0 0
      examples/jsm/node-editor/examples/rim.json
  12. 70 6
      examples/jsm/node-editor/inputs/ColorEditor.js
  13. 6 7
      examples/jsm/node-editor/inputs/FloatEditor.js
  14. 68 0
      examples/jsm/node-editor/inputs/SliderEditor.js
  15. 28 0
      examples/jsm/node-editor/inputs/Vector2Editor.js
  16. 30 0
      examples/jsm/node-editor/inputs/Vector3Editor.js
  17. 37 0
      examples/jsm/node-editor/inputs/Vector4Editor.js
  18. 1 2
      examples/jsm/node-editor/materials/StandardMaterialEditor.js
  19. 36 0
      examples/jsm/node-editor/math/DotEditor.js
  20. 38 0
      examples/jsm/node-editor/math/InvertEditor.js
  21. 48 0
      examples/jsm/node-editor/math/LimiterEditor.js
  22. 26 0
      examples/jsm/node-editor/math/NormalizeEditor.js
  23. 2 5
      examples/jsm/node-editor/math/OperatorEditor.js
  24. 34 0
      examples/jsm/node-editor/math/PowerEditor.js
  25. 39 0
      examples/jsm/node-editor/math/TrigonometryEditor.js
  26. 3 6
      examples/jsm/node-editor/procedural/CheckerEditor.js
  27. 42 0
      examples/jsm/node-editor/utils/OscillatorEditor.js
  28. 57 0
      examples/jsm/node-editor/utils/TimerEditor.js
  29. 2 0
      examples/jsm/renderers/nodes/Nodes.js
  30. 8 0
      examples/jsm/renderers/nodes/ShaderNode.js
  31. 58 0
      examples/jsm/renderers/nodes/utils/OscNode.js
  32. 18 8
      examples/jsm/renderers/nodes/utils/SplitNode.js

文件差异内容过多而无法显示
+ 0 - 0
examples/jsm/libs/flow.module.js


+ 248 - 39
examples/jsm/node-editor/NodeEditor.js

@@ -1,22 +1,48 @@
 import { Canvas, CircleMenu, ButtonInput, ContextMenu, Loader } from '../libs/flow.module.js';
 import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
 import { OperatorEditor } from './math/OperatorEditor.js';
+import { NormalizeEditor } from './math/NormalizeEditor.js';
+import { InvertEditor } from './math/InvertEditor.js';
+import { LimiterEditor } from './math/LimiterEditor.js';
+import { DotEditor } from './math/DotEditor.js';
+import { PowerEditor } from './math/PowerEditor.js';
+import { TrigonometryEditor } from './math/TrigonometryEditor.js';
 import { FloatEditor } from './inputs/FloatEditor.js';
+import { Vector2Editor } from './inputs/Vector2Editor.js';
+import { Vector3Editor } from './inputs/Vector3Editor.js';
+import { Vector4Editor } from './inputs/Vector4Editor.js';
+import { SliderEditor } from './inputs/SliderEditor.js';
 import { ColorEditor } from './inputs/ColorEditor.js';
+import { BlendEditor } from './display/BlendEditor.js';
 import { UVEditor } from './accessors/UVEditor.js';
 import { PositionEditor } from './accessors/PositionEditor.js';
 import { NormalEditor } from './accessors/NormalEditor.js';
+import { TimerEditor } from './utils/TimerEditor.js';
+import { OscillatorEditor } from './utils/OscillatorEditor.js';
 import { CheckerEditor } from './procedural/CheckerEditor.js';
 import { EventDispatcher } from 'three';
 
 export const ClassLib = {
 	'StandardMaterialEditor': StandardMaterialEditor,
 	'OperatorEditor': OperatorEditor,
+	'NormalizeEditor': NormalizeEditor,
+	'InvertEditor': InvertEditor,
+	'LimiterEditor': LimiterEditor,
+	'DotEditor': DotEditor,
+	'PowerEditor': PowerEditor,
+	'TrigonometryEditor': TrigonometryEditor,
 	'FloatEditor': FloatEditor,
+	'Vector2Editor': Vector2Editor,
+	'Vector3Editor': Vector3Editor,
+	'Vector4Editor': Vector4Editor,
+	'SliderEditor': SliderEditor,
 	'ColorEditor': ColorEditor,
+	'BlendEditor': BlendEditor,
 	'UVEditor': UVEditor,
 	'PositionEditor': PositionEditor,
 	'NormalEditor': NormalEditor,
+	'TimerEditor': TimerEditor,
+	'OscillatorEditor': OscillatorEditor,
 	'CheckerEditor': CheckerEditor
 };
 
@@ -34,8 +60,12 @@ export class NodeEditor extends EventDispatcher {
 		this.canvas = canvas;
 		this.domElement = domElement;
 
+		this.nodesContext = null;
+		this.examplesContext = null;
+
 		this._initMenu();
-		this._initContextMenu();
+		this._initNodesContext();
+		this._initExamplesContext();
 
 	}
 
@@ -53,32 +83,64 @@ export class NodeEditor extends EventDispatcher {
 
 	}
 
+	newProject() {
+
+		this.canvas.clear();
+
+		this.dispatchEvent( { type: 'new' } );
+
+	}
+
+	loadJSON( json ) {
+
+		this.canvas.clear();
+
+		this.canvas.deserialize( json );
+
+		this.dispatchEvent( { type: 'load' } );
+
+	}
+
 	_initMenu() {
 
 		const menu = new CircleMenu();
 
 		const menuButton = new ButtonInput().setIcon( 'ti ti-menu-2' );
+		const examplesButton = new ButtonInput().setIcon( 'ti ti-file-symlink' ).setToolTip( 'Examples' );
 		const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' );
 		const openButton = new ButtonInput().setIcon( 'ti ti-upload' ).setToolTip( 'Open' );
 		const saveButton = new ButtonInput().setIcon( 'ti ti-download' ).setToolTip( 'Save' );
 
+		const hideContext = () => {
+
+			this.examplesContext.hide();
+			this.nodesContext.hide();
+
+		};
+
 		menuButton.onClick( () => {
 
-			this.context.show( 50, 50 );
+			this.nodesContext.show( 60, 50 );
+
+		} );
+
+		examplesButton.onClick( () => {
+
+			this.examplesContext.show( 60, 175 );
 
 		} );
 
 		newButton.onClick( () => {
 
-			this.canvas.clear();
+			hideContext();
 
-			this.dispatchEvent( { type: 'new' } );
+			this.newProject();
 
 		} );
 
 		openButton.onClick( () => {
 
-			this.context.hide();
+			hideContext();
 
 			const input = document.createElement( 'input' );
 			input.type = 'file';
@@ -92,13 +154,10 @@ export class NodeEditor extends EventDispatcher {
 
 				reader.onload = readerEvent => {
 
-					const json = Loader.parseObjects( JSON.parse( readerEvent.target.result ), ClassLib );
-
-					this.canvas.clear();
+					const loader = new Loader( Loader.OBJECTS );
+					const json = loader.parse( JSON.parse( readerEvent.target.result ), ClassLib );
 
-					this.canvas.deserialize( json );
-
-					this.dispatchEvent( { type: 'load' } );
+					this.loadJSON( json );
 
 				};
 
@@ -110,7 +169,7 @@ export class NodeEditor extends EventDispatcher {
 
 		saveButton.onClick( () => {
 
-			this.context.hide();
+			hideContext();
 
 			const json = JSON.stringify( this.canvas.toJSON() );
 
@@ -123,10 +182,11 @@ export class NodeEditor extends EventDispatcher {
 
 		} );
 
-		menu.add( menuButton );
-		menu.add( newButton );
-		menu.add( openButton );
-		menu.add( saveButton );
+		menu.add( menuButton )
+			.add( newButton )
+			.add( examplesButton )
+			.add( openButton )
+			.add( saveButton );
 
 		this.domElement.appendChild( menu.dom );
 
@@ -134,19 +194,95 @@ export class NodeEditor extends EventDispatcher {
 
 	}
 
-	_initContextMenu() {
+	_initExamplesContext() {
+
+		const context = new ContextMenu();
+
+		//**************//
+		// MAIN
+		//**************//
+
+		const onClickExample = async ( button ) => {
+
+			this.examplesContext.hide();
+
+			const filename = button.getExtra();
+
+			const loader = new Loader( Loader.OBJECTS );
+			const json = await loader.load( `./jsm/node-editor/examples/${filename}.json`, ClassLib );
+
+			this.loadJSON( json );
+
+		};
+
+		const addExample = ( context, name, filename = null ) => {
+
+			filename = filename || name.replaceAll( ' ', '-' ).toLowerCase();
+
+			context.add( new ButtonInput( name )
+				.setIcon( 'ti ti-file-symlink' )
+				.onClick( onClickExample )
+				.setExtra( filename )
+			);
+
+		};
+
+		//**************//
+		// EXAMPLES
+		//**************//
+
+		const basicContext = new ContextMenu();
+		const advancedContext = new ContextMenu();
+
+		addExample( basicContext, 'Animate UV' );
+		addExample( basicContext, 'Fake top light' );
+		addExample( basicContext, 'Oscillator color' );
+
+		addExample( advancedContext, 'Rim' );
+
+		//**************//
+		// MAIN
+		//**************//
+
+		context.add( new ButtonInput( 'Basic' ), basicContext );
+		context.add( new ButtonInput( 'Advanced' ), advancedContext );
+
+		this.domElement.appendChild( context.dom );
+
+		this.examplesContext = context;
+
+	}
+
+	_initNodesContext() {
 
 		const context = new ContextMenu( this.domElement );
 
+		let isContext = false;
+		let contextPosition = {};
+
 		const add = ( node ) => {
 
 			const canvas = this.canvas;
 			const canvasRect = canvas.rect;
 
-			node.setPosition(
-				( canvas.relativeX + ( canvasRect.width / 2 ) ) - ( 350 / 2 ),
-				( canvas.relativeY + ( canvasRect.height / 2 ) ) - 20
-			);
+			if ( isContext ) {
+
+				node.setPosition(
+					contextPosition.x,
+					contextPosition.y
+				);
+
+			} else {
+
+				const defaultOffsetX = 350 / 2;
+				const defaultOffsetY = 20;
+
+				node.setPosition(
+					( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
+					( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
+				);
+
+			}
 
 			context.hide();
 
@@ -154,50 +290,95 @@ export class NodeEditor extends EventDispatcher {
 
 			this.canvas.select( node );
 
+			isContext = false;
+
 		};
 
+		context.onContext( () => {
+
+			isContext = true;
+
+			const { relativeClientX, relativeClientY } = this.canvas;
+
+			contextPosition.x = relativeClientX;
+			contextPosition.y = relativeClientY;
+
+		} );
+
 		//**************//
-		//* INPUTS
+		// INPUTS
 		//**************//
 
 		const inputsContext = new ContextMenu();
 
+		const sliderInput = new ButtonInput( 'Slider' ).setIcon( 'ti ti-adjustments-horizontal' )
+			.onClick( () => add( new SliderEditor() ) );
+
 		const floatInput = new ButtonInput( 'Float' ).setIcon( 'ti ti-box-multiple-1' )
 			.onClick( () => add( new FloatEditor() ) );
 
-		//const vec2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' );
-		//const vec3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' );
-		//const vec4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' );
+		const vector2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' )
+			.onClick( () => add( new Vector2Editor() ) );
+
+		const vector3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' )
+			.onClick( () => add( new Vector3Editor() ) );
+
+		const vector4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' )
+			.onClick( () => add( new Vector4Editor() ) );
 
 		const colorInput = new ButtonInput( 'Color' ).setIcon( 'ti ti-palette' )
 			.onClick( () => add( new ColorEditor() ) );
 
 		//const mapInput = new ButtonInput( 'Map' ).setIcon( 'ti ti-photo' );
 		//const cubeMapInput = new ButtonInput( 'Cube Map' ).setIcon( 'ti ti-box' );
-		//const sliderInput = new ButtonInput( 'Slider' ).setIcon( 'ti ti-adjustments-horizontal' );
 		//const integerInput = new ButtonInput( 'Integer' ).setIcon( 'ti ti-list-numbers' );
 
 		inputsContext
+			.add( sliderInput )
 			.add( floatInput )
-			//.add( vec2Input )
-			//.add( vec3Input )
-			//.add( vec4Input )
+			.add( vector2Input )
+			.add( vector3Input )
+			.add( vector4Input )
 			.add( colorInput );
-		//.add( sliderInput );
 
 		//**************//
-		//* MATH
+		// MATH
 		//**************//
 
 		const mathContext = new ContextMenu();
-		const operatorsNode = new ButtonInput( 'Operators' ).setIcon( 'ti ti-math-symbols' )
+
+		const operatorsNode = new ButtonInput( 'Operator' ).setIcon( 'ti ti-math-symbols' )
 			.onClick( () => add( new OperatorEditor() ) );
 
+		const normalizeNode = new ButtonInput( 'Normalize' ).setIcon( 'ti ti-fold' )
+			.onClick( () => add( new NormalizeEditor() ) );
+
+		const invertNode = new ButtonInput( 'Invert' ).setToolTip( 'Negate' ).setIcon( 'ti ti-flip-vertical' )
+			.onClick( () => add( new InvertEditor() ) );
+
+		const limiterNode = new ButtonInput( 'Limiter' ).setToolTip( 'Min / Max' ).setIcon( 'ti ti-arrow-bar-to-up' )
+			.onClick( () => add( new LimiterEditor() ) );
+
+		const dotNode = new ButtonInput( 'Dot Product' ).setIcon( 'ti ti-arrows-up-left' )
+			.onClick( () => add( new DotEditor() ) );
+
+		const powNode = new ButtonInput( 'Power' ).setIcon( 'ti ti-arrow-up-right' )
+			.onClick( () => add( new PowerEditor() ) );
+
+		const triNode = new ButtonInput( 'Trigonometry' ).setToolTip( 'Sin / Cos / Tan' ).setIcon( 'ti ti-wave-sine' )
+			.onClick( () => add( new TrigonometryEditor() ) );
+
 		mathContext
-			.add( operatorsNode );
+			.add( operatorsNode )
+			.add( invertNode )
+			.add( limiterNode )
+			.add( dotNode )
+			.add( powNode )
+			.add( triNode )
+			.add( normalizeNode );
 
 		//**************//
-		//* ACCESSORS
+		// ACCESSORS
 		//**************//
 
 		const accessorsContext = new ContextMenu();
@@ -217,7 +398,7 @@ export class NodeEditor extends EventDispatcher {
 			.add( normalNode );
 
 		//**************//
-		//* PROCEDURAL
+		// PROCEDURAL
 		//**************//
 
 		const proceduralContext = new ContextMenu();
@@ -229,17 +410,45 @@ export class NodeEditor extends EventDispatcher {
 			.add( checkerNode );
 
 		//**************//
-		//* MAIN
+		// DISPLAY
+		//**************//
+
+		const displayContext = new ContextMenu();
+
+		const blendNode = new ButtonInput( 'Blend' ).setIcon( 'ti ti-layers-subtract' )
+			.onClick( () => add( new BlendEditor() ) );
+
+		displayContext
+			.add( blendNode );
+
+		//**************//
+		// UTILS
+		//**************//
+
+		const utilsContext = new ContextMenu();
+
+		const timerNode = new ButtonInput( 'Timer' ).setIcon( 'ti ti-clock' )
+			.onClick( () => add( new TimerEditor() ) );
+
+		const oscNode = new ButtonInput( 'Oscillator' ).setIcon( 'ti ti-wave-sine' )
+			.onClick( () => add( new OscillatorEditor() ) );
+
+		utilsContext
+			.add( timerNode )
+			.add( oscNode );
+
+		//**************//
+		// MAIN
 		//**************//
 
 		context.add( new ButtonInput( 'Inputs' ).setIcon( 'ti ti-forms' ), inputsContext );
 		context.add( new ButtonInput( 'Accessors' ).setIcon( 'ti ti-vector-triangle' ), accessorsContext );
+		context.add( new ButtonInput( 'Display' ).setIcon( 'ti ti-brightness' ), displayContext );
 		context.add( new ButtonInput( 'Math' ).setIcon( 'ti ti-calculator' ), mathContext );
 		context.add( new ButtonInput( 'Procedural' ).setIcon( 'ti ti-infinity' ), proceduralContext );
+		context.add( new ButtonInput( 'Utils' ).setIcon( 'ti ti-apps' ), utilsContext );
 
-		this.domElement.appendChild( context.dom );
-
-		this.context = context;
+		this.nodesContext = context;
 
 	}
 

+ 2 - 3
examples/jsm/node-editor/accessors/NormalEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
 import { NormalNode } from '../../renderers/nodes/Nodes.js';
 
 export class NormalEditor extends ObjectNode {
@@ -8,7 +7,7 @@ export class NormalEditor extends ObjectNode {
 
 		const node = new NormalNode();
 
-		super( 'Normal', 3, node );
+		super( 'Normal', 3, node, 250 );
 
 		this.title.setStyle( 'red' );
 

+ 2 - 3
examples/jsm/node-editor/accessors/PositionEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
 import { PositionNode } from '../../renderers/nodes/Nodes.js';
 
 export class PositionEditor extends ObjectNode {
@@ -8,7 +7,7 @@ export class PositionEditor extends ObjectNode {
 
 		const node = new PositionNode();
 
-		super( 'Position', 3, node );
+		super( 'Position', 3, node, 250 );
 
 		this.title.setStyle( 'red' );
 

+ 2 - 3
examples/jsm/node-editor/accessors/UVEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
 import { UVNode } from '../../renderers/nodes/Nodes.js';
 
 export class UVEditor extends ObjectNode {
@@ -8,7 +7,7 @@ export class UVEditor extends ObjectNode {
 
 		const node = new UVNode();
 
-		super( 'UV', 2, node );
+		super( 'UV', 2, node, 250 );
 
 		this.title.setStyle( 'red' );
 

+ 0 - 34
examples/jsm/node-editor/core/ObjectNode.js

@@ -1,34 +0,0 @@
-import { Node, TitleElement, ButtonInput } from '../../libs/flow.module.js';
-
-export class ObjectNode extends Node {
-
-	constructor( name, inputLength, extra = null ) {
-
-		super();
-
-		const title = new TitleElement( name )
-			.setExtra( extra )
-			.setOutput( inputLength );
-
-		const closeButton = new ButtonInput( '✖' ).onClick( () => {
-
-			this.dispose();
-
-		} );
-
-		title.addButton( closeButton );
-
-		this.add( title );
-
-		this.title = title;
-		this.closeButton = closeButton;
-
-	}
-
-	invalidate() {
-
-		this.title.dispatchEvent( new Event( 'connect' ) );
-
-	}
-
-}

+ 43 - 0
examples/jsm/node-editor/display/BlendEditor.js

@@ -0,0 +1,43 @@
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+const ONE_VALUE = new FloatNode( 1 );
+
+export class BlendEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.MIX, NULL_VALUE, NULL_VALUE, ONE_VALUE );
+
+		super( 'Blend', 3, node );
+
+		const aElement = new LabelElement( 'Base' ).setInput( 3 );
+		const bElement = new LabelElement( 'Blend' ).setInput( 3 );
+		const cElement = new LabelElement( 'Opacity' ).setInput( 1 );
+
+		aElement.onConnect( () => {
+
+			node.aNode = aElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		bElement.onConnect( () => {
+
+			node.bNode = bElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		cElement.onConnect( () => {
+
+			node.cNode = cElement.linkedExtra || ONE_VALUE;
+
+		} );
+
+		this.add( aElement )
+			.add( bElement )
+			.add( cElement );
+
+	}
+
+}

文件差异内容过多而无法显示
+ 0 - 0
examples/jsm/node-editor/examples/animate-uv.json


+ 1 - 0
examples/jsm/node-editor/examples/fake-top-light.json

@@ -0,0 +1 @@
+{"objects":{"575":{"x":885,"y":119,"width":"300px","elements":[576,578,579,580,581],"id":575,"type":"StandardMaterialEditor"},"576":{"style":"blue","title":"Standard Material","id":576,"type":"TitleElement"},"578":{"inputLength":3,"links":[595],"label":"Color","id":578,"type":"LabelElement"},"579":{"inputLength":1,"label":"Opacity","id":579,"type":"LabelElement"},"580":{"inputLength":1,"label":"Metalness","id":580,"type":"LabelElement"},"581":{"inputLength":1,"label":"Roughness","id":581,"type":"LabelElement"},"587":{"x":73,"y":296,"width":"","elements":[588,593],"id":587,"type":"Vector3Editor"},"588":{"outputLength":3,"title":"Vector 3","icon":"ti ti-box-multiple-3","id":588,"type":"TitleElement"},"590":{"value":0,"id":590,"type":"NumberInput"},"591":{"value":1,"id":591,"type":"NumberInput"},"592":{"value":0,"id":592,"type":"NumberInput"},"593":{"inputs":[590,591,592],"label":"Values","id":593,"type":"LabelElement"},"594":{"x":541,"y":199,"width":"200px","elements":[595,597,598],"id":594,"type":"DotEditor"},"595":{"outputLength":1,"title":"Dot Product","id":595,"type":"TitleElement"},"597":{"inputLength":3,"links":[600],"label":"A","id":597,"type":"LabelElement"},"598":{"inputLength":3,"links":[588],"label":"B","id":598,"type":"LabelElement"},"599":{"x":106,"y":158,"width":"250px","elements":[600,603],"id":599,"type":"NormalEditor"},"600":{"outputLength":3,"style":"red","title":"Normal","id":600,"type":"TitleElement"},"602":{"options":[{"name":"Local","value":"local"},{"name":"World","value":"world"},{"name":"View","value":"view"}],"value":"world","id":602,"type":"SelectInput"},"603":{"inputs":[602],"label":"Scope","id":603,"type":"LabelElement"}},"nodes":[575,587,594,599],"id":0,"type":"Canvas"}

文件差异内容过多而无法显示
+ 0 - 0
examples/jsm/node-editor/examples/oscillator-color.json


文件差异内容过多而无法显示
+ 0 - 0
examples/jsm/node-editor/examples/rim.json


+ 70 - 6
examples/jsm/node-editor/inputs/ColorEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { ColorInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, ColorInput, StringInput, NumberInput, LabelElement } from '../../libs/flow.module.js';
 import { ColorNode } from '../../renderers/nodes/Nodes.js';
 
 export class ColorEditor extends ObjectNode {
@@ -12,15 +11,80 @@ export class ColorEditor extends ObjectNode {
 
 		this.title.setIcon( 'ti ti-palette' );
 
-		const colorField = new ColorInput( 0xFFFFFF ).onChange( ( input ) => {
+		const updateFields = ( editing ) => {
 
-			const hex = parseInt( input.getValue() );
+			const value = node.value;
+			const hexValue = value.getHex();
 
-			node.value.setHex( hex );
+			if ( editing !== 'color' ) {
+
+				field.setValue( hexValue, false );
+
+			}
+
+			if ( editing !== 'hex' ) {
+
+				hexField.setValue( '#' + hexValue.toString( 16 ).toUpperCase().padEnd( 6, '0' ), false );
+
+			}
+
+			if ( editing !== 'rgb' ) {
+
+				fieldR.setValue( value.r.toFixed( 3 ), false );
+				fieldG.setValue( value.g.toFixed( 3 ), false );
+				fieldB.setValue( value.b.toFixed( 3 ), false );
+
+			}
+
+		};
+
+		const field = new ColorInput( 0xFFFFFF ).onChange( () => {
+
+			node.value.setHex( field.getValue() );
+
+			updateFields( 'picker' );
 
 		} );
 
-		this.add( new LabelElement( 'Value' ).add( colorField ) );
+		const hexField = new StringInput().onChange( () => {
+
+			const value = hexField.getValue();
+
+			if ( value.indexOf( '#' ) === 0 ) {
+
+				const hexStr = value.substr( 1 ).padEnd( 6, '0' );
+
+				node.value.setHex( parseInt( hexStr, 16 ) );
+
+				updateFields( 'hex' );
+
+			}
+
+		} );
+
+		hexField.addEventListener( 'blur', () => {
+
+			updateFields();
+
+		} );
+
+		const onChangeRGB = () => {
+
+			node.value.setRGB( fieldR.getValue(), fieldG.getValue(), fieldB.getValue() );
+
+			updateFields( 'rgb' );
+
+		};
+
+		const fieldR = new NumberInput( 1, 0, 1 ).onChange( onChangeRGB );
+		const fieldG = new NumberInput( 1, 0, 1 ).onChange( onChangeRGB );
+		const fieldB = new NumberInput( 1, 0, 1 ).onChange( onChangeRGB );
+
+		this.add( new LabelElement( 'Value' ).add( field ).setSerializable( false ) )
+			.add( new LabelElement( 'Hex' ).add( hexField ).setSerializable( false ) )
+			.add( new LabelElement( 'RGB' ).add( fieldR ).add( fieldG ).add( fieldB ) );
+
+		updateFields();
 
 	}
 

+ 6 - 7
examples/jsm/node-editor/inputs/FloatEditor.js

@@ -1,24 +1,23 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
 import { FloatNode } from '../../renderers/nodes/Nodes.js';
 
 export class FloatEditor extends ObjectNode {
 
 	constructor() {
 
-		const node = new FloatNode( 0 );
+		const node = new FloatNode();
 
-		super( 'Float', 1, node );
+		super( 'Float', 1, node, 250 );
 
 		this.title.setIcon( 'ti ti-box-multiple-1' );
 
-		const numberField = new NumberInput().onChange( ( input ) => {
+		const field = new NumberInput().onChange( () => {
 
-			node.value = input.getValue();
+			node.value = field.getValue();
 
 		} );
 
-		this.add( new LabelElement( 'Value' ).add( numberField ) );
+		this.add( new LabelElement( 'Value' ).add( field ) );
 
 	}
 

+ 68 - 0
examples/jsm/node-editor/inputs/SliderEditor.js

@@ -0,0 +1,68 @@
+import { ObjectNode, ButtonInput, SliderInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { FloatNode } from '../../renderers/nodes/Nodes.js';
+
+export class SliderEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new FloatNode();
+
+		super( 'Slider', 1, node );
+
+		this.title.setIcon( 'ti ti-adjustments-horizontal' );
+
+		this.collapse = true;
+
+		const field = new SliderInput( 0, 0, 1 ).onChange( () => {
+
+			node.value = field.getValue();
+
+		} );
+
+		const updateRange = () => {
+
+			const min = minInput.getValue();
+			const max = maxInput.getValue();
+
+			if ( min <= max ) {
+
+				field.setRange( min, max );
+
+			} else {
+
+				maxInput.setValue( min );
+
+			}
+
+		};
+
+		const minInput = new NumberInput().onChange( updateRange );
+		const maxInput = new NumberInput( 1 ).onChange( updateRange );
+
+		const minElement = new LabelElement( 'Min.' ).add( minInput ).setVisible( false );
+		const maxElement = new LabelElement( 'Max.' ).add( maxInput ).setVisible( false );
+
+		const moreElement = new Element().add( new ButtonInput( 'More' ).onClick( () => {
+
+			minElement.setVisible( true );
+			maxElement.setVisible( true );
+			moreElement.setVisible( false );
+
+		} ) ).setSerializable( false );
+
+		this.add( new LabelElement( 'Value' ).add( field ) )
+			.add( minElement )
+			.add( maxElement )
+			.add( moreElement );
+
+		this.onBlur( () => {
+
+			minElement.setVisible( false );
+			maxElement.setVisible( false );
+			moreElement.setVisible( true );
+
+		} );
+
+	}
+
+}

+ 28 - 0
examples/jsm/node-editor/inputs/Vector2Editor.js

@@ -0,0 +1,28 @@
+import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { Vector2Node } from '../../renderers/nodes/Nodes.js';
+
+export class Vector2Editor extends ObjectNode {
+
+	constructor() {
+
+		const node = new Vector2Node();
+
+		super( 'Vector 2', 2, node );
+
+		this.title.setIcon( 'ti ti-box-multiple-2' );
+
+		const onUpdate = () => {
+
+			node.value.x = fieldX.getValue();
+			node.value.y = fieldY.getValue();
+
+		};
+
+		const fieldX = new NumberInput().onChange( onUpdate );
+		const fieldY = new NumberInput().onChange( onUpdate );
+
+		this.add( new LabelElement( 'Values' ).add( fieldX ).add( fieldY ) );
+
+	}
+
+}

+ 30 - 0
examples/jsm/node-editor/inputs/Vector3Editor.js

@@ -0,0 +1,30 @@
+import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { Vector3Node } from '../../renderers/nodes/Nodes.js';
+
+export class Vector3Editor extends ObjectNode {
+
+	constructor() {
+
+		const node = new Vector3Node();
+
+		super( 'Vector 3', 3, node );
+
+		this.title.setIcon( 'ti ti-box-multiple-3' );
+
+		const onUpdate = () => {
+
+			node.value.x = fieldX.getValue();
+			node.value.y = fieldY.getValue();
+			node.value.z = fieldZ.getValue();
+
+		};
+
+		const fieldX = new NumberInput().onChange( onUpdate );
+		const fieldY = new NumberInput().onChange( onUpdate );
+		const fieldZ = new NumberInput().onChange( onUpdate );
+
+		this.add( new LabelElement( 'Values' ).add( fieldX ).add( fieldY ).add( fieldZ ) );
+
+	}
+
+}

+ 37 - 0
examples/jsm/node-editor/inputs/Vector4Editor.js

@@ -0,0 +1,37 @@
+import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { Vector4Node } from '../../renderers/nodes/Nodes.js';
+
+export class Vector4Editor extends ObjectNode {
+
+	constructor() {
+
+		const node = new Vector4Node();
+
+		super( 'Vector 4', 4, node );
+
+		this.title.setIcon( 'ti ti-box-multiple-4' );
+
+		const onUpdate = () => {
+
+			node.value.x = fieldX.getValue();
+			node.value.y = fieldY.getValue();
+			node.value.z = fieldZ.getValue();
+			node.value.w = fieldW.getValue();
+
+		};
+
+		const fieldX = new NumberInput().onChange( onUpdate );
+		const fieldY = new NumberInput().onChange( onUpdate );
+		const fieldZ = new NumberInput().onChange( onUpdate );
+		const fieldW = new NumberInput().onChange( onUpdate );
+
+		this.add( new LabelElement( 'Values' )
+			.add( fieldX )
+			.add( fieldY )
+			.add( fieldZ )
+			.add( fieldW )
+		);
+
+	}
+
+}

+ 1 - 2
examples/jsm/node-editor/materials/StandardMaterialEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
 import { MeshStandardNodeMaterial, ColorNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 import * as THREE from 'three';
 

+ 36 - 0
examples/jsm/node-editor/math/DotEditor.js

@@ -0,0 +1,36 @@
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class DotEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.DOT, NULL_VALUE, NULL_VALUE );
+
+		super( 'Dot Product', 1, node );
+
+		this.setWidth( 200 );
+
+		const aElement = new LabelElement( 'A' ).setInput( 3 );
+		const bElement = new LabelElement( 'B' ).setInput( 3 );
+
+		aElement.onConnect( () => {
+
+			node.aNode = aElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		bElement.onConnect( () => {
+
+			node.bNode = bElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		this.add( aElement )
+			.add( bElement );
+
+	}
+
+}

+ 38 - 0
examples/jsm/node-editor/math/InvertEditor.js

@@ -0,0 +1,38 @@
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const DEFAULT_VALUE = new FloatNode();
+
+export class InvertEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.INVERT, DEFAULT_VALUE );
+
+		super( 'Invert / Negate', 1, node );
+
+		const optionsField = new SelectInput( [
+			{ name: 'Invert ( 1 - Source )', value: MathNode.INVERT },
+			{ name: 'Negate ( - Source )', value: MathNode.NEGATE }
+		] ).onChange( () => {
+
+			node.method = optionsField.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const input = new LabelElement( 'Source' ).setInput( 1 );
+
+		input.onConnect( () => {
+
+			node.aNode = input.linkedExtra || DEFAULT_VALUE;
+
+		} );
+
+		this.add( new LabelElement( 'Method' ).add( optionsField ) )
+			.add( input );
+
+	}
+
+}

+ 48 - 0
examples/jsm/node-editor/math/LimiterEditor.js

@@ -0,0 +1,48 @@
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class LimiterEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.MAX, NULL_VALUE, NULL_VALUE );
+
+		super( 'Limiter', 1, node, 250 );
+
+		const methodInput = new SelectInput( [
+			{ name: 'Max', value: MathNode.MAX },
+			{ name: 'Min', value: MathNode.MIN }
+		] );
+
+		methodInput.onChange( ( data ) => {
+
+			node.method = data.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const aElement = new LabelElement( 'A' ).setInput( 1 );
+		const bElement = new LabelElement( 'B' ).setInput( 1 );
+
+		aElement.onConnect( () => {
+
+			node.aNode = aElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		bElement.onConnect( () => {
+
+			node.bNode = bElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		this.add( new LabelElement( 'Method' ).add( methodInput ) )
+			.add( aElement )
+			.add( bElement );
+
+	}
+
+}

+ 26 - 0
examples/jsm/node-editor/math/NormalizeEditor.js

@@ -0,0 +1,26 @@
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, Vector3Node } from '../../renderers/nodes/Nodes.js';
+
+const DEFAULT_VALUE = new Vector3Node();
+
+export class NormalizeEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.NORMALIZE, DEFAULT_VALUE );
+
+		super( 'Normalize', 3, node, 200 );
+
+		const input = new LabelElement( 'Source' ).setInput( 3 );
+
+		input.onConnect( () => {
+
+			node.aNode = input.linkedExtra || DEFAULT_VALUE;
+
+		} );
+
+		this.add( input );
+
+	}
+
+}

+ 2 - 5
examples/jsm/node-editor/math/OperatorEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
 import { OperatorNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 
 const NULL_VALUE = new FloatNode();
@@ -10,9 +9,7 @@ export class OperatorEditor extends ObjectNode {
 
 		const node = new OperatorNode( '+', NULL_VALUE, NULL_VALUE );
 
-		super( 'Float', 1, node );
-
-		this.title.setStyle( 'green' );
+		super( 'Operator', 1, node, 250 );
 
 		const opInput = new SelectInput( [
 			{ name: '+ Addition', value: '+' },

+ 34 - 0
examples/jsm/node-editor/math/PowerEditor.js

@@ -0,0 +1,34 @@
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class PowerEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.POW, NULL_VALUE, NULL_VALUE );
+
+		super( 'Power', 1, node, 200 );
+
+		const aElement = new LabelElement( 'A' ).setInput( 1 );
+		const bElement = new LabelElement( 'B' ).setInput( 1 );
+
+		aElement.onConnect( () => {
+
+			node.aNode = aElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		bElement.onConnect( () => {
+
+			node.bNode = bElement.linkedExtra || NULL_VALUE;
+
+		} );
+
+		this.add( aElement )
+			.add( bElement );
+
+	}
+
+}

+ 39 - 0
examples/jsm/node-editor/math/TrigonometryEditor.js

@@ -0,0 +1,39 @@
+import { ObjectNode, SelectInput, Element, LabelElement } from '../../libs/flow.module.js';
+import { MathNode, Vector3Node } from '../../renderers/nodes/Nodes.js';
+
+const DEFAULT_VALUE = new Vector3Node();
+
+export class TrigonometryEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new MathNode( MathNode.SIN, DEFAULT_VALUE );
+
+		super( 'Trigonometry', 1, node, 200 );
+
+		const optionsField = new SelectInput( [
+			{ name: 'Sin', value: MathNode.SIN },
+			{ name: 'Cos', value: MathNode.COS },
+			{ name: 'Tan', value: MathNode.TAN }
+		] ).onChange( () => {
+
+			node.method = optionsField.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const input = new LabelElement( 'Source' ).setInput( 1 );
+
+		input.onConnect( () => {
+
+			node.aNode = input.linkedExtra || DEFAULT_VALUE;
+
+		} );
+
+		this.add( new Element().add( optionsField ) )
+			.add( input );
+
+	}
+
+}

+ 3 - 6
examples/jsm/node-editor/procedural/CheckerEditor.js

@@ -1,5 +1,4 @@
-import { ObjectNode } from '../core/ObjectNode.js';
-import { LabelElement } from '../../libs/flow.module.js';
+import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
 import { CheckerNode, UVNode } from '../../renderers/nodes/Nodes.js';
 
 const DEFAULT_UV = new UVNode();
@@ -8,11 +7,9 @@ export class CheckerEditor extends ObjectNode {
 
 	constructor() {
 
-		const node = new CheckerNode();
+		const node = new CheckerNode( DEFAULT_UV );
 
-		super( 'Checker', 1, node );
-
-		this.title.setStyle( 'yellow' );
+		super( 'Checker', 1, node, 200 );
 
 		const field = new LabelElement( 'UV' ).setInput( 2 );
 

+ 42 - 0
examples/jsm/node-editor/utils/OscillatorEditor.js

@@ -0,0 +1,42 @@
+import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { OscNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class OscillatorEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new OscNode( OscNode.SINE, NULL_VALUE );
+
+		super( 'Oscillator', 1, node, 250 );
+
+		const methodInput = new SelectInput( [
+			{ name: 'Sine', value: OscNode.SINE },
+			{ name: 'Square', value: OscNode.SQUARE },
+			{ name: 'Triangle', value: OscNode.TRIANGLE },
+			{ name: 'Sawtooth', value: OscNode.SAWTOOTH }
+		] );
+
+		methodInput.onChange( () => {
+
+			node.method = methodInput.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const timeElement = new LabelElement( 'Time' ).setInput( 1 );
+
+		timeElement.onConnect( () => {
+
+			node.timeNode = timeElement.linkedExtra || NULL_VALUE;
+
+		} );
+		
+		this.add( new LabelElement( 'Method' ).add( methodInput ) )
+			.add( timeElement );
+
+	}
+
+}

+ 57 - 0
examples/jsm/node-editor/utils/TimerEditor.js

@@ -0,0 +1,57 @@
+import { ObjectNode, NumberInput, LabelElement, Element, ButtonInput } from '../../libs/flow.module.js';
+import { TimerNode } from '../../renderers/nodes/Nodes.js';
+
+export class TimerEditor extends ObjectNode {
+
+	constructor() {
+
+		const node = new TimerNode();
+
+		super( 'Timer', 1, node, 250 );
+
+		this.title.setIcon( 'ti ti-clock' );
+
+		const updateField = () => {
+
+			field.setValue( node.value.toFixed( 3 ) );
+
+		};
+
+		const field = new NumberInput().onChange( () => {
+
+			node.value = field.getValue();
+
+		} );
+
+		const scaleField = new NumberInput( 1 ).onChange( () => {
+
+			node.scale = scaleField.getValue();
+
+		} );
+
+		const moreElement = new Element().add( new ButtonInput( 'Reset' ).onClick( () => {
+
+			node.value = 0;
+
+			updateField();
+
+		} ) ).setSerializable( false );
+
+		this.add( new LabelElement( 'Value' ).add( field ).setSerializable( false ) )
+			.add( new LabelElement( 'Scale' ).add( scaleField ) )
+			.add( moreElement );
+
+		// extends node
+
+		node._update = node.update;
+		node.update = function ( ...params ) {
+
+			this._update( ...params );
+
+			updateField();
+
+		};
+
+	}
+
+}

+ 2 - 0
examples/jsm/renderers/nodes/Nodes.js

@@ -66,6 +66,7 @@ import ArrayElementNode from './utils/ArrayElementNode.js';
 import JoinNode from './utils/JoinNode.js';
 import SplitNode from './utils/SplitNode.js';
 import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js';
+import OscNode from './utils/OscNode.js';
 import TimerNode from './utils/TimerNode.js';
 
 // procedural
@@ -152,6 +153,7 @@ export {
 	JoinNode,
 	SplitNode,
 	SpriteSheetUVNode,
+	OscNode,
 	TimerNode,
 
 	// procedural

+ 8 - 0
examples/jsm/renderers/nodes/ShaderNode.js

@@ -278,7 +278,10 @@ export const positionView = new PositionNode( PositionNode.VIEW );
 export const positionViewDirection = new PositionNode( PositionNode.VIEW_DIRECTION );
 
 export const PI = float( 3.141592653589793 );
+export const PI2 = float( 6.283185307179586 );
+export const PI_HALF = float( 1.5707963267948966 );
 export const RECIPROCAL_PI = float( 0.3183098861837907 );
+export const RECIPROCAL_PI2 = float( 0.15915494309189535 );
 export const EPSILON = float( 1e-6 );
 
 export const diffuseColor = new PropertyNode( 'DiffuseColor', 'vec4' );
@@ -287,12 +290,17 @@ export const metalness = new PropertyNode( 'Metalness', 'float' );
 export const alphaTest = new PropertyNode( 'AlphaTest', 'float' );
 export const specularColor = new PropertyNode( 'SpecularColor', 'color' );
 
+export const abs = ShaderNodeProxy( MathNode, 'abs' );
 export const negate = ShaderNodeProxy( MathNode, 'negate' );
 export const floor = ShaderNodeProxy( MathNode, 'floor' );
 export const mod = ShaderNodeProxy( MathNode, 'mod' );
 export const cross = ShaderNodeProxy( MathNode, 'cross' );
+export const fract = ShaderNodeProxy( MathNode, 'fract' );
+export const round = ShaderNodeProxy( MathNode, 'round' );
 export const max = ShaderNodeProxy( MathNode, 'max' );
 export const min = ShaderNodeProxy( MathNode, 'min' );
+export const sin = ShaderNodeProxy( MathNode, 'sin' );
+export const cos = ShaderNodeProxy( MathNode, 'cos' );
 export const dot = ShaderNodeProxy( MathNode, 'dot' );
 export const normalize = ShaderNodeProxy( MathNode, 'normalize' );
 export const sqrt = ShaderNodeProxy( MathNode, 'sqrt' );

+ 58 - 0
examples/jsm/renderers/nodes/utils/OscNode.js

@@ -0,0 +1,58 @@
+import Node from '../core/Node.js';
+import TimerNode from './TimerNode.js';
+import { abs, fract, round, sin, add, sub, mul, PI2 } from '../ShaderNode.js';
+
+class OscNode extends Node {
+
+	static SINE = 'sine';
+	static SQUARE = 'square';
+	static TRIANGLE = 'triangle';
+	static SAWTOOTH = 'sawtooth';
+
+	constructor( method = OscNode.SINE, timeNode = new TimerNode() ) {
+
+		super();
+
+		this.method = method;
+		this.timeNode = timeNode;
+
+	}
+
+	getNodeType( builder ) {
+
+		return this.timeNode.getNodeType( builder );
+
+	}
+
+	generate( builder ) {
+
+		const method = this.method;
+		const timeNode = this.timeNode;
+
+		let outputNode = null;
+
+		if ( method === OscNode.SINE ) {
+
+			outputNode = add( mul( sin( mul( add( timeNode, .75 ), PI2 ) ), .5 ), .5 );
+
+		} else if ( method === OscNode.SQUARE ) {
+
+			outputNode = round( fract( timeNode ) );
+
+		} else if ( method === OscNode.TRIANGLE ) {
+
+			outputNode = abs( sub( 1, mul( fract( add( timeNode, .5 ) ), 2 ) ) );
+
+		} else if ( method === OscNode.SAWTOOTH ) {
+
+			outputNode = fract( timeNode );
+
+		}
+
+		return outputNode.build( builder );
+
+	}
+
+}
+
+export default OscNode;

+ 18 - 8
examples/jsm/renderers/nodes/utils/SplitNode.js

@@ -22,21 +22,31 @@ class SplitNode extends Node {
 		const node = this.node;
 		const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) );
 
-		const components = this.components;
+		if ( nodeTypeLength > 1 ) {
 
-		let type = null;
+			const components = this.components;
 
-		if ( components.length >= nodeTypeLength ) {
+			let type = null;
 
-			// need expand the input node
+			if ( components.length >= nodeTypeLength ) {
 
-			type = this.getNodeType( builder );
+				// need expand the input node
 
-		}
+				type = this.getNodeType( builder );
+
+			}
+
+			const nodeSnippet = node.build( builder, type );
+
+			return `${nodeSnippet}.${this.components}`;
 
-		const nodeSnippet = node.build( builder, type );
+		} else {
 
-		return `${nodeSnippet}.${this.components}`;
+			// ignore components if node is a float
+
+			return node.build( builder );
+
+		}
 
 	}
 

部分文件因为文件数量过多而无法显示