浏览代码

NodeEditor: Node-based for native objects (#23165)

* update lib

* Create BaseNode.js

* NodeEditor r3

* update examples

* fix autoComplete click

* fix output length

* fix duplicated Operator in menus

* semicolon

* Update webgl_materials_nodes_playground.html

* rename example: webgl_materials_nodes_playground -> webgl_nodes_playground

* fix vector length input and add .getVectorLength() method

* add Split and Join NodeEditor
sunag 3 年之前
父节点
当前提交
9ba3581516
共有 39 个文件被更改,包括 1177 次插入544 次删除
  1. 1 1
      examples/files.json
  2. 0 0
      examples/jsm/libs/flow.module.js
  3. 386 186
      examples/jsm/node-editor/NodeEditor.js
  4. 6 7
      examples/jsm/node-editor/accessors/NormalEditor.js
  5. 6 7
      examples/jsm/node-editor/accessors/PositionEditor.js
  6. 5 6
      examples/jsm/node-editor/accessors/UVEditor.js
  7. 83 0
      examples/jsm/node-editor/core/BaseNode.js
  8. 7 6
      examples/jsm/node-editor/display/BlendEditor.js
  9. 0 0
      examples/jsm/node-editor/examples/animate-uv.json
  10. 0 1
      examples/jsm/node-editor/examples/fake-top-light.json
  11. 0 0
      examples/jsm/node-editor/examples/oscillator-color.json
  12. 0 0
      examples/jsm/node-editor/examples/rim.json
  13. 14 10
      examples/jsm/node-editor/inputs/ColorEditor.js
  14. 6 7
      examples/jsm/node-editor/inputs/FloatEditor.js
  15. 4 5
      examples/jsm/node-editor/inputs/SliderEditor.js
  16. 6 7
      examples/jsm/node-editor/inputs/Vector2Editor.js
  17. 8 9
      examples/jsm/node-editor/inputs/Vector3Editor.js
  18. 9 10
      examples/jsm/node-editor/inputs/Vector4Editor.js
  19. 13 14
      examples/jsm/node-editor/materials/StandardMaterialEditor.js
  20. 6 7
      examples/jsm/node-editor/math/DotEditor.js
  21. 4 3
      examples/jsm/node-editor/math/InvertEditor.js
  22. 11 10
      examples/jsm/node-editor/math/LimiterEditor.js
  23. 6 5
      examples/jsm/node-editor/math/NormalizeEditor.js
  24. 12 11
      examples/jsm/node-editor/math/OperatorEditor.js
  25. 6 5
      examples/jsm/node-editor/math/PowerEditor.js
  26. 7 6
      examples/jsm/node-editor/math/TrigonometryEditor.js
  27. 4 3
      examples/jsm/node-editor/procedural/CheckerEditor.js
  28. 208 0
      examples/jsm/node-editor/scene/MeshEditor.js
  29. 58 0
      examples/jsm/node-editor/utils/JoinEditor.js
  30. 7 6
      examples/jsm/node-editor/utils/OscillatorEditor.js
  31. 39 0
      examples/jsm/node-editor/utils/SplitEditor.js
  32. 6 5
      examples/jsm/node-editor/utils/TimerEditor.js
  33. 2 1
      examples/jsm/renderers/nodes/core/NodeBuilder.js
  34. 19 4
      examples/jsm/renderers/nodes/utils/SplitNode.js
  35. 二进制
      examples/screenshots/webgl_materials_nodes_playground.jpg
  36. 二进制
      examples/screenshots/webgl_nodes_playground.jpg
  37. 0 186
      examples/webgl_materials_nodes_playground.html
  38. 198 0
      examples/webgl_nodes_playground.html
  39. 30 16
      examples/webgpu_nodes_playground.html

+ 1 - 1
examples/files.json

@@ -231,8 +231,8 @@
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_instance_uniform_nodes",
 		"webgl_materials_nodes",
-		"webgl_materials_nodes_playground",
 		"webgl_materials_standard_nodes",
+		"webgl_nodes_playground",
 		"webgl_mirror_nodes",
 		"webgl_performance_nodes",
 		"webgl_points_nodes",

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


+ 386 - 186
examples/jsm/node-editor/NodeEditor.js

@@ -1,4 +1,4 @@
-import { Canvas, CircleMenu, ButtonInput, ContextMenu, Loader } from '../libs/flow.module.js';
+import { Styles, Canvas, CircleMenu, ButtonInput, ContextMenu, Tips, Search, Loader } from '../libs/flow.module.js';
 import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
 import { OperatorEditor } from './math/OperatorEditor.js';
 import { NormalizeEditor } from './math/NormalizeEditor.js';
@@ -19,43 +19,227 @@ 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 { SplitEditor } from './utils/SplitEditor.js';
+import { JoinEditor } from './utils/JoinEditor.js';
 import { CheckerEditor } from './procedural/CheckerEditor.js';
+import { MeshEditor } from './scene/MeshEditor.js';
 import { EventDispatcher } from 'three';
 
+Styles.icons.unlink = 'ti ti-unlink';
+
+export const NodeList = [
+	{
+		name: 'Inputs',
+		icon: 'forms',
+		children: [
+			{
+				name: 'Slider',
+				icon: 'adjustments-horizontal',
+				nodeClass: SliderEditor
+			},
+			{
+				name: 'Float',
+				icon: 'box-multiple-1',
+				nodeClass: FloatEditor
+			},
+			{
+				name: 'Vector 2',
+				icon: 'box-multiple-2',
+				nodeClass: Vector2Editor
+			},
+			{
+				name: 'Vector 3',
+				icon: 'box-multiple-3',
+				nodeClass: Vector3Editor
+			},
+			{
+				name: 'Vector 4',
+				icon: 'box-multiple-4',
+				nodeClass: Vector4Editor
+			},
+			{
+				name: 'Color',
+				icon: 'palette',
+				nodeClass: ColorEditor
+			}
+		]
+	},
+	{
+		name: 'Accessors',
+		icon: 'vector-triangle',
+		children: [
+			{
+				name: 'UV',
+				icon: 'details',
+				nodeClass: UVEditor
+			},
+			{
+				name: 'Position',
+				icon: 'hierarchy',
+				nodeClass: PositionEditor
+			},
+			{
+				name: 'Normal',
+				icon: 'fold-up',
+				nodeClass: NormalEditor
+			}
+		]
+	},
+	{
+		name: 'Display',
+		icon: 'brightness',
+		children: [
+			{
+				name: 'Blend',
+				icon: 'layers-subtract',
+				nodeClass: BlendEditor
+			}
+		]
+	},
+	{
+		name: 'Math',
+		icon: 'calculator',
+		children: [
+			{
+				name: 'Operator',
+				icon: 'math-symbols',
+				nodeClass: OperatorEditor
+			},
+			{
+				name: 'Invert',
+				icon: 'flip-vertical',
+				tip: 'Negate',
+				nodeClass: OperatorEditor
+			},
+			{
+				name: 'Limiter',
+				icon: 'arrow-bar-to-up',
+				tip: 'Min / Max',
+				nodeClass: LimiterEditor
+			},
+			{
+				name: 'Dot Product',
+				icon: 'arrows-up-left',
+				nodeClass: DotEditor
+			},
+			{
+				name: 'Power',
+				icon: 'arrow-up-right',
+				nodeClass: PowerEditor
+			},
+			{
+				name: 'Trigonometry',
+				icon: 'wave-sine',
+				tip: 'Sin / Cos / Tan',
+				nodeClass: TrigonometryEditor
+			},
+			{
+				name: 'Normalize',
+				icon: 'fold',
+				nodeClass: OperatorEditor
+			}
+		]
+	},
+	{
+		name: 'Procedural',
+		icon: 'infinity',
+		children: [
+			{
+				name: 'Checker',
+				icon: 'border-outer',
+				nodeClass: CheckerEditor
+			}
+		]
+	},
+	{
+		name: 'Utils',
+		icon: 'apps',
+		children: [
+			{
+				name: 'Timer',
+				icon: 'clock',
+				nodeClass: TimerEditor
+			},
+			{
+				name: 'Oscillator',
+				icon: 'wave-sine',
+				nodeClass: OscillatorEditor
+			},
+			{
+				name: 'Split',
+				icon: 'arrows-split-2',
+				nodeClass: SplitEditor
+			},
+			{
+				name: 'Join',
+				icon: 'arrows-join-2',
+				nodeClass: JoinEditor
+			}
+		]
+	},
+	/*{
+		name: 'Scene',
+		icon: '3d-cube-sphere',
+		children: [
+			{
+				name: 'Mesh',
+				icon: '3d-cube-sphere',
+				nodeClass: MeshEditor
+			}
+		]
+	},*/
+	{
+		name: 'Material',
+		icon: 'circles',
+		children: [
+			{
+				name: 'Standard Material',
+				icon: 'circle',
+				nodeClass: StandardMaterialEditor
+			}
+		]
+	}
+];
+
 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
+	StandardMaterialEditor,
+	MeshEditor,
+	OperatorEditor,
+	NormalizeEditor,
+	InvertEditor,
+	LimiterEditor,
+	DotEditor,
+	PowerEditor,
+	TrigonometryEditor,
+	FloatEditor,
+	Vector2Editor,
+	Vector3Editor,
+	Vector4Editor,
+	SliderEditor,
+	ColorEditor,
+	BlendEditor,
+	UVEditor,
+	PositionEditor,
+	NormalEditor,
+	TimerEditor,
+	OscillatorEditor,
+	SplitEditor,
+	JoinEditor,
+	CheckerEditor
 };
 
 export class NodeEditor extends EventDispatcher {
 
-	constructor() {
+	constructor( scene = null ) {
 
 		super();
 
 		const domElement = document.createElement( 'flow' );
 		const canvas = new Canvas();
 
-		domElement.appendChild( canvas.dom );
+		domElement.append( canvas.dom );
+
+		this.scene = scene;
 
 		this.canvas = canvas;
 		this.domElement = domElement;
@@ -63,16 +247,48 @@ export class NodeEditor extends EventDispatcher {
 		this.nodesContext = null;
 		this.examplesContext = null;
 
+		this._initTips();
 		this._initMenu();
+		this._initSearch();
 		this._initNodesContext();
 		this._initExamplesContext();
 
 	}
 
+	centralizeNode( node ) {
+
+		const canvas = this.canvas;
+		const canvasRect = canvas.rect;
+
+		const nodeRect = node.dom.getBoundingClientRect();
+
+		const defaultOffsetX = nodeRect.width;
+		const defaultOffsetY = nodeRect.height;
+
+		node.setPosition(
+			( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
+			( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
+		);
+
+	}
+
 	add( node ) {
 
+		const onRemove = () => {
+
+			node.removeEventListener( 'remove', onRemove );
+
+			node.setEditor( null );
+
+		};
+
+		node.setEditor( this );
+		node.addEventListener( 'remove', onRemove );
+
 		this.canvas.add( node );
 
+		this.dispatchEvent( { type: 'add', node } );
+
 		return this;
 
 	}
@@ -97,51 +313,41 @@ export class NodeEditor extends EventDispatcher {
 
 		this.canvas.deserialize( json );
 
+		for ( const node of this.canvas.nodes ) {
+
+			this.add( node );
+
+		}
+
 		this.dispatchEvent( { type: 'load' } );
 
 	}
 
+	_initTips() {
+
+		this.tips = new Tips();
+
+		this.domElement.append( this.tips.dom );
+
+	}
+
 	_initMenu() {
 
 		const menu = new CircleMenu();
 
-		const menuButton = new ButtonInput().setIcon( 'ti ti-menu-2' );
+		const menuButton = new ButtonInput().setIcon( 'ti ti-apps' ).setToolTip( 'Add' );
 		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.nodesContext.show( 60, 50 );
-
-		} );
-
-		examplesButton.onClick( () => {
-
-			this.examplesContext.show( 60, 175 );
-
-		} );
-
-		newButton.onClick( () => {
+		menuButton.onClick( () => this.nodesContext.open() );
+		examplesButton.onClick( () => this.examplesContext.open() );
 
-			hideContext();
-
-			this.newProject();
-
-		} );
+		newButton.onClick( () => this.newProject() );
 
 		openButton.onClick( () => {
 
-			hideContext();
-
 			const input = document.createElement( 'input' );
 			input.type = 'file';
 
@@ -169,8 +375,6 @@ export class NodeEditor extends EventDispatcher {
 
 		saveButton.onClick( () => {
 
-			hideContext();
-
 			const json = JSON.stringify( this.canvas.toJSON() );
 
 			const a = document.createElement( 'a' );
@@ -182,13 +386,13 @@ export class NodeEditor extends EventDispatcher {
 
 		} );
 
-		menu.add( menuButton )
+		menu.add( examplesButton )
+			.add( menuButton )
 			.add( newButton )
-			.add( examplesButton )
 			.add( openButton )
 			.add( saveButton );
 
-		this.domElement.appendChild( menu.dom );
+		this.domElement.append( menu.dom );
 
 		this.menu = menu;
 
@@ -247,12 +451,111 @@ export class NodeEditor extends EventDispatcher {
 		context.add( new ButtonInput( 'Basic' ), basicContext );
 		context.add( new ButtonInput( 'Advanced' ), advancedContext );
 
-		this.domElement.appendChild( context.dom );
-
 		this.examplesContext = context;
 
 	}
 
+	_initSearch() {
+
+		const traverseNodeEditors = ( item ) => {
+
+			if ( item.nodeClass ) {
+
+				const button = new ButtonInput( item.name );
+				button.setIcon( `ti ti-${item.icon}` );
+				button.addEventListener( 'complete', () => {
+
+					const node = new item.nodeClass();
+
+					this.add( node );
+
+					this.centralizeNode( node );
+
+				} );
+
+				search.add( button );
+
+			}
+
+			if ( item.children ) {
+
+				for ( const subItem of item.children ) {
+
+					traverseNodeEditors( subItem );
+
+				}
+
+			}
+
+		};
+
+		const search = new Search();
+		search.forceAutoComplete = true;
+
+		search.onFilter( () => {
+
+			search.clear();
+
+			for ( const item of NodeList ) {
+
+				traverseNodeEditors( item );
+
+			}
+
+			const object3d = this.scene;
+
+			object3d.traverse( ( obj3d ) => {
+
+				if ( obj3d.isMesh === true ) {
+
+					const button = new ButtonInput( `Mesh - ${obj3d.name}` );
+					button.setIcon( `ti ti-3d-cube-sphere` );
+					button.addEventListener( 'complete', () => {
+
+						for ( const node of this.canvas.nodes ) {
+
+							if ( node.value === obj3d ) {
+
+								// not duplicated node
+
+								this.canvas.select( node );
+
+								return;
+
+							}
+
+						}
+
+						const node = new MeshEditor( obj3d );
+
+						this.add( node );
+
+						this.centralizeNode( node );
+
+					} );
+
+					search.add( button );
+
+				}
+
+			} );
+
+		} );
+
+		search.onSubmit( () => {
+
+			if ( search.currentFiltered !== null ) {
+
+				search.currentFiltered.button.dispatchEvent( new Event( 'complete' ) );
+
+			}
+
+		} );
+
+		this.domElement.append( search.dom );
+
+	}
+
 	_initNodesContext() {
 
 		const context = new ContextMenu( this.domElement );
@@ -262,9 +565,6 @@ export class NodeEditor extends EventDispatcher {
 
 		const add = ( node ) => {
 
-			const canvas = this.canvas;
-			const canvasRect = canvas.rect;
-
 			if ( isContext ) {
 
 				node.setPosition(
@@ -274,13 +574,7 @@ export class NodeEditor extends EventDispatcher {
 
 			} else {
 
-				const defaultOffsetX = 350 / 2;
-				const defaultOffsetY = 20;
-
-				node.setPosition(
-					( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
-					( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
-				);
+				this.centralizeNode( node );
 
 			}
 
@@ -309,144 +603,50 @@ export class NodeEditor extends EventDispatcher {
 		// 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 vector2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' )
-			.onClick( () => add( new Vector2Editor() ) );
+		const createButtonMenu = ( item ) => {
 
-		const vector3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' )
-			.onClick( () => add( new Vector3Editor() ) );
+			const button = new ButtonInput( item.name );
+			button.setIcon( `ti ti-${item.icon}` );
 
-		const vector4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' )
-			.onClick( () => add( new Vector4Editor() ) );
+			let context = null;
 
-		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 integerInput = new ButtonInput( 'Integer' ).setIcon( 'ti ti-list-numbers' );
-
-		inputsContext
-			.add( sliderInput )
-			.add( floatInput )
-			.add( vector2Input )
-			.add( vector3Input )
-			.add( vector4Input )
-			.add( colorInput );
-
-		//**************//
-		// MATH
-		//**************//
+			if ( item.nodeClass ) {
 
-		const mathContext = new ContextMenu();
+				button.onClick( () => add( new item.nodeClass() ) );
 
-		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( invertNode )
-			.add( limiterNode )
-			.add( dotNode )
-			.add( powNode )
-			.add( triNode )
-			.add( normalizeNode );
-
-		//**************//
-		// ACCESSORS
-		//**************//
-
-		const accessorsContext = new ContextMenu();
-
-		const uvNode = new ButtonInput( 'UV' ).setIcon( 'ti ti-details' )
-			.onClick( () => add( new UVEditor() ) );
-
-		const positionNode = new ButtonInput( 'Position' ).setIcon( 'ti ti-hierarchy' )
-			.onClick( () => add( new PositionEditor() ) );
-
-		const normalNode = new ButtonInput( 'Normal' ).setIcon( 'ti ti-fold-up' )
-			.onClick( () => add( new NormalEditor() ) );
+			}
 
-		accessorsContext
-			.add( uvNode )
-			.add( positionNode )
-			.add( normalNode );
+			if ( item.tip ) {
 
-		//**************//
-		// PROCEDURAL
-		//**************//
+				button.setToolTip( item.tip );
 
-		const proceduralContext = new ContextMenu();
+			}
 
-		const checkerNode = new ButtonInput( 'Checker' ).setIcon( 'ti ti-border-outer' )
-			.onClick( () => add( new CheckerEditor() ) );
+			if ( item.children ) {
 
-		proceduralContext
-			.add( checkerNode );
+				context = new ContextMenu();
 
-		//**************//
-		// DISPLAY
-		//**************//
+				for ( const subItem of item.children ) {
 
-		const displayContext = new ContextMenu();
+					const buttonMenu = createButtonMenu( subItem );
 
-		const blendNode = new ButtonInput( 'Blend' ).setIcon( 'ti ti-layers-subtract' )
-			.onClick( () => add( new BlendEditor() ) );
+					context.add( buttonMenu.button, buttonMenu.context );
 
-		displayContext
-			.add( blendNode );
+				}
 
-		//**************//
-		// UTILS
-		//**************//
+			}
 
-		const utilsContext = new ContextMenu();
+			return { button, context };
 
-		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() ) );
+		for ( const item of NodeList ) {
 
-		utilsContext
-			.add( timerNode )
-			.add( oscNode );
+			const buttonMenu = createButtonMenu( item );
 
-		//**************//
-		// MAIN
-		//**************//
+			context.add( buttonMenu.button, buttonMenu.context );
 
-		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.nodesContext = context;
 

+ 6 - 7
examples/jsm/node-editor/accessors/NormalEditor.js

@@ -1,21 +1,20 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { NormalNode } from '../../renderers/nodes/Nodes.js';
 
-export class NormalEditor extends ObjectNode {
+export class NormalEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new NormalNode();
 
-		super( 'Normal', 3, node, 250 );
-
-		this.title.setStyle( 'red' );
+		super( 'Normal', 3, node, 200 );
 
 		const optionsField = new SelectInput( [
 			{ name: 'Local', value: NormalNode.LOCAL },
 			{ name: 'World', value: NormalNode.WORLD },
 			{ name: 'View', value: NormalNode.VIEW }
-		] ).onChange( () => {
+		], NormalNode.LOCAL ).onChange( () => {
 
 			node.scope = optionsField.getValue();
 
@@ -23,7 +22,7 @@ export class NormalEditor extends ObjectNode {
 
 		} );
 
-		this.add( new LabelElement( 'Scope' ).add( optionsField ) );
+		this.add( new Element().add( optionsField ) );
 
 	}
 

+ 6 - 7
examples/jsm/node-editor/accessors/PositionEditor.js

@@ -1,21 +1,20 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { PositionNode } from '../../renderers/nodes/Nodes.js';
 
-export class PositionEditor extends ObjectNode {
+export class PositionEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new PositionNode();
 
-		super( 'Position', 3, node, 250 );
-
-		this.title.setStyle( 'red' );
+		super( 'Position', 3, node, 200 );
 
 		const optionsField = new SelectInput( [
 			{ name: 'Local', value: PositionNode.LOCAL },
 			{ name: 'World', value: PositionNode.WORLD },
 			{ name: 'View', value: PositionNode.VIEW }
-		] ).onChange( () => {
+		], PositionNode.LOCAL ).onChange( () => {
 
 			node.scope = optionsField.getValue();
 
@@ -23,7 +22,7 @@ export class PositionEditor extends ObjectNode {
 
 		} );
 
-		this.add( new LabelElement( 'Scope' ).add( optionsField ) );
+		this.add( new Element().add( optionsField ) );
 
 	}
 

+ 5 - 6
examples/jsm/node-editor/accessors/UVEditor.js

@@ -1,17 +1,16 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { UVNode } from '../../renderers/nodes/Nodes.js';
 
-export class UVEditor extends ObjectNode {
+export class UVEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new UVNode();
 
-		super( 'UV', 2, node, 250 );
+		super( 'UV', 2, node, 200 );
 
-		this.title.setStyle( 'red' );
-
-		const optionsField = new SelectInput( [ '1', '2' ] ).onChange( () => {
+		const optionsField = new SelectInput( [ '1', '2' ], 0 ).onChange( () => {
 
 			node.value = Number( optionsField.getValue() );
 

+ 83 - 0
examples/jsm/node-editor/core/BaseNode.js

@@ -0,0 +1,83 @@
+import { ObjectNode } from '../../libs/flow.module.js';
+
+export class BaseNode extends ObjectNode {
+
+	constructor( name, inputLength, value = null, width = 300 ) {
+
+		const getObjectCallback = ( /*output = null*/ ) => {
+
+			return this.value;
+
+		};
+
+		super( name, inputLength, getObjectCallback, width );
+
+		this.setOutputColor( this.getColorValueFromValue( value ) );
+
+		this.editor = null;
+
+		this.value = value;
+
+		this.onValidElement = ( inputElement, outputElement ) => {
+
+			const outputObject = outputElement.getObject();
+
+			if ( ! outputObject || ! outputObject.isNode ) {
+
+				return false;
+
+			}
+
+		};
+
+	}
+
+	serialize( data ) {
+
+		super.serialize( data );
+
+		delete data.width;
+
+	}
+
+	deserialize( data ) {
+
+		delete data.width;
+
+		super.deserialize( data );
+
+	}
+
+	setEditor( value ) {
+
+		this.editor = value;
+
+		return this;
+
+	}
+
+	getColorValueFromValue( value ) {
+
+		if ( ! value ) return;
+
+		if ( value.isMaterial === true ) {
+
+			return 'forestgreen';
+
+		} else if ( value.isObject3D === true ) {
+
+			return 'orange';
+
+		}
+
+	}
+
+	add( element ) {
+
+		element.onValid( ( source, target ) => this.onValidElement( source, target ) );
+
+		return super.add( element );
+
+	}
+
+}

+ 7 - 6
examples/jsm/node-editor/display/BlendEditor.js

@@ -1,16 +1,17 @@
-import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.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 {
+export class BlendEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new MathNode( MathNode.MIX, NULL_VALUE, NULL_VALUE, ONE_VALUE );
 
-		super( 'Blend', 3, node );
+		super( 'Blend', 3, node, 200 );
 
 		const aElement = new LabelElement( 'Base' ).setInput( 3 );
 		const bElement = new LabelElement( 'Blend' ).setInput( 3 );
@@ -18,19 +19,19 @@ export class BlendEditor extends ObjectNode {
 
 		aElement.onConnect( () => {
 
-			node.aNode = aElement.linkedExtra || NULL_VALUE;
+			node.aNode = aElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
 		bElement.onConnect( () => {
 
-			node.bNode = bElement.linkedExtra || NULL_VALUE;
+			node.bNode = bElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
 		cElement.onConnect( () => {
 
-			node.cNode = cElement.linkedExtra || ONE_VALUE;
+			node.cNode = cElement.getLinkedObject() || ONE_VALUE;
 
 		} );
 

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


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


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


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


+ 14 - 10
examples/jsm/node-editor/inputs/ColorEditor.js

@@ -1,20 +1,20 @@
-import { ObjectNode, ColorInput, StringInput, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { ColorInput, StringInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { ColorNode } from '../../renderers/nodes/Nodes.js';
 
-export class ColorEditor extends ObjectNode {
+export class ColorEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new ColorNode();
 
-		super( 'Color', 1, node );
-
-		this.title.setIcon( 'ti ti-palette' );
+		super( 'Color', 3, node );
 
 		const updateFields = ( editing ) => {
 
 			const value = node.value;
 			const hexValue = value.getHex();
+			const hexString = hexValue.toString( 16 ).toUpperCase().padStart( 6, '0' );
 
 			if ( editing !== 'color' ) {
 
@@ -24,7 +24,7 @@ export class ColorEditor extends ObjectNode {
 
 			if ( editing !== 'hex' ) {
 
-				hexField.setValue( '#' + hexValue.toString( 16 ).toUpperCase().padEnd( 6, '0' ), false );
+				hexField.setValue( '#' + hexString, false );
 
 			}
 
@@ -36,6 +36,10 @@ export class ColorEditor extends ObjectNode {
 
 			}
 
+			fieldR.setTagColor( `#${hexString.substr( 0, 2 )}0000` );
+			fieldG.setTagColor( `#00${hexString.substr( 2, 2 )}00` );
+			fieldB.setTagColor( `#0000${hexString.substr( 4, 2 )}` );
+
 		};
 
 		const field = new ColorInput( 0xFFFFFF ).onChange( () => {
@@ -76,11 +80,11 @@ export class ColorEditor extends ObjectNode {
 
 		};
 
-		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 );
+		const fieldR = new NumberInput( 1, 0, 1 ).setTagColor( 'red' ).onChange( onChangeRGB );
+		const fieldG = new NumberInput( 1, 0, 1 ).setTagColor( 'green' ).onChange( onChangeRGB );
+		const fieldB = new NumberInput( 1, 0, 1 ).setTagColor( 'blue' ).onChange( onChangeRGB );
 
-		this.add( new LabelElement( 'Value' ).add( field ).setSerializable( false ) )
+		this.add( new Element().add( field ).setSerializable( false ) )
 			.add( new LabelElement( 'Hex' ).add( hexField ).setSerializable( false ) )
 			.add( new LabelElement( 'RGB' ).add( fieldR ).add( fieldG ).add( fieldB ) );
 

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

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

+ 4 - 5
examples/jsm/node-editor/inputs/SliderEditor.js

@@ -1,7 +1,8 @@
-import { ObjectNode, ButtonInput, SliderInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { ButtonInput, SliderInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { FloatNode } from '../../renderers/nodes/Nodes.js';
 
-export class SliderEditor extends ObjectNode {
+export class SliderEditor extends BaseNode {
 
 	constructor() {
 
@@ -9,8 +10,6 @@ export class SliderEditor extends ObjectNode {
 
 		super( 'Slider', 1, node );
 
-		this.title.setIcon( 'ti ti-adjustments-horizontal' );
-
 		this.collapse = true;
 
 		const field = new SliderInput( 0, 0, 1 ).onChange( () => {
@@ -50,7 +49,7 @@ export class SliderEditor extends ObjectNode {
 
 		} ) ).setSerializable( false );
 
-		this.add( new LabelElement( 'Value' ).add( field ) )
+		this.add( new Element().add( field ) )
 			.add( minElement )
 			.add( maxElement )
 			.add( moreElement );

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

@@ -1,7 +1,8 @@
-import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { Vector2Node } from '../../renderers/nodes/Nodes.js';
 
-export class Vector2Editor extends ObjectNode {
+export class Vector2Editor extends BaseNode {
 
 	constructor() {
 
@@ -9,8 +10,6 @@ export class Vector2Editor extends ObjectNode {
 
 		super( 'Vector 2', 2, node );
 
-		this.title.setIcon( 'ti ti-box-multiple-2' );
-
 		const onUpdate = () => {
 
 			node.value.x = fieldX.getValue();
@@ -18,10 +17,10 @@ export class Vector2Editor extends ObjectNode {
 
 		};
 
-		const fieldX = new NumberInput().onChange( onUpdate );
-		const fieldY = new NumberInput().onChange( onUpdate );
+		const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
+		const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
 
-		this.add( new LabelElement( 'Values' ).add( fieldX ).add( fieldY ) );
+		this.add( new LabelElement( 'XY' ).add( fieldX ).add( fieldY ) );
 
 	}
 

+ 8 - 9
examples/jsm/node-editor/inputs/Vector3Editor.js

@@ -1,15 +1,14 @@
-import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { Vector3Node } from '../../renderers/nodes/Nodes.js';
 
-export class Vector3Editor extends ObjectNode {
+export class Vector3Editor extends BaseNode {
 
 	constructor() {
 
 		const node = new Vector3Node();
 
-		super( 'Vector 3', 3, node );
-
-		this.title.setIcon( 'ti ti-box-multiple-3' );
+		super( 'Vector 3', 3, node, 325 );
 
 		const onUpdate = () => {
 
@@ -19,11 +18,11 @@ export class Vector3Editor extends ObjectNode {
 
 		};
 
-		const fieldX = new NumberInput().onChange( onUpdate );
-		const fieldY = new NumberInput().onChange( onUpdate );
-		const fieldZ = new NumberInput().onChange( onUpdate );
+		const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
+		const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
+		const fieldZ = new NumberInput().setTagColor( 'blue' ).onChange( onUpdate );
 
-		this.add( new LabelElement( 'Values' ).add( fieldX ).add( fieldY ).add( fieldZ ) );
+		this.add( new LabelElement( 'XYZ' ).add( fieldX ).add( fieldY ).add( fieldZ ) );
 
 	}
 

+ 9 - 10
examples/jsm/node-editor/inputs/Vector4Editor.js

@@ -1,15 +1,14 @@
-import { ObjectNode, NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { NumberInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { Vector4Node } from '../../renderers/nodes/Nodes.js';
 
-export class Vector4Editor extends ObjectNode {
+export class Vector4Editor extends BaseNode {
 
 	constructor() {
 
 		const node = new Vector4Node();
 
-		super( 'Vector 4', 4, node );
-
-		this.title.setIcon( 'ti ti-box-multiple-4' );
+		super( 'Vector 4', 4, node, 350 );
 
 		const onUpdate = () => {
 
@@ -20,12 +19,12 @@ export class Vector4Editor extends ObjectNode {
 
 		};
 
-		const fieldX = new NumberInput().onChange( onUpdate );
-		const fieldY = new NumberInput().onChange( onUpdate );
-		const fieldZ = new NumberInput().onChange( onUpdate );
-		const fieldW = new NumberInput().onChange( onUpdate );
+		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 );
 
-		this.add( new LabelElement( 'Values' )
+		this.add( new LabelElement( 'XYZW' )
 			.add( fieldX )
 			.add( fieldY )
 			.add( fieldZ )

+ 13 - 14
examples/jsm/node-editor/materials/StandardMaterialEditor.js

@@ -1,16 +1,15 @@
-import { ObjectNode, ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
+import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { MeshStandardNodeMaterial } from '../../renderers/nodes/Nodes.js';
 import * as THREE from 'three';
 
-export class StandardMaterialEditor extends ObjectNode {
+export class StandardMaterialEditor extends BaseNode {
 
 	constructor() {
 
 		const material = new MeshStandardNodeMaterial();
 
-		super( 'Standard Material', 0, material );
-
-		this.title.setStyle( 'blue' );
+		super( 'Standard Material', 1, material );
 
 		this.setWidth( 300 );
 
@@ -70,17 +69,17 @@ export class StandardMaterialEditor extends ObjectNode {
 
 		const { material, color, opacity, roughness, metalness } = this;
 
-		color.setEnabledInputs( ! color.linkedExtra );
-		opacity.setEnabledInputs( ! opacity.linkedExtra );
-		roughness.setEnabledInputs( ! roughness.linkedExtra );
-		metalness.setEnabledInputs( ! metalness.linkedExtra );
+		color.setEnabledInputs( ! color.getLinkedObject() );
+		opacity.setEnabledInputs( ! opacity.getLinkedObject() );
+		roughness.setEnabledInputs( ! roughness.getLinkedObject() );
+		metalness.setEnabledInputs( ! metalness.getLinkedObject() );
 
-		material.colorNode = color.linkedExtra;
+		material.colorNode = color.getLinkedObject();
 
-		material.opacityNode = opacity.linkedExtra || null;
+		material.opacityNode = opacity.getLinkedObject() || null;
 
-		material.metalnessNode = metalness.linkedExtra;
-		material.roughnessNode = roughness.linkedExtra;
+		material.metalnessNode = metalness.getLinkedObject();
+		material.roughnessNode = roughness.getLinkedObject();
 
 		material.dispose();
 
@@ -99,7 +98,7 @@ export class StandardMaterialEditor extends ObjectNode {
 
 		const { material, opacity } = this;
 
-		material.transparent = opacity.linkedExtra || material.opacity < 1 ? true : false;
+		material.transparent = opacity.getLinkedObject() || material.opacity < 1 ? true : false;
 
 		opacity.setIcon( material.transparent ? 'ti ti-layers-intersect' : 'ti ti-layers-subtract' );
 

+ 6 - 7
examples/jsm/node-editor/math/DotEditor.js

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

+ 4 - 3
examples/jsm/node-editor/math/InvertEditor.js

@@ -1,9 +1,10 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 
 const DEFAULT_VALUE = new FloatNode();
 
-export class InvertEditor extends ObjectNode {
+export class InvertEditor extends BaseNode {
 
 	constructor() {
 
@@ -26,7 +27,7 @@ export class InvertEditor extends ObjectNode {
 
 		input.onConnect( () => {
 
-			node.aNode = input.linkedExtra || DEFAULT_VALUE;
+			node.aNode = input.getLinkedObject() || DEFAULT_VALUE;
 
 		} );
 

+ 11 - 10
examples/jsm/node-editor/math/LimiterEditor.js

@@ -1,20 +1,21 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { MathNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 
 const NULL_VALUE = new FloatNode();
 
-export class LimiterEditor extends ObjectNode {
+export class LimiterEditor extends BaseNode {
 
 	constructor() {
 
-		const node = new MathNode( MathNode.MAX, NULL_VALUE, NULL_VALUE );
+		const node = new MathNode( MathNode.MIN, NULL_VALUE, NULL_VALUE );
 
-		super( 'Limiter', 1, node, 250 );
+		super( 'Limiter', 1, node, 175 );
 
 		const methodInput = new SelectInput( [
-			{ name: 'Max', value: MathNode.MAX },
-			{ name: 'Min', value: MathNode.MIN }
-		] );
+			{ name: 'Min', value: MathNode.MIN },
+			{ name: 'Max', value: MathNode.MAX }
+		], MathNode.MIN );
 
 		methodInput.onChange( ( data ) => {
 
@@ -29,17 +30,17 @@ export class LimiterEditor extends ObjectNode {
 
 		aElement.onConnect( () => {
 
-			node.aNode = aElement.linkedExtra || NULL_VALUE;
+			node.aNode = aElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
 		bElement.onConnect( () => {
 
-			node.bNode = bElement.linkedExtra || NULL_VALUE;
+			node.bNode = bElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
-		this.add( new LabelElement( 'Method' ).add( methodInput ) )
+		this.add( new Element().add( methodInput ) )
 			.add( aElement )
 			.add( bElement );
 

+ 6 - 5
examples/jsm/node-editor/math/NormalizeEditor.js

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

+ 12 - 11
examples/jsm/node-editor/math/OperatorEditor.js

@@ -1,22 +1,23 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { OperatorNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 
 const NULL_VALUE = new FloatNode();
 
-export class OperatorEditor extends ObjectNode {
+export class OperatorEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new OperatorNode( '+', NULL_VALUE, NULL_VALUE );
 
-		super( 'Operator', 1, node, 250 );
+		super( 'Operator', 1, node, 150 );
 
 		const opInput = new SelectInput( [
-			{ name: '+ Addition', value: '+' },
-			{ name: '- Subtraction', value: '-' },
-			{ name: '* Multiplication', value: '*' },
-			{ name: '/ Division', value: '/' }
-		] );
+			{ name: 'Addition ( + )', value: '+' },
+			{ name: 'Subtraction ( - )', value: '-' },
+			{ name: 'Multiplication ( * )', value: '*' },
+			{ name: 'Division ( / )', value: '/' }
+		], '+' );
 
 		opInput.onChange( ( data ) => {
 
@@ -31,17 +32,17 @@ export class OperatorEditor extends ObjectNode {
 
 		aElement.onConnect( () => {
 
-			node.aNode = aElement.linkedExtra || NULL_VALUE;
+			node.aNode = aElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
 		bElement.onConnect( () => {
 
-			node.bNode = bElement.linkedExtra || NULL_VALUE;
+			node.bNode = bElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
-		this.add( new LabelElement( 'Operator' ).add( opInput ) )
+		this.add( new Element().add( opInput ) )
 			.add( aElement )
 			.add( bElement );
 

+ 6 - 5
examples/jsm/node-editor/math/PowerEditor.js

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

+ 7 - 6
examples/jsm/node-editor/math/TrigonometryEditor.js

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

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

@@ -1,9 +1,10 @@
-import { ObjectNode, LabelElement } from '../../libs/flow.module.js';
+import { LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { CheckerNode, UVNode } from '../../renderers/nodes/Nodes.js';
 
 const DEFAULT_UV = new UVNode();
 
-export class CheckerEditor extends ObjectNode {
+export class CheckerEditor extends BaseNode {
 
 	constructor() {
 
@@ -15,7 +16,7 @@ export class CheckerEditor extends ObjectNode {
 
 		field.onConnect( () => {
 
-			node.uvNode = field.linkedExtra || DEFAULT_UV;
+			node.uvNode = field.getLinkedObject() || DEFAULT_UV;
 
 		} );
 

+ 208 - 0
examples/jsm/node-editor/scene/MeshEditor.js

@@ -0,0 +1,208 @@
+import { NumberInput, StringInput, LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
+import { Mesh, MathUtils, Vector3} from '../../../../build/three.module.js';
+
+export class MeshEditor extends BaseNode {
+
+	constructor( mesh = null ) {
+
+		if ( mesh === null ) {
+
+			mesh = new Mesh();
+
+		}
+
+		super( 'Mesh', 1, mesh );
+
+		this.material = null;
+
+		this.defaultMaterial = null;
+		this.defaultPosition = new Vector3();
+		this.defaultRotation = new Vector3();
+		this.defaultScale = new Vector3( 100, 100, 100 );
+
+		this._initTags();
+		this._initTransform();
+		this._initMaterial();
+
+		this.updateDefault();
+		this.update();
+
+		this.onValidElement = () => {};
+
+	}
+
+	setEditor( editor ) {
+
+		if ( this.editor ) {
+			
+			this._restoreDefault();
+			
+		}
+
+		super.setEditor( editor );
+
+		if ( editor ) {
+
+			const name = this.nameInput.getValue();
+			const mesh = editor.scene.getObjectByName( name );
+
+			this.value = mesh;
+
+			this.updateDefault();
+			this.update();
+
+		}
+
+		return this;
+
+	}
+
+	get mesh() {
+
+		return this.value;
+
+	}
+
+	_initMaterial() {
+
+		const materialElement = new LabelElement( 'Material' ).setInputColor( 'forestgreen' ).setInput( 1 );
+
+		materialElement.onValid( ( source, target, stage ) => {
+
+			const object = target.getObject();
+
+			if ( object && object.isMaterial !== true ) {
+
+				if ( stage === 'dragged' ) {
+
+					const name = target.node.getName();
+
+					this.editor.tips.error( `"${name}" is not a Material.` );
+
+				}
+
+				return false;
+
+			}
+
+		} ).onConnect( () => {
+
+			this.material = materialElement.getLinkedObject() || this.defaultMaterial;
+
+			this.update();
+
+		} );
+
+		this.add( materialElement );
+
+	}
+
+	_initTags() {
+
+		this.nameInput = new StringInput( this.mesh.name ).setReadOnly( true )
+			.onChange( () => this.mesh.name = this.nameInput.getValue() );
+
+		this.add( new LabelElement( 'Name' ).add( this.nameInput ) );
+
+	}
+
+	_initTransform() {
+
+		const update = () => this.update();
+
+		const posX = new NumberInput().setTagColor( 'red' ).onChange( update );
+		const posY = new NumberInput().setTagColor( 'green' ).onChange( update );
+		const posZ = new NumberInput().setTagColor( 'blue' ).onChange( update );
+
+		const rotationStep = 1;
+
+		const rotX = new NumberInput().setTagColor( 'red' ).setStep( rotationStep ).onChange( update );
+		const rotY = new NumberInput().setTagColor( 'green' ).setStep( rotationStep ).onChange( update );
+		const rotZ = new NumberInput().setTagColor( 'blue' ).setStep( rotationStep ).onChange( update );
+
+		const scaleX = new NumberInput( 100 ).setTagColor( 'red' ).setStep( rotationStep ).onChange( update );
+		const scaleY = new NumberInput( 100 ).setTagColor( 'green' ).setStep( rotationStep ).onChange( update );
+		const scaleZ = new NumberInput( 100 ).setTagColor( 'blue' ).setStep( rotationStep ).onChange( update );
+
+		this.add( new LabelElement( 'Position' ).add( posX ).add( posY ).add( posZ ) )
+			.add( new LabelElement( 'Rotation' ).add( rotX ).add( rotY ).add( rotZ ) )
+			.add( new LabelElement( 'Scale' ).add( scaleX ).add( scaleY ).add( scaleZ ) );
+
+		this.posX = posX;
+		this.posY = posY;
+		this.posZ = posZ;
+
+		this.rotX = rotX;
+		this.rotY = rotY;
+		this.rotZ = rotZ;
+
+		this.scaleX = scaleX;
+		this.scaleY = scaleY;
+		this.scaleZ = scaleZ;
+
+	}
+
+	update() {
+
+		const mesh = this.mesh;
+
+		if ( mesh ) {
+			
+			const { position, rotation, scale } = mesh;
+
+			position.x = this.posX.getValue();
+			position.y = this.posY.getValue();
+			position.z = this.posZ.getValue();
+
+			rotation.x = MathUtils.degToRad( this.rotX.getValue() );
+			rotation.y = MathUtils.degToRad( this.rotY.getValue() );
+			rotation.z = MathUtils.degToRad( this.rotZ.getValue() );
+			
+			scale.x = this.scaleX.getValue() / 100;
+			scale.y = this.scaleY.getValue() / 100;
+			scale.z = this.scaleZ.getValue() / 100;
+			
+			mesh.material = this.material || this.defaultMaterial;
+
+		}
+
+	}
+	
+	updateDefault() {
+
+		const { material, position, rotation, scale } = this.mesh;
+
+		this.defaultMaterial = material;
+
+		this.defaultPosition = position.clone();
+		this.defaultRotation = new Vector3( MathUtils.radToDeg( rotation.x ), MathUtils.radToDeg( rotation.y ), MathUtils.radToDeg( rotation.z ) );
+		this.defaultScale = scale.clone().multiplyScalar( 100 );
+
+		this._restoreDefault();
+
+	}
+	
+	_restoreDefault() {
+		
+		const position = this.defaultPosition;
+		const rotation = this.defaultRotation;
+		const scale = this.defaultScale;
+
+		this.posX.setValue( position.x );
+		this.posY.setValue( position.y );
+		this.posZ.setValue( position.z );
+
+		this.rotX.setValue( rotation.x );
+		this.rotY.setValue( rotation.y );
+		this.rotZ.setValue( rotation.z );
+
+		this.scaleX.setValue( scale.x );
+		this.scaleY.setValue( scale.y );
+		this.scaleZ.setValue( scale.z );
+		
+		this.mesh.material = this.defaultMaterial;
+		
+	}
+
+}

+ 58 - 0
examples/jsm/node-editor/utils/JoinEditor.js

@@ -0,0 +1,58 @@
+import { LabelElement } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
+import { JoinNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class JoinEditor extends BaseNode {
+
+	constructor() {
+
+		const node = new JoinNode();
+
+		super( 'Join', 1, node, 175 );
+
+		const update = () => {
+
+			const values = [
+				xElement.getLinkedObject(),
+				yElement.getLinkedObject(),
+				zElement.getLinkedObject(),
+				wElement.getLinkedObject()
+			];
+
+			let length = 1;
+
+			if ( values[ 3 ] !== null ) length = 4;
+			else if ( values[ 2 ] !== null ) length = 3;
+			else if ( values[ 1 ] !== null ) length = 2;
+
+			const nodes = [];
+
+			for ( let i = 0; i < length; i ++ ) {
+
+				nodes.push( values[ i ] || NULL_VALUE );
+
+			}
+
+			node.nodes = nodes;
+
+			this.invalidate();
+
+		};
+
+		const xElement = new LabelElement( 'X | R' ).setInput( 1 ).onConnect( update );
+		const yElement = new LabelElement( 'Y | G' ).setInput( 1 ).onConnect( update );
+		const zElement = new LabelElement( 'Z | B' ).setInput( 1 ).onConnect( update );
+		const wElement = new LabelElement( 'W | A' ).setInput( 1 ).onConnect( update );
+
+		this.add( xElement )
+			.add( yElement )
+			.add( zElement )
+			.add( wElement );
+
+		update();
+
+	}
+
+}

+ 7 - 6
examples/jsm/node-editor/utils/OscillatorEditor.js

@@ -1,22 +1,23 @@
-import { ObjectNode, SelectInput, LabelElement } from '../../libs/flow.module.js';
+import { SelectInput, LabelElement, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { OscNode, FloatNode } from '../../renderers/nodes/Nodes.js';
 
 const NULL_VALUE = new FloatNode();
 
-export class OscillatorEditor extends ObjectNode {
+export class OscillatorEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new OscNode( OscNode.SINE, NULL_VALUE );
 
-		super( 'Oscillator', 1, node, 250 );
+		super( 'Oscillator', 1, node, 175 );
 
 		const methodInput = new SelectInput( [
 			{ name: 'Sine', value: OscNode.SINE },
 			{ name: 'Square', value: OscNode.SQUARE },
 			{ name: 'Triangle', value: OscNode.TRIANGLE },
 			{ name: 'Sawtooth', value: OscNode.SAWTOOTH }
-		] );
+		], OscNode.SINE );
 
 		methodInput.onChange( () => {
 
@@ -30,11 +31,11 @@ export class OscillatorEditor extends ObjectNode {
 
 		timeElement.onConnect( () => {
 
-			node.timeNode = timeElement.linkedExtra || NULL_VALUE;
+			node.timeNode = timeElement.getLinkedObject() || NULL_VALUE;
 
 		} );
 
-		this.add( new LabelElement( 'Method' ).add( methodInput ) )
+		this.add( new Element().add( methodInput ) )
 			.add( timeElement );
 
 	}

+ 39 - 0
examples/jsm/node-editor/utils/SplitEditor.js

@@ -0,0 +1,39 @@
+import { SelectInput, Element } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
+import { SplitNode, FloatNode } from '../../renderers/nodes/Nodes.js';
+
+const NULL_VALUE = new FloatNode();
+
+export class SplitEditor extends BaseNode {
+
+	constructor() {
+
+		const node = new SplitNode( NULL_VALUE, 'x' );
+
+		super( 'Split', 1, node, 175 );
+
+		const componentsField = new SelectInput( [
+			{ name: 'X | R', value: 'x' },
+			{ name: 'Y | G', value: 'y' },
+			{ name: 'Z | B', value: 'z' },
+			{ name: 'W | A', value: 'w' }
+		], node.components ).onChange( () => {
+
+			node.components = componentsField.getValue();
+
+			this.invalidate();
+
+		} );
+
+		const componentsElement = new Element().add( componentsField ).setInput( 1 )
+			.onConnect( () => {
+
+				node.node = componentsElement.getLinkedObject() || NULL_VALUE;
+
+			} );
+
+		this.add( componentsElement );
+
+	}
+
+}

+ 6 - 5
examples/jsm/node-editor/utils/TimerEditor.js

@@ -1,13 +1,14 @@
-import { ObjectNode, NumberInput, LabelElement, Element, ButtonInput } from '../../libs/flow.module.js';
+import { NumberInput, LabelElement, Element, ButtonInput } from '../../libs/flow.module.js';
+import { BaseNode } from '../core/BaseNode.js';
 import { TimerNode } from '../../renderers/nodes/Nodes.js';
 
-export class TimerEditor extends ObjectNode {
+export class TimerEditor extends BaseNode {
 
 	constructor() {
 
 		const node = new TimerNode();
 
-		super( 'Timer', 1, node, 250 );
+		super( 'Timer', 1, node, 200 );
 
 		this.title.setIcon( 'ti ti-clock' );
 
@@ -37,8 +38,8 @@ export class TimerEditor extends ObjectNode {
 
 		} ) ).setSerializable( false );
 
-		this.add( new LabelElement( 'Value' ).add( field ).setSerializable( false ) )
-			.add( new LabelElement( 'Scale' ).add( scaleField ) )
+		this.add( new Element().add( field ).setSerializable( false ) )
+			.add( new LabelElement( 'Speed' ).add( scaleField ) )
 			.add( moreElement );
 
 		// extends node

+ 2 - 1
examples/jsm/renderers/nodes/core/NodeBuilder.js

@@ -8,7 +8,8 @@ import { NodeUpdateType } from './constants.js';
 
 import { REVISION, LinearEncoding } from 'three';
 
-const shaderStages = [ 'fragment', 'vertex' ];
+export const shaderStages = [ 'fragment', 'vertex' ];
+export const vector = [ 'x', 'y', 'z', 'w' ];
 
 class NodeBuilder {
 

+ 19 - 4
examples/jsm/renderers/nodes/utils/SplitNode.js

@@ -1,4 +1,5 @@
 import Node from '../core/Node.js';
+import { vector } from '../core/NodeBuilder.js';
 
 class SplitNode extends Node {
 
@@ -11,6 +12,20 @@ class SplitNode extends Node {
 
 	}
 
+	getVectorLength() {
+
+		let vectorLength = this.components.length;
+
+		for ( const c of this.components ) {
+
+			vectorLength = Math.max( vector.indexOf( c ) + 1, vectorLength );
+
+		}
+
+		return vectorLength;
+
+	}
+
 	getNodeType( builder ) {
 
 		return builder.getTypeFromLength( this.components.length );
@@ -24,15 +39,15 @@ class SplitNode extends Node {
 
 		if ( nodeTypeLength > 1 ) {
 
-			const components = this.components;
-
 			let type = null;
 
-			if ( components.length >= nodeTypeLength ) {
+			const componentsLength = this.getVectorLength();
+
+			if ( componentsLength >= nodeTypeLength ) {
 
 				// need expand the input node
 
-				type = this.getNodeType( builder );
+				type = builder.getTypeFromLength( this.getVectorLength() );
 
 			}
 

二进制
examples/screenshots/webgl_materials_nodes_playground.jpg


二进制
examples/screenshots/webgl_nodes_playground.jpg


+ 0 - 186
examples/webgl_materials_nodes_playground.html

@@ -1,186 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - node-editor playground</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<link rel="stylesheet" href="fonts/open-sans/open-sans.css" type="text/css"/>
-		<link rel="stylesheet" href="fonts/tabler-icons/tabler-icons.min.css" type="text/css"/>
-		<link type="text/css" rel="stylesheet" href="main.css">
-		<style>
-			body {
-				overflow: hidden;
-				width: 100%;
-				height: 100%;
-			}
-			.renderer {
-				position: absolute;
-				top: 0;
-				left: 0;
-				height: 50%;
-				width: 100%;
-			}
-			flow {
-				position: absolute;
-				top: 50%;
-				left: 0;
-				height: 50%;
-				width: 100%;
-				background: #222;
-				box-shadow: inset 0 0 20px 0px #000000;
-			}
-		</style>
-	</head>
-	<body>
-		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js"
-				}
-			}
-		</script>
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import { nodeFrame } from './jsm/renderers/webgl/nodes/WebGLNodes.js';
-
-			import { NodeEditor } from './jsm/node-editor/NodeEditor.js';
-			import { StandardMaterialEditor } from './jsm/node-editor/materials/StandardMaterialEditor.js';
-
-			import Stats from './jsm/libs/stats.module.js';
-
-			import { OrbitControls } from './jsm/controls/OrbitControls.js';
-			import { FBXLoader } from './jsm/loaders/FBXLoader.js';
-
-			let stats;
-			let camera, scene, renderer;
-			let model;
-
-			init();
-			animate();
-
-			function init() {
-
-				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
-				camera.position.set( 0.0, 300, 400 * 3 );
-
-				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0x333333 );
-
-				// Lights
-
-				const topLight = new THREE.PointLight( 0xF4F6F0, 1 );
-				topLight.position.set( 0, 100000, 100000 );
-				scene.add( topLight );
-
-				const backLight = new THREE.PointLight( 0x0c1445, 1.4 );
-				backLight.position.set( - 100, 20, - 260 );
-				scene.add( backLight );
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				document.body.appendChild( renderer.domElement );
-				renderer.outputEncoding = THREE.sRGBEncoding;
-
-				renderer.domElement.className = 'renderer';
-
-				//
-
-				stats = new Stats();
-				document.body.appendChild( stats.dom );
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.minDistance = 500;
-				controls.maxDistance = 3000;
-
-				window.addEventListener( 'resize', onWindowResize );
-
-				onWindowResize();
-
-				initEditor();
-
-			}
-
-			function initEditor() {
-
-				const nodeEditor = new NodeEditor();
-
-				nodeEditor.addEventListener( 'new', () => {
-
-					const materialEditor = new StandardMaterialEditor();
-					materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 );
-
-					nodeEditor.add( materialEditor );
-
-					model.material = materialEditor.material;
-
-				} );
-
-				nodeEditor.addEventListener( 'load', () => {
-
-					const materialEditor = nodeEditor.nodes[ 0 ];
-					materialEditor.update(); // need move to deserialization
-
-					model.material = materialEditor.material;
-
-				} );
-
-				document.body.appendChild( nodeEditor.domElement );
-
-				const loaderFBX = new FBXLoader();
-				loaderFBX.load( 'models/fbx/stanford-bunny.fbx', ( object ) => {
-
-					const materialEditor = new StandardMaterialEditor();
-					materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 ); // canvas position
-
-					nodeEditor.add( materialEditor );
-
-					model = object.children[ 0 ];
-					model.position.set( 0, 0, 10 );
-					model.scale.setScalar( 1 );
-					model.material = materialEditor.material;
-					scene.add( model );
-
-				} );
-
-			}
-
-			function onWindowResize() {
-
-				const width = window.innerWidth;
-				const height = window.innerHeight / 2;
-
-				camera.aspect = width / height;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( width, height );
-
-			}
-
-			//
-
-			function animate() {
-
-				requestAnimationFrame( animate );
-
-				nodeFrame.update();
-
-				render();
-
-				stats.update();
-
-			}
-
-			function render() {
-
-				//if ( model ) model.rotation.y = performance.now() / 5000;
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 198 - 0
examples/webgl_nodes_playground.html

@@ -0,0 +1,198 @@
+@ -1,189 +0,0 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+		<title>three.js webgl - node-editor playground</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link rel="stylesheet" href="fonts/open-sans/open-sans.css" type="text/css"/>
+		<link rel="stylesheet" href="fonts/tabler-icons/tabler-icons.min.css" type="text/css"/>
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				overflow: hidden;
+				width: 100%;
+				height: 100%;
+			}
+			.renderer {
+				position: absolute;
+				top: 0;
+				left: 0;
+				height: 50%;
+				width: 100%;
+			}
+			flow {
+				position: absolute;
+				top: 50%;
+				left: 0;
+				height: 50%;
+				width: 100%;
+				background: #222;
+				box-shadow: inset 0 0 20px 0px #000000;
+			}
+		</style>
+  </head>
+  <body>
+
+	<div id="info">
+		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - WebGL - Node Editor ( Playground version )<br />
+	</div>
+
+	<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+	<script type="importmap">
+	{
+		"imports": {
+			"three": "../build/three.module.js"
+		}
+	}
+	</script>
+
+	<script type="module">
+
+		import * as THREE from 'three';
+
+		import { nodeFrame } from './jsm/renderers/webgl/nodes/WebGLNodes.js';
+
+		import { NodeEditor } from './jsm/node-editor/NodeEditor.js';
+		import { MeshEditor } from './jsm/node-editor/scene/MeshEditor.js';
+
+		import * as Nodes from './jsm/renderers/nodes/Nodes.js';
+
+		import Stats from './jsm/libs/stats.module.js';
+
+		import { OrbitControls } from './jsm/controls/OrbitControls.js';
+		import { FBXLoader } from './jsm/loaders/FBXLoader.js';
+
+		let stats;
+		let camera, scene, renderer;
+		let model;
+
+		init();
+		animate();
+
+		function init() {
+
+			camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
+			camera.position.set( 0.0, 300, 400 * 3 );
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0x333333 );
+
+			// Lights
+
+			const topLight = new THREE.PointLight( 0xF4F6F0, 1 );
+			topLight.position.set( 0, 100000, 100000 );
+			scene.add( topLight );
+
+			const backLight = new THREE.PointLight( 0x0c1445, 1.4 );
+			backLight.position.set( - 100, 20, - 260 );
+			scene.add( backLight );
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			document.body.appendChild( renderer.domElement );
+			renderer.outputEncoding = THREE.sRGBEncoding;
+
+			renderer.domElement.className = 'renderer';
+
+			//
+
+			stats = new Stats();
+			document.body.appendChild( stats.dom );
+
+			const controls = new OrbitControls( camera, renderer.domElement );
+			controls.minDistance = 500;
+			controls.maxDistance = 3000;
+
+			window.addEventListener( 'resize', onWindowResize );
+
+			onWindowResize();
+
+			initEditor();
+
+		}
+
+		function initEditor() {
+
+			const nodeEditor = new NodeEditor( scene );
+
+			nodeEditor.addEventListener( 'new', () => {
+
+				const materialEditor = new MeshEditor( model );
+
+				nodeEditor.add( materialEditor );
+				nodeEditor.centralizeNode( materialEditor );
+
+			} );
+
+			document.body.appendChild( nodeEditor.domElement );
+
+			const loaderFBX = new FBXLoader();
+			loaderFBX.load( 'models/fbx/stanford-bunny.fbx', ( object ) => {
+
+				const defaultMaterial = new Nodes.MeshBasicNodeMaterial();
+				defaultMaterial.colorNode = new Nodes.FloatNode( 0 );
+
+				const sphere = new THREE.Mesh( new THREE.SphereGeometry( 200, 32, 16 ), defaultMaterial  ) ;
+				sphere.name = 'Sphere';
+				sphere.position.set( 500, 0, -500 );
+				scene.add( sphere );
+
+				const box = new THREE.Mesh( new THREE.BoxGeometry( 200, 200, 200 ), defaultMaterial  ) ;
+				box.name = 'Box';
+				box.position.set( -500, 0, -500 );
+				scene.add( box );
+
+				model = object.children[ 0 ];
+				model.position.set( 0, 0, 10 );
+				model.scale.setScalar( 1 );
+				model.material = defaultMaterial;
+				scene.add( model );
+
+				const materialEditor = new MeshEditor( model );
+
+				nodeEditor.add( materialEditor );
+				nodeEditor.centralizeNode( materialEditor );
+
+			} );
+
+		}
+
+		function onWindowResize() {
+
+			const width = window.innerWidth;
+			const height = window.innerHeight / 2;
+
+			camera.aspect = width / height;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( width, height );
+
+		}
+
+		//
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			nodeFrame.update();
+
+			render();
+
+			stats.update();
+
+		}
+
+		function render() {
+
+			//if ( model ) model.rotation.y = performance.now() / 5000;
+
+			renderer.render( scene, camera );
+
+		}
+
+	</script>
+
+  </body>
+</html>

+ 30 - 16
examples/webgpu_nodes_playground.html

@@ -58,6 +58,7 @@
 
 			import { NodeEditor } from './jsm/node-editor/NodeEditor.js';
 			import { StandardMaterialEditor } from './jsm/node-editor/materials/StandardMaterialEditor.js';
+			import { MeshEditor } from './jsm/node-editor/scene/MeshEditor.js';
 
 			import * as Nodes from './jsm/renderers/nodes/Nodes.js';
 
@@ -130,27 +131,28 @@
 
 			function initEditor() {
 
-				const nodeEditor = new NodeEditor();
+				const nodeEditor = new NodeEditor( scene );
 
 				nodeEditor.addEventListener( 'new', () => {
 
-					const materialEditor = new StandardMaterialEditor();
-					materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 );
+					const materialEditor = new MeshEditor( model );
 
 					nodeEditor.add( materialEditor );
-
-					model.material = materialEditor.material;
-					model.material.lightNode = nodeLights;
+					nodeEditor.centralizeNode( materialEditor );
 
 				} );
 
-				nodeEditor.addEventListener( 'load', () => {
+				nodeEditor.addEventListener( 'add', ( e ) => {
+
+					const node = e.node;
+
+					if ( node.value !== null && node.value.isMaterial === true ) {
 
-					const materialEditor = nodeEditor.nodes[ 0 ];
-					materialEditor.update(); // need move to deserialization
+						const material = node.value;
 
-					model.material = materialEditor.material;
-					model.material.lightNode = nodeLights;
+						material.lightNode = nodeLights;
+
+					}
 
 				} );
 
@@ -159,18 +161,30 @@
 				const loaderFBX = new FBXLoader();
 				loaderFBX.load( 'models/fbx/stanford-bunny.fbx', ( object ) => {
 
-					const materialEditor = new StandardMaterialEditor();
-					materialEditor.setPosition( ( window.innerWidth / 2 ) - 150, 100 ); // canvas position
+					const defaultMaterial = new Nodes.MeshBasicNodeMaterial();
+					defaultMaterial.colorNode = new Nodes.FloatNode( 0 );
 
-					nodeEditor.add( materialEditor );
+					const sphere = new THREE.Mesh( new THREE.SphereGeometry( 200, 32, 16 ), defaultMaterial  ) ;
+					sphere.name = 'Sphere';
+					sphere.position.set( 500, 0, -500 );
+					scene.add( sphere );
+
+					const box = new THREE.Mesh( new THREE.BoxGeometry( 200, 200, 200 ), defaultMaterial  ) ;
+					box.name = 'Box';
+					box.position.set( -500, 0, -500 );
+					scene.add( box );
 
 					model = object.children[ 0 ];
 					model.position.set( 0, 0, 10 );
 					model.scale.setScalar( 1 );
-					model.material = materialEditor.material;
-					model.material.lightNode = nodeLights;
+					model.material = defaultMaterial;
 					scene.add( model );
 
+					const materialEditor = new MeshEditor( model );
+
+					nodeEditor.add( materialEditor );
+					nodeEditor.centralizeNode( materialEditor );
+
 				} );
 
 			}

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