From 98985923afe4fe4030fce256b0cc0d02dc68696f Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 6 Feb 2022 14:17:01 -0300 Subject: [PATCH] NodeMaterial: serialize/deserialize and Material.fromType() (#23314) * Node serializer (draft) * add Material.fromType() * NodeObjectLoader: serialize/deserialize * update default values * fix analytic light * force refresh material uniforms using .uniformsNeedUpdate * Revert "force refresh material uniforms using .uniformsNeedUpdate" This reverts commit b083fb44296941b0724ef3529bdcfa30b9dbda53. * mrdoob code style * cleanup * cleanup (2) * Update NodeMaterial.js Co-authored-by: mrdoob --- examples/jsm/renderers/nodes/Nodes.js | 104 +++++++++++++++- .../renderers/nodes/accessors/NormalNode.js | 16 +++ .../renderers/nodes/accessors/Object3DNode.js | 16 +++ .../renderers/nodes/accessors/PositionNode.js | 16 +++ .../jsm/renderers/nodes/accessors/UVNode.js | 16 +++ examples/jsm/renderers/nodes/core/Node.js | 114 +++++++++++++++++- .../jsm/renderers/nodes/core/NodeUtils.js | 19 +++ .../jsm/renderers/nodes/inputs/ColorNode.js | 25 ++++ .../jsm/renderers/nodes/inputs/FloatNode.js | 16 +++ .../jsm/renderers/nodes/inputs/IntNode.js | 16 +++ .../jsm/renderers/nodes/inputs/TextureNode.js | 16 +++ .../jsm/renderers/nodes/inputs/Vector2Node.js | 23 ++++ .../jsm/renderers/nodes/inputs/Vector3Node.js | 25 ++++ .../jsm/renderers/nodes/inputs/Vector4Node.js | 27 +++++ .../jsm/renderers/nodes/loaders/NodeLoader.js | 107 ++++++++++++++++ .../nodes/loaders/NodeMaterialLoader.js | 42 +++++++ .../nodes/loaders/NodeObjectLoader.js | 70 +++++++++++ .../nodes/materials/LineBasicNodeMaterial.js | 11 +- .../renderers/nodes/materials/Materials.js | 22 ++++ .../nodes/materials/MeshBasicNodeMaterial.js | 11 +- .../materials/MeshStandardNodeMaterial.js | 13 +- .../renderers/nodes/materials/NodeMaterial.js | 98 +++++++++++++++ .../nodes/materials/PointsNodeMaterial.js | 11 +- examples/jsm/renderers/nodes/math/MathNode.js | 16 +++ .../jsm/renderers/nodes/math/OperatorNode.js | 16 +++ examples/jsm/renderers/nodes/utils/OscNode.js | 16 +++ .../jsm/renderers/nodes/utils/SplitNode.js | 16 +++ .../jsm/renderers/nodes/utils/TimerNode.js | 19 ++- .../renderers/webgl/nodes/WebGLNodeBuilder.js | 24 +++- examples/webgl_materials_standard_nodes.html | 30 ++++- src/core/Object3D.js | 5 +- src/loaders/MaterialLoader.js | 4 +- src/materials/Material.js | 8 ++ src/materials/Materials.js | 84 ++++++++++--- 34 files changed, 1033 insertions(+), 39 deletions(-) create mode 100644 examples/jsm/renderers/nodes/core/NodeUtils.js create mode 100644 examples/jsm/renderers/nodes/loaders/NodeLoader.js create mode 100644 examples/jsm/renderers/nodes/loaders/NodeMaterialLoader.js create mode 100644 examples/jsm/renderers/nodes/loaders/NodeObjectLoader.js create mode 100644 examples/jsm/renderers/nodes/materials/NodeMaterial.js diff --git a/examples/jsm/renderers/nodes/Nodes.js b/examples/jsm/renderers/nodes/Nodes.js index 389794c68ef9a1..19b4ef55a20c5d 100644 --- a/examples/jsm/renderers/nodes/Nodes.js +++ b/examples/jsm/renderers/nodes/Nodes.js @@ -71,6 +71,11 @@ import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js'; import OscNode from './utils/OscNode.js'; import TimerNode from './utils/TimerNode.js'; +// loaders +import NodeLoader from './loaders/NodeLoader.js'; +import NodeObjectLoader from './loaders/NodeObjectLoader.js'; +import NodeMaterialLoader from './loaders/NodeMaterialLoader.js'; + // procedural import CheckerNode from './procedural/CheckerNode.js'; @@ -86,7 +91,7 @@ export * from './materials/Materials.js'; // shader node export * from './ShaderNode.js'; -export { +const nodeLib = { // core ArrayInputNode, AttributeNode, @@ -161,6 +166,101 @@ export { TimerNode, // procedural - CheckerNode + CheckerNode, + + // loaders + NodeLoader, + NodeObjectLoader, + NodeMaterialLoader + }; +export const fromType = ( type ) => { + + return new nodeLib[ type ](); + +}; + +export { + // core + ArrayInputNode, + AttributeNode, + BypassNode, + CodeNode, + ContextNode, + ExpressionNode, + FunctionCallNode, + FunctionNode, + InputNode, + Node, + NodeAttribute, + NodeBuilder, + NodeCode, + NodeFrame, + NodeFunctionInput, + NodeKeywords, + NodeUniform, + NodeVar, + NodeVary, + PropertyNode, + TempNode, + VarNode, + VaryNode, + + // accessors + CameraNode, + MaterialNode, + MaterialReferenceNode, + ModelNode, + ModelViewProjectionNode, + NormalNode, + Object3DNode, + PointUVNode, + PositionNode, + ReferenceNode, + SkinningNode, + UVNode, + + // inputs + ColorNode, + FloatNode, + IntNode, + Matrix3Node, + Matrix4Node, + TextureNode, + Vector2Node, + Vector3Node, + Vector4Node, + + // display + ColorSpaceNode, + NormalMapNode, + + // math + MathNode, + OperatorNode, + CondNode, + + // lights + LightContextNode, + LightNode, + LightsNode, + + // utils + ArrayElementNode, + ConvertNode, + JoinNode, + SplitNode, + SpriteSheetUVNode, + OscNode, + TimerNode, + + // procedural + CheckerNode, + + // loaders + NodeLoader, + NodeObjectLoader, + NodeMaterialLoader + +}; diff --git a/examples/jsm/renderers/nodes/accessors/NormalNode.js b/examples/jsm/renderers/nodes/accessors/NormalNode.js index bd58d9cd6bd8f3..f77e22bdb1f833 100644 --- a/examples/jsm/renderers/nodes/accessors/NormalNode.js +++ b/examples/jsm/renderers/nodes/accessors/NormalNode.js @@ -58,6 +58,22 @@ class NormalNode extends Node { } + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + + } + } export default NormalNode; diff --git a/examples/jsm/renderers/nodes/accessors/Object3DNode.js b/examples/jsm/renderers/nodes/accessors/Object3DNode.js index 9bc3ae3b3bc422..09c270e1e7d0f1 100644 --- a/examples/jsm/renderers/nodes/accessors/Object3DNode.js +++ b/examples/jsm/renderers/nodes/accessors/Object3DNode.js @@ -100,6 +100,22 @@ class Object3DNode extends Node { } + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + + } + } export default Object3DNode; diff --git a/examples/jsm/renderers/nodes/accessors/PositionNode.js b/examples/jsm/renderers/nodes/accessors/PositionNode.js index fb3e8cf1a591f1..b342be12009bf7 100644 --- a/examples/jsm/renderers/nodes/accessors/PositionNode.js +++ b/examples/jsm/renderers/nodes/accessors/PositionNode.js @@ -62,6 +62,22 @@ class PositionNode extends Node { } + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + + } + } export default PositionNode; diff --git a/examples/jsm/renderers/nodes/accessors/UVNode.js b/examples/jsm/renderers/nodes/accessors/UVNode.js index b92f7d47724cf0..7af0a7be8e5cdc 100644 --- a/examples/jsm/renderers/nodes/accessors/UVNode.js +++ b/examples/jsm/renderers/nodes/accessors/UVNode.js @@ -18,6 +18,22 @@ class UVNode extends AttributeNode { } + serialize( data ) { + + super.serialize( data ); + + data.index = this.index; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.index = data.index; + + } + } UVNode.prototype.isUVNode = true; diff --git a/examples/jsm/renderers/nodes/core/Node.js b/examples/jsm/renderers/nodes/core/Node.js index ec10dab417c8ef..9b4b78ec3eab91 100644 --- a/examples/jsm/renderers/nodes/core/Node.js +++ b/examples/jsm/renderers/nodes/core/Node.js @@ -1,5 +1,5 @@ import { NodeUpdateType } from './constants.js'; - +import { getNodesKeys } from './NodeUtils.js'; import { MathUtils } from 'three'; class Node { @@ -97,6 +97,118 @@ class Node { } + serialize( json ) { + + const nodeKeys = getNodesKeys( this ); + + if ( nodeKeys.length > 0 ) { + + const inputNodes = {}; + + for ( const property of nodeKeys ) { + + inputNodes[ property ] = this[ property ].toJSON( json.meta ).uuid; + + } + + json.inputNodes = inputNodes; + + } + + } + + deserialize( json ) { + + if ( json.inputNodes !== undefined ) { + + const nodes = json.meta.nodes; + + for ( const property in json.inputNodes ) { + + const uuid = json.inputNodes[ property ]; + + this[ property ] = nodes[ uuid ]; + + } + + } + + } + + toJSON( meta ) { + + const { uuid, type } = this; + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + // serialize + + let data = meta.nodes[ uuid ]; + + if ( data === undefined ) { + + data = { + uuid, + type, + meta, + metadata: { + version: 4.5, + type: 'Node', + generator: 'Node.toJSON' + } + }; + + meta.nodes[ data.uuid ] = data; + + this.serialize( data ); + + delete data.meta; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + } Node.prototype.isNode = true; diff --git a/examples/jsm/renderers/nodes/core/NodeUtils.js b/examples/jsm/renderers/nodes/core/NodeUtils.js new file mode 100644 index 00000000000000..a45b12aa05f294 --- /dev/null +++ b/examples/jsm/renderers/nodes/core/NodeUtils.js @@ -0,0 +1,19 @@ +export const getNodesKeys = ( object ) => { + + const props = []; + + for ( const name in object ) { + + const value = object[ name ]; + + if ( value && value.isNode === true ) { + + props.push( name ); + + } + + } + + return props; + +}; diff --git a/examples/jsm/renderers/nodes/inputs/ColorNode.js b/examples/jsm/renderers/nodes/inputs/ColorNode.js index 9c40609345ed8e..144dd761f4c4c7 100644 --- a/examples/jsm/renderers/nodes/inputs/ColorNode.js +++ b/examples/jsm/renderers/nodes/inputs/ColorNode.js @@ -11,6 +11,31 @@ class ColorNode extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + const { r, g, b } = this.value; + + data.r = r; + data.g = g; + data.b = b; + + } + + deserialize( data ) { + + super.serialize( data ); + + const { r, g, b } = data; + const value = this.value; + + value.r = r; + value.g = g; + value.b = b; + + } + } ColorNode.prototype.isColorNode = true; diff --git a/examples/jsm/renderers/nodes/inputs/FloatNode.js b/examples/jsm/renderers/nodes/inputs/FloatNode.js index af7e252d83e412..db2b9c41a14309 100644 --- a/examples/jsm/renderers/nodes/inputs/FloatNode.js +++ b/examples/jsm/renderers/nodes/inputs/FloatNode.js @@ -10,6 +10,22 @@ class FloatNode extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + data.value = this.value; + + } + + deserialize( data ) { + + super.serialize( data ); + + data.value = this.value; + + } + } FloatNode.prototype.isFloatNode = true; diff --git a/examples/jsm/renderers/nodes/inputs/IntNode.js b/examples/jsm/renderers/nodes/inputs/IntNode.js index e0d1eb92a95c14..2ca5c6e453d518 100644 --- a/examples/jsm/renderers/nodes/inputs/IntNode.js +++ b/examples/jsm/renderers/nodes/inputs/IntNode.js @@ -10,6 +10,22 @@ class IntNode extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + data.value = this.value; + + } + + deserialize( data ) { + + super.serialize( data ); + + this.value = data.value; + + } + } IntNode.prototype.isIntNode = true; diff --git a/examples/jsm/renderers/nodes/inputs/TextureNode.js b/examples/jsm/renderers/nodes/inputs/TextureNode.js index 46e5cf863f3179..390dd587c26d26 100644 --- a/examples/jsm/renderers/nodes/inputs/TextureNode.js +++ b/examples/jsm/renderers/nodes/inputs/TextureNode.js @@ -66,6 +66,22 @@ class TextureNode extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + data.value = this.value.toJSON( data.meta ).uuid; + + } + + deserialize( data ) { + + super.serialize( data ); + + this.value = data.meta.textures[ data.value ]; + + } + } TextureNode.prototype.isTextureNode = true; diff --git a/examples/jsm/renderers/nodes/inputs/Vector2Node.js b/examples/jsm/renderers/nodes/inputs/Vector2Node.js index da8a46492e2e82..de41890773919f 100644 --- a/examples/jsm/renderers/nodes/inputs/Vector2Node.js +++ b/examples/jsm/renderers/nodes/inputs/Vector2Node.js @@ -11,6 +11,29 @@ class Vector2Node extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + const { x, y } = this.value; + + data.x = x; + data.y = y; + + } + + deserialize( data ) { + + super.serialize( data ); + + const { x, y } = data; + const value = this.value; + + value.x = x; + value.y = y; + + } + } Vector2Node.prototype.isVector2Node = true; diff --git a/examples/jsm/renderers/nodes/inputs/Vector3Node.js b/examples/jsm/renderers/nodes/inputs/Vector3Node.js index a87741e12756c7..546b372eb1f5f8 100644 --- a/examples/jsm/renderers/nodes/inputs/Vector3Node.js +++ b/examples/jsm/renderers/nodes/inputs/Vector3Node.js @@ -11,6 +11,31 @@ class Vector3Node extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + const { x, y, z } = this.value; + + data.x = x; + data.y = y; + data.z = z; + + } + + deserialize( data ) { + + super.serialize( data ); + + const { x, y, z } = data; + const value = this.value; + + value.x = x; + value.y = y; + value.z = z; + + } + } Vector3Node.prototype.isVector3Node = true; diff --git a/examples/jsm/renderers/nodes/inputs/Vector4Node.js b/examples/jsm/renderers/nodes/inputs/Vector4Node.js index c933bdaa57fbc6..a1223731a1691d 100644 --- a/examples/jsm/renderers/nodes/inputs/Vector4Node.js +++ b/examples/jsm/renderers/nodes/inputs/Vector4Node.js @@ -11,6 +11,33 @@ class Vector4Node extends InputNode { } + serialize( data ) { + + super.serialize( data ); + + const { x, y, z, w } = this.value; + + data.x = x; + data.y = y; + data.z = z; + data.w = w; + + } + + deserialize( data ) { + + super.serialize( data ); + + const { x, y, z, w } = data; + const value = this.value; + + value.x = x; + value.y = y; + value.z = z; + value.w = w; + + } + } Vector4Node.prototype.isVector4Node = true; diff --git a/examples/jsm/renderers/nodes/loaders/NodeLoader.js b/examples/jsm/renderers/nodes/loaders/NodeLoader.js new file mode 100644 index 00000000000000..34d1d4a0c6671c --- /dev/null +++ b/examples/jsm/renderers/nodes/loaders/NodeLoader.js @@ -0,0 +1,107 @@ +import * as Nodes from '../Nodes.js'; +import { Loader } from 'three'; + +class NodeLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.textures = {}; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, ( text ) => { + + try { + + onLoad( this.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parseNodes( json ) { + + const nodes = {}; + + if ( json !== undefined ) { + + for ( const nodeJSON of json ) { + + const { uuid, type } = nodeJSON; + + nodes[ uuid ] = Nodes.fromType( type ); + nodes[ uuid ].uuid = uuid; + + } + + const meta = { nodes, textures: this.textures }; + + for ( const nodeJSON of json ) { + + nodeJSON.meta = meta; + + const node = nodes[ nodeJSON.uuid ]; + node.deserialize( nodeJSON ); + + delete nodeJSON.meta; + + } + + } + + return nodes; + + } + + parse( json ) { + + const node = Nodes.fromType( type ); + node.uuid = json.uuid; + + const nodes = this.parseNodes( json.inputNodes ); + const meta = { nodes, textures: this.textures }; + + json.meta = meta; + + node.deserialize( json ); + + delete json.meta; + + return node; + + } + + setTextures( value ) { + + this.textures = value; + return this; + + } + +} + +export default NodeLoader; diff --git a/examples/jsm/renderers/nodes/loaders/NodeMaterialLoader.js b/examples/jsm/renderers/nodes/loaders/NodeMaterialLoader.js new file mode 100644 index 00000000000000..99728c82dabffc --- /dev/null +++ b/examples/jsm/renderers/nodes/loaders/NodeMaterialLoader.js @@ -0,0 +1,42 @@ +import { MaterialLoader } from 'three'; + +class NodeMaterialLoader extends MaterialLoader { + + constructor( manager ) { + + super( manager ); + + this.nodes = {}; + + } + + parse( json ) { + + const material = super.parse( json ); + + const nodes = this.nodes; + const inputNodes = json.inputNodes; + + for ( const property in inputNodes ) { + + const uuid = inputNodes[ property ]; + + material[ property ] = nodes[ uuid ]; + + } + + return material; + + } + + setNodes( value ) { + + this.nodes = value; + + return this; + + } + +} + +export default NodeMaterialLoader; diff --git a/examples/jsm/renderers/nodes/loaders/NodeObjectLoader.js b/examples/jsm/renderers/nodes/loaders/NodeObjectLoader.js new file mode 100644 index 00000000000000..c149b9948cfc4d --- /dev/null +++ b/examples/jsm/renderers/nodes/loaders/NodeObjectLoader.js @@ -0,0 +1,70 @@ +import NodeLoader from './NodeLoader.js'; +import NodeMaterialLoader from './NodeMaterialLoader.js'; +import { ObjectLoader } from 'three'; + +class NodeObjectLoader extends ObjectLoader { + + constructor( manager ) { + + super( manager ); + + this._nodesJSON = null; + + } + + parse( json, onLoad ) { + + this._nodesJSON = json.nodes; + + const data = super.parse( json, onLoad ); + + this._nodesJSON = null; // dispose + + return data; + + } + + parseNodes( json, textures ) { + + if ( json !== undefined ) { + + const loader = new NodeLoader(); + loader.setTextures( textures ); + + return loader.parseNodes( json ); + + } + + return {}; + + } + + parseMaterials( json, textures ) { + + const materials = {}; + + if ( json !== undefined ) { + + const nodes = this.parseNodes( this._nodesJSON, textures ); + + const loader = new NodeMaterialLoader(); + loader.setTextures( textures ); + loader.setNodes( nodes ); + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const data = json[ i ]; + + materials[ data.uuid ] = loader.parse( data ); + + } + + } + + return materials; + + } + +} + +export default NodeObjectLoader; diff --git a/examples/jsm/renderers/nodes/materials/LineBasicNodeMaterial.js b/examples/jsm/renderers/nodes/materials/LineBasicNodeMaterial.js index 06ba7b56cfef32..75765940c6b7dc 100644 --- a/examples/jsm/renderers/nodes/materials/LineBasicNodeMaterial.js +++ b/examples/jsm/renderers/nodes/materials/LineBasicNodeMaterial.js @@ -1,10 +1,13 @@ +import NodeMaterial from './NodeMaterial.js'; import { LineBasicMaterial } from 'three'; -class LineBasicNodeMaterial extends LineBasicMaterial { +const defaultValues = new LineBasicMaterial(); + +class LineBasicNodeMaterial extends NodeMaterial { constructor( parameters ) { - super( parameters ); + super(); this.colorNode = null; this.opacityNode = null; @@ -15,6 +18,10 @@ class LineBasicNodeMaterial extends LineBasicMaterial { this.positionNode = null; + this.setDefaultValues( defaultValues ); + + this.setValues( parameters ); + } copy( source ) { diff --git a/examples/jsm/renderers/nodes/materials/Materials.js b/examples/jsm/renderers/nodes/materials/Materials.js index 784cdf48deabd1..31b0aeecc4dd9a 100644 --- a/examples/jsm/renderers/nodes/materials/Materials.js +++ b/examples/jsm/renderers/nodes/materials/Materials.js @@ -2,6 +2,7 @@ import LineBasicNodeMaterial from './LineBasicNodeMaterial.js'; import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js'; import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js'; import PointsNodeMaterial from './PointsNodeMaterial.js'; +import { Material } from 'three'; export { LineBasicNodeMaterial, @@ -9,3 +10,24 @@ export { MeshStandardNodeMaterial, PointsNodeMaterial }; + +const materialLib = { + LineBasicNodeMaterial, + MeshBasicNodeMaterial, + MeshStandardNodeMaterial, + PointsNodeMaterial +}; + +const fromTypeFunction = Material.fromType; + +Material.fromType = function ( type ) { + + if ( materialLib[ type ] !== undefined ) { + + return new materialLib[ type ](); + + } + + return fromTypeFunction.call( this, type ); + +}; diff --git a/examples/jsm/renderers/nodes/materials/MeshBasicNodeMaterial.js b/examples/jsm/renderers/nodes/materials/MeshBasicNodeMaterial.js index d41eb9a3c36f50..cdc69886d38058 100644 --- a/examples/jsm/renderers/nodes/materials/MeshBasicNodeMaterial.js +++ b/examples/jsm/renderers/nodes/materials/MeshBasicNodeMaterial.js @@ -1,10 +1,13 @@ +import NodeMaterial from './NodeMaterial.js'; import { MeshBasicMaterial } from 'three'; -class MeshBasicNodeMaterial extends MeshBasicMaterial { +const defaultValues = new MeshBasicMaterial(); + +class MeshBasicNodeMaterial extends NodeMaterial { constructor( parameters ) { - super( parameters ); + super(); this.colorNode = null; this.opacityNode = null; @@ -15,6 +18,10 @@ class MeshBasicNodeMaterial extends MeshBasicMaterial { this.positionNode = null; + this.setDefaultValues( defaultValues ); + + this.setValues( parameters ); + } copy( source ) { diff --git a/examples/jsm/renderers/nodes/materials/MeshStandardNodeMaterial.js b/examples/jsm/renderers/nodes/materials/MeshStandardNodeMaterial.js index b2306cb7b042f2..dc2edeb3a61e7c 100644 --- a/examples/jsm/renderers/nodes/materials/MeshStandardNodeMaterial.js +++ b/examples/jsm/renderers/nodes/materials/MeshStandardNodeMaterial.js @@ -1,10 +1,13 @@ +import NodeMaterial from './NodeMaterial.js'; import { MeshStandardMaterial } from 'three'; -class MeshStandardNodeMaterial extends MeshStandardMaterial { +const defaultValues = new MeshStandardMaterial(); + +export default class MeshStandardNodeMaterial extends NodeMaterial { constructor( parameters ) { - super( parameters ); + super(); this.colorNode = null; this.opacityNode = null; @@ -27,6 +30,10 @@ class MeshStandardNodeMaterial extends MeshStandardMaterial { this.positionNode = null; + this.setDefaultValues( defaultValues ); + + this.setValues( parameters ); + } copy( source ) { @@ -59,5 +66,3 @@ class MeshStandardNodeMaterial extends MeshStandardMaterial { } MeshStandardNodeMaterial.prototype.isNodeMaterial = true; - -export default MeshStandardNodeMaterial; diff --git a/examples/jsm/renderers/nodes/materials/NodeMaterial.js b/examples/jsm/renderers/nodes/materials/NodeMaterial.js new file mode 100644 index 00000000000000..5e52a44fbf8669 --- /dev/null +++ b/examples/jsm/renderers/nodes/materials/NodeMaterial.js @@ -0,0 +1,98 @@ +import { Material, ShaderMaterial } from 'three'; +import { getNodesKeys } from '../core/NodeUtils.js'; + +class NodeMaterial extends ShaderMaterial { + + constructor() { + + super(); + + this.type = this.constructor.name; + + this.lights = true; + + } + + setDefaultValues( values ) { + + // This approach is to reuse the native refreshUniforms* + // and turn available the use of features like transmission and environment in core + + for ( const property in values ) { + + if ( this[ property ] === undefined ) { + + this[ property ] = values[ property ]; + + } + + } + + Object.assign( this.defines, values.defines ); + + } + + toJSON( meta ) { + + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + const data = Material.prototype.toJSON.call( this, meta ); + const nodeKeys = getNodesKeys( this ); + + data.inputNodes = {}; + + for ( const name of nodeKeys ) { + + data.inputNodes[ name ] = this[ name ].toJSON( meta ).uuid; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + +} + +NodeMaterial.prototype.isNodeMaterial = true; + +export default NodeMaterial; diff --git a/examples/jsm/renderers/nodes/materials/PointsNodeMaterial.js b/examples/jsm/renderers/nodes/materials/PointsNodeMaterial.js index 2ad5aa887f3979..1acb52a9fa2ea5 100644 --- a/examples/jsm/renderers/nodes/materials/PointsNodeMaterial.js +++ b/examples/jsm/renderers/nodes/materials/PointsNodeMaterial.js @@ -1,10 +1,13 @@ +import NodeMaterial from './NodeMaterial.js'; import { PointsMaterial } from 'three'; -class PointsNodeMaterial extends PointsMaterial { +const defaultValues = new PointsMaterial(); + +class PointsNodeMaterial extends NodeMaterial { constructor( parameters ) { - super( parameters ); + super(); this.colorNode = null; this.opacityNode = null; @@ -17,6 +20,10 @@ class PointsNodeMaterial extends PointsMaterial { this.positionNode = null; + this.setDefaultValues( defaultValues ); + + this.setValues( parameters ); + } copy( source ) { diff --git a/examples/jsm/renderers/nodes/math/MathNode.js b/examples/jsm/renderers/nodes/math/MathNode.js index 08c45208415827..fb709b3d432e89 100644 --- a/examples/jsm/renderers/nodes/math/MathNode.js +++ b/examples/jsm/renderers/nodes/math/MathNode.js @@ -237,6 +237,22 @@ class MathNode extends TempNode { } + serialize( data ) { + + super.serialize( data ); + + data.method = this.method; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.method = data.method; + + } + } export default MathNode; diff --git a/examples/jsm/renderers/nodes/math/OperatorNode.js b/examples/jsm/renderers/nodes/math/OperatorNode.js index 3e980d0c5fe4e6..44bcbe867f8769 100644 --- a/examples/jsm/renderers/nodes/math/OperatorNode.js +++ b/examples/jsm/renderers/nodes/math/OperatorNode.js @@ -169,6 +169,22 @@ class OperatorNode extends TempNode { } + serialize( data ) { + + super.serialize( data ); + + data.op = this.op; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.op = data.op; + + } + } export default OperatorNode; diff --git a/examples/jsm/renderers/nodes/utils/OscNode.js b/examples/jsm/renderers/nodes/utils/OscNode.js index 94c60a0b6bfbd0..9f85fc40b6143d 100644 --- a/examples/jsm/renderers/nodes/utils/OscNode.js +++ b/examples/jsm/renderers/nodes/utils/OscNode.js @@ -53,6 +53,22 @@ class OscNode extends Node { } + serialize( data ) { + + super.serialize( data ); + + data.method = this.method; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.method = data.method; + + } + } export default OscNode; diff --git a/examples/jsm/renderers/nodes/utils/SplitNode.js b/examples/jsm/renderers/nodes/utils/SplitNode.js index 50b4a9c0050abd..1784f786ee3cce 100644 --- a/examples/jsm/renderers/nodes/utils/SplitNode.js +++ b/examples/jsm/renderers/nodes/utils/SplitNode.js @@ -65,6 +65,22 @@ class SplitNode extends Node { } + serialize( data ) { + + super.serialize( data ); + + data.components = this.components; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.components = data.components; + + } + } export default SplitNode; diff --git a/examples/jsm/renderers/nodes/utils/TimerNode.js b/examples/jsm/renderers/nodes/utils/TimerNode.js index 0cb458716d2ec2..772b0ff19ab539 100644 --- a/examples/jsm/renderers/nodes/utils/TimerNode.js +++ b/examples/jsm/renderers/nodes/utils/TimerNode.js @@ -12,7 +12,6 @@ class TimerNode extends FloatNode { super(); this.scope = scope; - this.scale = 1; this.updateType = NodeUpdateType.Frame; @@ -42,6 +41,24 @@ class TimerNode extends FloatNode { } + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + data.scale = this.scale; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + this.scale = data.scale; + + } + } export default TimerNode; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index 94dd597eb1b951..63c678b7b5d1df 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -3,10 +3,18 @@ import SlotNode from './SlotNode.js'; import GLSLNodeParser from '../../nodes/parsers/GLSLNodeParser.js'; import WebGLPhysicalContextNode from './WebGLPhysicalContextNode.js'; -import { ShaderChunk, LinearEncoding, RGBAFormat, UnsignedByteType, sRGBEncoding } from 'three'; +import { ShaderChunk, ShaderLib, UniformsUtils, UniformsLib, + LinearEncoding, RGBAFormat, UnsignedByteType, sRGBEncoding } from 'three'; const shaderStages = [ 'vertex', 'fragment' ]; +const nodeShaderLib = { + LineBasicNodeMaterial: ShaderLib.basic, + MeshBasicNodeMaterial: ShaderLib.basic, + PointsNodeMaterial: ShaderLib.points, + MeshStandardNodeMaterial: ShaderLib.standard +}; + function getIncludeSnippet( name ) { return `#include <${name}>`; @@ -55,6 +63,20 @@ class WebGLNodeBuilder extends NodeBuilder { _parseObject() { const material = this.material; + const type = material.type; + + // shader lib + + if ( nodeShaderLib[ type ] !== undefined ) { + + const shaderLib = nodeShaderLib[ type ]; + const shader = this.shader; + + shader.vertexShader = shaderLib.vertexShader; + shader.fragmentShader = shaderLib.fragmentShader; + shader.uniforms = UniformsUtils.merge( [ shaderLib.uniforms, UniformsLib.lights ] ); + + } // parse inputs diff --git a/examples/webgl_materials_standard_nodes.html b/examples/webgl_materials_standard_nodes.html index 5b4e8df35ec710..10196a5291588d 100644 --- a/examples/webgl_materials_standard_nodes.html +++ b/examples/webgl_materials_standard_nodes.html @@ -82,7 +82,9 @@ .setPath( 'models/obj/cerberus/' ) .load( 'Cerberus.obj', function ( group ) { - const loader = new THREE.TextureLoader() + const loaderManager = new THREE.LoadingManager(); + + const loader = new THREE.TextureLoader( loaderManager ) .setPath( 'models/obj/cerberus/' ); const diffuseMap = loader.load( 'Cerberus_A.jpg' ); @@ -97,7 +99,7 @@ const mpMapNode = new Nodes.TextureNode( rmMap ); - material.colorNode = new Nodes.OperatorNode( '*', new Nodes.TextureNode( diffuseMap ), new Nodes.Vector3Node( material.color ) ); + material.colorNode = new Nodes.OperatorNode( '*', new Nodes.TextureNode( diffuseMap ), new Nodes.ColorNode( material.color ) ); // roughness is in G channel, metalness is in B channel material.roughnessNode = new Nodes.SplitNode( mpMapNode, 'g' ); @@ -117,7 +119,29 @@ group.position.x = - 0.45; group.rotation.y = - Math.PI / 2; - scene.add( group ); + //scene.add( group ); + + // TODO: Serialization test + + loaderManager.onLoad = () => { + + const groupJSON = JSON.stringify( group.toJSON() ); + + const objectLoader = new Nodes.NodeObjectLoader(); + objectLoader.parse( JSON.parse( groupJSON ), ( newGroup ) => { + + //scene.remove( group ); + + newGroup.position.copy( group.position ); + newGroup.rotation.copy( group.rotation ); + + scene.add( newGroup ); + + console.log( 'Serialized!' ); + + } ); + + }; } ); diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 25428e8c4bc1fb..c315bbda6ab33e 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -646,7 +646,8 @@ class Object3D extends EventDispatcher { images: {}, shapes: {}, skeletons: {}, - animations: {} + animations: {}, + nodes: {} }; output.metadata = { @@ -830,6 +831,7 @@ class Object3D extends EventDispatcher { const shapes = extractFromCache( meta.shapes ); const skeletons = extractFromCache( meta.skeletons ); const animations = extractFromCache( meta.animations ); + const nodes = extractFromCache( meta.nodes ); if ( geometries.length > 0 ) output.geometries = geometries; if ( materials.length > 0 ) output.materials = materials; @@ -838,6 +840,7 @@ class Object3D extends EventDispatcher { if ( shapes.length > 0 ) output.shapes = shapes; if ( skeletons.length > 0 ) output.skeletons = skeletons; if ( animations.length > 0 ) output.animations = animations; + if ( nodes.length > 0 ) output.nodes = nodes; } diff --git a/src/loaders/MaterialLoader.js b/src/loaders/MaterialLoader.js index 97571fde5280dd..b53c16401fb4f8 100644 --- a/src/loaders/MaterialLoader.js +++ b/src/loaders/MaterialLoader.js @@ -6,7 +6,7 @@ import { Matrix3 } from '../math/Matrix3.js'; import { Matrix4 } from '../math/Matrix4.js'; import { FileLoader } from './FileLoader.js'; import { Loader } from './Loader.js'; -import * as Materials from '../materials/Materials.js'; +import { Material } from '../materials/Material.js'; class MaterialLoader extends Loader { @@ -67,7 +67,7 @@ class MaterialLoader extends Loader { } - const material = new Materials[ json.type ](); + const material = Material.fromType( json.type ); if ( json.uuid !== undefined ) material.uuid = json.uuid; if ( json.name !== undefined ) material.name = json.name; diff --git a/src/materials/Material.js b/src/materials/Material.js index c46b12ba4b435c..c2ed1fe02204ea 100644 --- a/src/materials/Material.js +++ b/src/materials/Material.js @@ -485,4 +485,12 @@ class Material extends EventDispatcher { Material.prototype.isMaterial = true; +Material.fromType = function ( /*type*/ ) { + + // TODO: Behavior added in Materials.js + + return null; + +}; + export { Material }; diff --git a/src/materials/Materials.js b/src/materials/Materials.js index 28844e95884fc5..c3d39fc4c867f4 100644 --- a/src/materials/Materials.js +++ b/src/materials/Materials.js @@ -1,18 +1,66 @@ -export { ShadowMaterial } from './ShadowMaterial.js'; -export { SpriteMaterial } from './SpriteMaterial.js'; -export { RawShaderMaterial } from './RawShaderMaterial.js'; -export { ShaderMaterial } from './ShaderMaterial.js'; -export { PointsMaterial } from './PointsMaterial.js'; -export { MeshPhysicalMaterial } from './MeshPhysicalMaterial.js'; -export { MeshStandardMaterial } from './MeshStandardMaterial.js'; -export { MeshPhongMaterial } from './MeshPhongMaterial.js'; -export { MeshToonMaterial } from './MeshToonMaterial.js'; -export { MeshNormalMaterial } from './MeshNormalMaterial.js'; -export { MeshLambertMaterial } from './MeshLambertMaterial.js'; -export { MeshDepthMaterial } from './MeshDepthMaterial.js'; -export { MeshDistanceMaterial } from './MeshDistanceMaterial.js'; -export { MeshBasicMaterial } from './MeshBasicMaterial.js'; -export { MeshMatcapMaterial } from './MeshMatcapMaterial.js'; -export { LineDashedMaterial } from './LineDashedMaterial.js'; -export { LineBasicMaterial } from './LineBasicMaterial.js'; -export { Material } from './Material.js'; +import { ShadowMaterial } from './ShadowMaterial.js'; +import { SpriteMaterial } from './SpriteMaterial.js'; +import { RawShaderMaterial } from './RawShaderMaterial.js'; +import { ShaderMaterial } from './ShaderMaterial.js'; +import { PointsMaterial } from './PointsMaterial.js'; +import { MeshPhysicalMaterial } from './MeshPhysicalMaterial.js'; +import { MeshStandardMaterial } from './MeshStandardMaterial.js'; +import { MeshPhongMaterial } from './MeshPhongMaterial.js'; +import { MeshToonMaterial } from './MeshToonMaterial.js'; +import { MeshNormalMaterial } from './MeshNormalMaterial.js'; +import { MeshLambertMaterial } from './MeshLambertMaterial.js'; +import { MeshDepthMaterial } from './MeshDepthMaterial.js'; +import { MeshDistanceMaterial } from './MeshDistanceMaterial.js'; +import { MeshBasicMaterial } from './MeshBasicMaterial.js'; +import { MeshMatcapMaterial } from './MeshMatcapMaterial.js'; +import { LineDashedMaterial } from './LineDashedMaterial.js'; +import { LineBasicMaterial } from './LineBasicMaterial.js'; +import { Material } from './Material.js'; + +export { + ShadowMaterial, + SpriteMaterial, + RawShaderMaterial, + ShaderMaterial, + PointsMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MeshPhongMaterial, + MeshToonMaterial, + MeshNormalMaterial, + MeshLambertMaterial, + MeshDepthMaterial, + MeshDistanceMaterial, + MeshBasicMaterial, + MeshMatcapMaterial, + LineDashedMaterial, + LineBasicMaterial, + Material +}; + +const materialLib = { + ShadowMaterial, + SpriteMaterial, + RawShaderMaterial, + ShaderMaterial, + PointsMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MeshPhongMaterial, + MeshToonMaterial, + MeshNormalMaterial, + MeshLambertMaterial, + MeshDepthMaterial, + MeshDistanceMaterial, + MeshBasicMaterial, + MeshMatcapMaterial, + LineDashedMaterial, + LineBasicMaterial, + Material +}; + +Material.fromType = function ( type ) { + + return new materialLib[ type ](); + +};