Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
// core
import ArrayInputNode from './core/ArrayInputNode.js';
import AttributeNode from './core/AttributeNode.js';
import BypassNode from './core/BypassNode.js';
import CodeNode from './core/CodeNode.js';
import ContextNode from './core/ContextNode.js';
import ExpressionNode from './core/ExpressionNode.js';
import FunctionCallNode from './core/FunctionCallNode.js';
import FunctionNode from './core/FunctionNode.js';
import InputNode from './core/InputNode.js';
import Node from './core/Node.js';
import NodeAttribute from './core/NodeAttribute.js';
import NodeBuilder from './core/NodeBuilder.js';
import NodeCode from './core/NodeCode.js';
import NodeFrame from './core/NodeFrame.js';
import NodeFunctionInput from './core/NodeFunctionInput.js';
import NodeKeywords from './core/NodeKeywords.js';
import NodeUniform from './core/NodeUniform.js';
import NodeVar from './core/NodeVar.js';
import NodeVary from './core/NodeVary.js';
import PropertyNode from './core/PropertyNode.js';
import TempNode from './core/TempNode.js';
import VarNode from './core/VarNode.js';
import VaryNode from './core/VaryNode.js';
// accessors
import CameraNode from './accessors/CameraNode.js';
import MaterialNode from './accessors/MaterialNode.js';
import MaterialReferenceNode from './accessors/MaterialReferenceNode.js';
import ModelNode from './accessors/ModelNode.js';
import ModelViewProjectionNode from './accessors/ModelViewProjectionNode.js';
import NormalNode from './accessors/NormalNode.js';
import Object3DNode from './accessors/Object3DNode.js';
import PointUVNode from './accessors/PointUVNode.js';
import PositionNode from './accessors/PositionNode.js';
import ReferenceNode from './accessors/ReferenceNode.js';
import SkinningNode from './accessors/SkinningNode.js';
import UVNode from './accessors/UVNode.js';
// inputs
import ColorNode from './inputs/ColorNode.js';
import FloatNode from './inputs/FloatNode.js';
import IntNode from './inputs/IntNode.js';
import Matrix3Node from './inputs/Matrix3Node.js';
import Matrix4Node from './inputs/Matrix3Node.js';
import TextureNode from './inputs/TextureNode.js';
import Vector2Node from './inputs/Vector2Node.js';
import Vector3Node from './inputs/Vector3Node.js';
import Vector4Node from './inputs/Vector4Node.js';
// display
import ColorSpaceNode from './display/ColorSpaceNode.js';
import NormalMapNode from './display/NormalMapNode.js';
// math
import MathNode from './math/MathNode.js';
import OperatorNode from './math/OperatorNode.js';
import CondNode from './math/CondNode.js';
// lights
import LightContextNode from './lights/LightContextNode.js';
import LightNode from './lights/LightNode.js';
import LightsNode from './lights/LightsNode.js';
// utils
import ArrayElementNode from './utils/ArrayElementNode.js';
import ConvertNode from './utils/ConvertNode.js';
import JoinNode from './utils/JoinNode.js';
import SplitNode from './utils/SplitNode.js';
import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js';
import OscNode from './utils/OscNode.js';
import TimerNode from './utils/TimerNode.js';
// 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';
// core
export * from './core/constants.js';
// functions
export * from './functions/BSDFs.js';
// materials
export * from './materials/Materials.js';
// shader node
export * from './ShaderNode.js';
const nodeLib = {
// 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
};
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
};

View File

@@ -0,0 +1,408 @@
// core
import PropertyNode from './core/PropertyNode.js';
import VarNode from './core/VarNode.js';
// inputs
import ColorNode from './inputs/ColorNode.js';
import FloatNode from './inputs/FloatNode.js';
import IntNode from './inputs/IntNode.js';
import Vector2Node from './inputs/Vector2Node.js';
import Vector3Node from './inputs/Vector3Node.js';
import Vector4Node from './inputs/Vector4Node.js';
// accessors
import PositionNode from './accessors/PositionNode.js';
import NormalNode from './accessors/NormalNode.js';
// math
import OperatorNode from './math/OperatorNode.js';
import CondNode from './math/CondNode.js';
import MathNode from './math/MathNode.js';
// utils
import ArrayElementNode from './utils/ArrayElementNode.js';
import ConvertNode from './utils/ConvertNode.js';
import JoinNode from './utils/JoinNode.js';
import SplitNode from './utils/SplitNode.js';
// core
import { Vector2, Vector3, Vector4, Color } from 'three';
const NodeHandler = {
construct( NodeClosure, params ) {
const inputs = params.shift();
return NodeClosure( ShaderNodeObjects( inputs ), ...params );
},
get: function ( node, prop ) {
if ( typeof prop === 'string' && node[ prop ] === undefined ) {
if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) {
// accessing properties ( swizzle )
prop = prop
.replace( /r|s/g, 'x' )
.replace( /g|t/g, 'y' )
.replace( /b|p/g, 'z' )
.replace( /a|q/g, 'w' );
return ShaderNodeObject( new SplitNode( node, prop ) );
} else if ( /^\d+$/.test( prop ) === true ) {
// accessing array
return ShaderNodeObject( new ArrayElementNode( node, new FloatNode( Number( prop ) ).setConst( true ) ) );
}
}
return node[ prop ];
}
};
const nodeObjects = new WeakMap();
const ShaderNodeObject = ( obj ) => {
const type = typeof obj;
if ( type === 'number' ) {
return ShaderNodeObject( new FloatNode( obj ).setConst( true ) );
} else if ( type === 'object' ) {
if ( obj.isNode === true ) {
let nodeObject = nodeObjects.get( obj );
if ( nodeObject === undefined ) {
nodeObject = new Proxy( obj, NodeHandler );
nodeObjects.set( obj, nodeObject );
}
return nodeObject;
}
}
return obj;
};
const ShaderNodeObjects = ( objects ) => {
for ( const name in objects ) {
objects[ name ] = ShaderNodeObject( objects[ name ] );
}
return objects;
};
const ShaderNodeArray = ( array ) => {
const len = array.length;
for ( let i = 0; i < len; i ++ ) {
array[ i ] = ShaderNodeObject( array[ i ] );
}
return array;
};
const ShaderNodeProxy = ( NodeClass, scope = null, factor = null ) => {
if ( scope === null ) {
return ( ...params ) => {
return ShaderNodeObject( new NodeClass( ...ShaderNodeArray( params ) ) );
};
} else if ( factor === null ) {
return ( ...params ) => {
return ShaderNodeObject( new NodeClass( scope, ...ShaderNodeArray( params ) ) );
};
} else {
factor = ShaderNodeObject( factor );
return ( ...params ) => {
return ShaderNodeObject( new NodeClass( scope, ...ShaderNodeArray( params ), factor ) );
};
}
};
const ShaderNodeScript = function ( jsFunc ) {
return ( inputs, builder ) => {
ShaderNodeObjects( inputs );
return ShaderNodeObject( jsFunc( inputs, builder ) );
};
};
export const ShaderNode = new Proxy( ShaderNodeScript, NodeHandler );
//
// Node Material Shader Syntax
//
export const uniform = new ShaderNode( ( inputNode ) => {
inputNode.setConst( false );
return inputNode;
} );
export const nodeObject = ( val ) => {
return ShaderNodeObject( val );
};
export const float = ( val ) => {
if ( val?.isNode === true ) {
return nodeObject( new ConvertNode( val, 'float' ) );
}
return nodeObject( new FloatNode( val ).setConst( true ) );
};
export const int = ( val ) => {
if ( val?.isNode === true ) {
return nodeObject( new ConvertNode( val, 'int' ) );
}
return nodeObject( new IntNode( val ).setConst( true ) );
};
export const color = ( ...params ) => {
if ( params[ 0 ]?.isNode === true ) {
return nodeObject( new ConvertNode( params[0], 'color' ) );
}
return nodeObject( new ColorNode( new Color( ...params ) ).setConst( true ) );
};
export const join = ( ...params ) => {
return nodeObject( new JoinNode( ShaderNodeArray( params ) ) );
};
export const cond = ( ...params ) => {
return nodeObject( new CondNode( ...ShaderNodeArray( params ) ) );
};
export const vec2 = ( ...params ) => {
if ( params[ 0 ]?.isNode === true ) {
return nodeObject( new ConvertNode( params[ 0 ], 'vec2' ) );
} else {
// Providing one scalar value: This value is used for all components
if ( params.length === 1 ) {
params[ 1 ] = params[ 0 ];
}
return nodeObject( new Vector2Node( new Vector2( ...params ) ).setConst( true ) );
}
};
export const vec3 = ( ...params ) => {
if ( params[ 0 ]?.isNode === true ) {
return nodeObject( new ConvertNode( params[ 0 ], 'vec3' ) );
} else {
// Providing one scalar value: This value is used for all components
if ( params.length === 1 ) {
params[ 1 ] = params[ 2 ] = params[ 0 ];
}
return nodeObject( new Vector3Node( new Vector3( ...params ) ).setConst( true ) );
}
};
export const vec4 = ( ...params ) => {
if ( params[ 0 ]?.isNode === true ) {
return nodeObject( new ConvertNode( params[ 0 ], 'vec4' ) );
} else {
// Providing one scalar value: This value is used for all components
if ( params.length === 1 ) {
params[ 1 ] = params[ 2 ] = params[ 3 ] = params[ 0 ];
}
return nodeObject( new Vector4Node( new Vector4( ...params ) ).setConst( true ) );
}
};
export const addTo = ( varNode, ...params ) => {
varNode.node = add( varNode.node, ...ShaderNodeArray( params ) );
return nodeObject( varNode );
};
export const add = ShaderNodeProxy( OperatorNode, '+' );
export const sub = ShaderNodeProxy( OperatorNode, '-' );
export const mul = ShaderNodeProxy( OperatorNode, '*' );
export const div = ShaderNodeProxy( OperatorNode, '/' );
export const remainder = ShaderNodeProxy( OperatorNode, '%' );
export const equal = ShaderNodeProxy( OperatorNode, '==' );
export const assign = ShaderNodeProxy( OperatorNode, '=' );
export const lessThan = ShaderNodeProxy( OperatorNode, '<' );
export const greaterThan = ShaderNodeProxy( OperatorNode, '>' );
export const lessThanEqual = ShaderNodeProxy( OperatorNode, '<=' );
export const greaterThanEqual = ShaderNodeProxy( OperatorNode, '>=' );
export const and = ShaderNodeProxy( OperatorNode, '&&' );
export const or = ShaderNodeProxy( OperatorNode, '||' );
export const xor = ShaderNodeProxy( OperatorNode, '^^' );
export const bitAnd = ShaderNodeProxy( OperatorNode, '&' );
export const bitOr = ShaderNodeProxy( OperatorNode, '|' );
export const bitXor = ShaderNodeProxy( OperatorNode, '^' );
export const shiftLeft = ShaderNodeProxy( OperatorNode, '<<' );
export const shiftRight = ShaderNodeProxy( OperatorNode, '>>' );
export const element = ShaderNodeProxy( ArrayElementNode );
export const normalGeometry = ShaderNodeObject( new NormalNode( NormalNode.GEOMETRY ) );
export const normalLocal = ShaderNodeObject( new NormalNode( NormalNode.LOCAL ) );
export const normalWorld = ShaderNodeObject( new NormalNode( NormalNode.WORLD ) );
export const normalView = ShaderNodeObject( new NormalNode( NormalNode.VIEW ) );
export const transformedNormalView = ShaderNodeObject( new VarNode( new NormalNode( NormalNode.VIEW ), 'TransformedNormalView', 'vec3' ) );
export const positionLocal = ShaderNodeObject( new PositionNode( PositionNode.LOCAL ) );
export const positionWorld = ShaderNodeObject( new PositionNode( PositionNode.WORLD ) );
export const positionView = ShaderNodeObject( new PositionNode( PositionNode.VIEW ) );
export const positionViewDirection = ShaderNodeObject( new PositionNode( PositionNode.VIEW_DIRECTION ) );
export const PI = float( 3.141592653589793 );
export const PI2 = float( 6.283185307179586 );
export const PI_HALF = float( 1.5707963267948966 );
export const RECIPROCAL_PI = float( 0.3183098861837907 );
export const RECIPROCAL_PI2 = float( 0.15915494309189535 );
export const EPSILON = float( 1e-6 );
export const diffuseColor = ShaderNodeObject( new PropertyNode( 'DiffuseColor', 'vec4' ) );
export const roughness = ShaderNodeObject( new PropertyNode( 'Roughness', 'float' ) );
export const metalness = ShaderNodeObject( new PropertyNode( 'Metalness', 'float' ) );
export const alphaTest = ShaderNodeObject( new PropertyNode( 'AlphaTest', 'float' ) );
export const specularColor = ShaderNodeObject( new PropertyNode( 'SpecularColor', 'color' ) );
export const abs = ShaderNodeProxy( MathNode, 'abs' );
export const acos = ShaderNodeProxy( MathNode, 'acos' );
export const asin = ShaderNodeProxy( MathNode, 'asin' );
export const atan = ShaderNodeProxy( MathNode, 'atan' );
export const ceil = ShaderNodeProxy( MathNode, 'ceil' );
export const clamp = ShaderNodeProxy( MathNode, 'clamp' );
export const cos = ShaderNodeProxy( MathNode, 'cos' );
export const cross = ShaderNodeProxy( MathNode, 'cross' );
export const degrees = ShaderNodeProxy( MathNode, 'degrees' );
export const dFdx = ShaderNodeProxy( MathNode, 'dFdx' );
export const dFdy = ShaderNodeProxy( MathNode, 'dFdy' );
export const distance = ShaderNodeProxy( MathNode, 'distance' );
export const dot = ShaderNodeProxy( MathNode, 'dot' );
export const exp = ShaderNodeProxy( MathNode, 'exp' );
export const exp2 = ShaderNodeProxy( MathNode, 'exp2' );
export const faceforward = ShaderNodeProxy( MathNode, 'faceforward' );
export const floor = ShaderNodeProxy( MathNode, 'floor' );
export const fract = ShaderNodeProxy( MathNode, 'fract' );
export const invert = ShaderNodeProxy( MathNode, 'invert' );
export const inversesqrt = ShaderNodeProxy( MathNode, 'inversesqrt' );
export const length = ShaderNodeProxy( MathNode, 'length' );
export const log = ShaderNodeProxy( MathNode, 'log' );
export const log2 = ShaderNodeProxy( MathNode, 'log2' );
export const max = ShaderNodeProxy( MathNode, 'max' );
export const min = ShaderNodeProxy( MathNode, 'min' );
export const mix = ShaderNodeProxy( MathNode, 'mix' );
export const mod = ShaderNodeProxy( MathNode, 'mod' );
export const negate = ShaderNodeProxy( MathNode, 'negate' );
export const normalize = ShaderNodeProxy( MathNode, 'normalize' );
export const pow = ShaderNodeProxy( MathNode, 'pow' );
export const pow2 = ShaderNodeProxy( MathNode, 'pow', 2 );
export const pow3 = ShaderNodeProxy( MathNode, 'pow', 3 );
export const pow4 = ShaderNodeProxy( MathNode, 'pow', 4 );
export const radians = ShaderNodeProxy( MathNode, 'radians' );
export const reflect = ShaderNodeProxy( MathNode, 'reflect' );
export const refract = ShaderNodeProxy( MathNode, 'refract' );
export const round = ShaderNodeProxy( MathNode, 'round' );
export const saturate = ShaderNodeProxy( MathNode, 'saturate' );
export const sign = ShaderNodeProxy( MathNode, 'sign' );
export const sin = ShaderNodeProxy( MathNode, 'sin' );
export const smoothstep = ShaderNodeProxy( MathNode, 'smoothstep' );
export const sqrt = ShaderNodeProxy( MathNode, 'sqrt' );
export const step = ShaderNodeProxy( MathNode, 'step' );
export const tan = ShaderNodeProxy( MathNode, 'tan' );
export const transformDirection = ShaderNodeProxy( MathNode, 'transformDirection' );

View File

@@ -0,0 +1,68 @@
import Object3DNode from './Object3DNode.js';
import Matrix4Node from '../inputs/Matrix4Node.js';
class CameraNode extends Object3DNode {
static PROJECTION_MATRIX = 'projectionMatrix';
constructor( scope = CameraNode.POSITION ) {
super( scope );
this._inputNode = null;
}
getNodeType( builder ) {
const scope = this.scope;
if ( scope === CameraNode.PROJECTION_MATRIX ) {
return 'mat4';
}
return super.getNodeType( builder );
}
update( frame ) {
const camera = frame.camera;
const inputNode = this._inputNode;
const scope = this.scope;
if ( scope === CameraNode.PROJECTION_MATRIX ) {
inputNode.value = camera.projectionMatrix;
} else if ( scope === CameraNode.VIEW_MATRIX ) {
inputNode.value = camera.matrixWorldInverse;
} else {
super.update( frame );
}
}
generate( builder ) {
const scope = this.scope;
if ( scope === CameraNode.PROJECTION_MATRIX ) {
this._inputNode = new Matrix4Node( null );
}
return super.generate( builder );
}
}
export default CameraNode;

View File

@@ -0,0 +1,114 @@
import Node from '../core/Node.js';
import OperatorNode from '../math/OperatorNode.js';
import MaterialReferenceNode from './MaterialReferenceNode.js';
class MaterialNode extends Node {
static ALPHA_TEST = 'alphaTest';
static COLOR = 'color';
static OPACITY = 'opacity';
static SPECULAR = 'specular';
static ROUGHNESS = 'roughness';
static METALNESS = 'metalness';
constructor( scope = MaterialNode.COLOR ) {
super();
this.scope = scope;
}
getNodeType( builder ) {
const scope = this.scope;
const material = builder.context.material;
if ( scope === MaterialNode.COLOR ) {
return material.map !== null ? 'vec4' : 'vec3';
} else if ( scope === MaterialNode.OPACITY ) {
return 'float';
} else if ( scope === MaterialNode.SPECULAR ) {
return 'vec3';
} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS ) {
return 'float';
}
}
generate( builder, output ) {
const material = builder.context.material;
const scope = this.scope;
let node = null;
if ( scope === MaterialNode.ALPHA_TEST ) {
node = new MaterialReferenceNode( 'alphaTest', 'float' );
} else if ( scope === MaterialNode.COLOR ) {
const colorNode = new MaterialReferenceNode( 'color', 'color' );
if ( material.map !== null && material.map !== undefined && material.map.isTexture === true ) {
node = new OperatorNode( '*', colorNode, new MaterialReferenceNode( 'map', 'texture' ) );
} else {
node = colorNode;
}
} else if ( scope === MaterialNode.OPACITY ) {
const opacityNode = new MaterialReferenceNode( 'opacity', 'float' );
if ( material.alphaMap !== null && material.alphaMap !== undefined && material.alphaMap.isTexture === true ) {
node = new OperatorNode( '*', opacityNode, new MaterialReferenceNode( 'alphaMap', 'texture' ) );
} else {
node = opacityNode;
}
} else if ( scope === MaterialNode.SPECULAR ) {
const specularColorNode = new MaterialReferenceNode( 'specularColor', 'color' );
if ( material.specularColorMap !== null && material.specularColorMap !== undefined && material.specularColorMap.isTexture === true ) {
node = new OperatorNode( '*', specularColorNode, new MaterialReferenceNode( 'specularColorMap', 'texture' ) );
} else {
node = specularColorNode;
}
} else {
const outputType = this.getNodeType( builder );
node = new MaterialReferenceNode( scope, outputType );
}
return node.build( builder, output );
}
}
export default MaterialNode;

View File

@@ -0,0 +1,23 @@
import ReferenceNode from './ReferenceNode.js';
class MaterialReferenceNode extends ReferenceNode {
constructor( property, inputType, material = null ) {
super( property, inputType, material );
this.material = material;
}
update( frame ) {
this.object = this.material !== null ? this.material : frame.material;
super.update( frame );
}
}
export default MaterialReferenceNode;

View File

@@ -0,0 +1,13 @@
import Object3DNode from './Object3DNode.js';
class ModelNode extends Object3DNode {
constructor( scope = ModelNode.VIEW_MATRIX ) {
super( scope );
}
}
export default ModelNode;

View File

@@ -0,0 +1,30 @@
import Node from '../core/Node.js';
import CameraNode from '../accessors/CameraNode.js';
import ModelNode from '../accessors/ModelNode.js';
import OperatorNode from '../math/OperatorNode.js';
import PositionNode from '../accessors/PositionNode.js';
class ModelViewProjectionNode extends Node {
constructor( position = new PositionNode() ) {
super( 'vec4' );
this.position = position;
}
generate( builder ) {
const position = this.position;
const mvpMatrix = new OperatorNode( '*', new CameraNode( CameraNode.PROJECTION_MATRIX ), new ModelNode( ModelNode.VIEW_MATRIX ) );
const mvpNode = new OperatorNode( '*', mvpMatrix, position );
return mvpNode.build( builder );
}
}
export default ModelViewProjectionNode;

View File

@@ -0,0 +1,79 @@
import Node from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import VaryNode from '../core/VaryNode.js';
import ModelNode from '../accessors/ModelNode.js';
import CameraNode from '../accessors/CameraNode.js';
import OperatorNode from '../math/OperatorNode.js';
import MathNode from '../math/MathNode.js';
class NormalNode extends Node {
static GEOMETRY = 'geometry';
static LOCAL = 'local';
static WORLD = 'world';
static VIEW = 'view';
constructor( scope = NormalNode.LOCAL ) {
super( 'vec3' );
this.scope = scope;
}
getHash( /*builder*/ ) {
return `normal-${this.scope}`;
}
generate( builder ) {
const scope = this.scope;
let outputNode = null;
if ( scope === NormalNode.GEOMETRY ) {
outputNode = new AttributeNode( 'normal', 'vec3' );
} else if ( scope === NormalNode.LOCAL ) {
outputNode = new VaryNode( new NormalNode( NormalNode.GEOMETRY ) );
} else if ( scope === NormalNode.VIEW ) {
const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), new NormalNode( NormalNode.LOCAL ) );
outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) );
} else if ( scope === NormalNode.WORLD ) {
// To use INVERSE_TRANSFORM_DIRECTION only inverse the param order like this: MathNode( ..., Vector, Matrix );
const vertexNormalNode = new MathNode( MathNode.TRANSFORM_DIRECTION, new NormalNode( NormalNode.VIEW ), new CameraNode( CameraNode.VIEW_MATRIX ) );
outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) );
}
return outputNode.build( builder );
}
serialize( data ) {
super.serialize( data );
data.scope = this.scope;
}
deserialize( data ) {
super.deserialize( data );
this.scope = data.scope;
}
}
export default NormalNode;

View File

@@ -0,0 +1,121 @@
import Node from '../core/Node.js';
import Matrix4Node from '../inputs/Matrix4Node.js';
import Matrix3Node from '../inputs/Matrix3Node.js';
import Vector3Node from '../inputs/Vector3Node.js';
import { NodeUpdateType } from '../core/constants.js';
class Object3DNode extends Node {
static VIEW_MATRIX = 'viewMatrix';
static NORMAL_MATRIX = 'normalMatrix';
static WORLD_MATRIX = 'worldMatrix';
static POSITION = 'position';
static VIEW_POSITION = 'viewPosition';
constructor( scope = Object3DNode.VIEW_MATRIX, object3d = null ) {
super();
this.scope = scope;
this.object3d = object3d;
this.updateType = NodeUpdateType.Object;
this._inputNode = null;
}
getNodeType() {
const scope = this.scope;
if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
return 'mat4';
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
return 'mat3';
} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
return 'vec3';
}
}
update( frame ) {
const object = this.object3d !== null ? this.object3d : frame.object;
const inputNode = this._inputNode;
const camera = frame.camera;
const scope = this.scope;
if ( scope === Object3DNode.VIEW_MATRIX ) {
inputNode.value = object.modelViewMatrix;
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
inputNode.value = object.normalMatrix;
} else if ( scope === Object3DNode.WORLD_MATRIX ) {
inputNode.value = object.matrixWorld;
} else if ( scope === Object3DNode.POSITION ) {
inputNode.value.setFromMatrixPosition( object.matrixWorld );
} else if ( scope === Object3DNode.VIEW_POSITION ) {
inputNode.value.setFromMatrixPosition( object.matrixWorld );
inputNode.value.applyMatrix4( camera.matrixWorldInverse );
}
}
generate( builder ) {
const scope = this.scope;
if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
this._inputNode = new Matrix4Node( /*null*/ );
} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
this._inputNode = new Matrix3Node( /*null*/ );
} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
this._inputNode = new Vector3Node();
}
return this._inputNode.build( builder );
}
serialize( data ) {
super.serialize( data );
data.scope = this.scope;
}
deserialize( data ) {
super.deserialize( data );
this.scope = data.scope;
}
}
export default Object3DNode;

View File

@@ -0,0 +1,21 @@
import Node from '../core/Node.js';
class PointUVNode extends Node {
constructor() {
super( 'vec2' );
}
generate( /*builder*/ ) {
return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )';
}
}
PointUVNode.prototype.isPointUVNode = true;
export default PointUVNode;

View File

@@ -0,0 +1,83 @@
import Node from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import VaryNode from '../core/VaryNode.js';
import ModelNode from '../accessors/ModelNode.js';
import MathNode from '../math/MathNode.js';
import OperatorNode from '../math/OperatorNode.js';
class PositionNode extends Node {
static GEOMETRY = 'geometry';
static LOCAL = 'local';
static WORLD = 'world';
static VIEW = 'view';
static VIEW_DIRECTION = 'viewDirection';
constructor( scope = PositionNode.LOCAL ) {
super( 'vec3' );
this.scope = scope;
}
getHash( /*builder*/ ) {
return `position-${this.scope}`;
}
generate( builder ) {
const scope = this.scope;
let outputNode = null;
if ( scope === PositionNode.GEOMETRY ) {
outputNode = new AttributeNode( 'position', 'vec3' );
} else if ( scope === PositionNode.LOCAL ) {
outputNode = new VaryNode( new PositionNode( PositionNode.GEOMETRY ) );
} else if ( scope === PositionNode.WORLD ) {
const vertexPositionNode = new MathNode( MathNode.TRANSFORM_DIRECTION, new ModelNode( ModelNode.WORLD_MATRIX ), new PositionNode( PositionNode.LOCAL ) );
outputNode = new VaryNode( vertexPositionNode );
} else if ( scope === PositionNode.VIEW ) {
const vertexPositionNode = new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), new PositionNode( PositionNode.LOCAL ) );
outputNode = new VaryNode( vertexPositionNode );
} else if ( scope === PositionNode.VIEW_DIRECTION ) {
const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) );
outputNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexPositionNode ) );
}
return outputNode.build( builder, this.getNodeType( builder ) );
}
serialize( data ) {
super.serialize( data );
data.scope = this.scope;
}
deserialize( data ) {
super.deserialize( data );
this.scope = data.scope;
}
}
export default PositionNode;

View File

@@ -0,0 +1,91 @@
import Node from '../core/Node.js';
import FloatNode from '../inputs/FloatNode.js';
import Vector2Node from '../inputs/Vector2Node.js';
import Vector3Node from '../inputs/Vector3Node.js';
import Vector4Node from '../inputs/Vector4Node.js';
import ColorNode from '../inputs/ColorNode.js';
import TextureNode from '../inputs/TextureNode.js';
import { NodeUpdateType } from '../core/constants.js';
class ReferenceNode extends Node {
constructor( property, inputType, object = null ) {
super();
this.property = property;
this.inputType = inputType;
this.object = object;
this.node = null;
this.updateType = NodeUpdateType.Object;
this.setNodeType( inputType );
}
setNodeType( inputType ) {
let node = null;
let nodeType = inputType;
if ( nodeType === 'float' ) {
node = new FloatNode();
} else if ( nodeType === 'vec2' ) {
node = new Vector2Node( null );
} else if ( nodeType === 'vec3' ) {
node = new Vector3Node( null );
} else if ( nodeType === 'vec4' ) {
node = new Vector4Node( null );
} else if ( nodeType === 'color' ) {
node = new ColorNode( null );
nodeType = 'vec3';
} else if ( nodeType === 'texture' ) {
node = new TextureNode();
nodeType = 'vec4';
}
this.node = node;
this.nodeType = nodeType;
this.inputType = inputType;
}
getNodeType() {
return this.inputType;
}
update( frame ) {
const object = this.object !== null ? this.object : frame.object;
const value = object[ this.property ];
this.node.value = value;
}
generate( builder ) {
return this.node.build( builder, this.getNodeType( builder ) );
}
}
export default ReferenceNode;

View File

@@ -0,0 +1,107 @@
import Node from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import PositionNode from '../accessors/PositionNode.js';
import NormalNode from '../accessors/NormalNode.js';
import Matrix4Node from '../inputs/Matrix4Node.js';
import BufferNode from '../inputs/BufferNode.js';
import { ShaderNode, assign, element, add, mul, transformDirection } from '../ShaderNode.js';
import { NodeUpdateType } from '../core/constants.js';
const Skinning = new ShaderNode( ( inputs, builder ) => {
const { position, normal, index, weight, bindMatrix, bindMatrixInverse, boneMatrices } = inputs;
const boneMatX = element( boneMatrices, index.x );
const boneMatY = element( boneMatrices, index.y );
const boneMatZ = element( boneMatrices, index.z );
const boneMatW = element( boneMatrices, index.w );
// POSITION
const skinVertex = mul( bindMatrix, position );
const skinned = add(
mul( mul( boneMatX, skinVertex ), weight.x ),
mul( mul( boneMatY, skinVertex ), weight.y ),
mul( mul( boneMatZ, skinVertex ), weight.z ),
mul( mul( boneMatW, skinVertex ), weight.w )
);
const skinPosition = mul( bindMatrixInverse, skinned ).xyz;
// NORMAL
let skinMatrix = add(
mul( weight.x, boneMatX ),
mul( weight.y, boneMatY ),
mul( weight.z, boneMatZ ),
mul( weight.w, boneMatW )
);
skinMatrix = mul( mul( bindMatrixInverse, skinMatrix ), bindMatrix );
const skinNormal = transformDirection( skinMatrix, normal ).xyz;
// ASSIGNS
assign( position, skinPosition ).build( builder );
assign( normal, skinNormal ).build( builder );
} );
class SkinningNode extends Node {
constructor( skinnedMesh ) {
super( 'void' );
this.skinnedMesh = skinnedMesh;
this.updateType = NodeUpdateType.Object;
//
this.skinIndexNode = new AttributeNode( 'skinIndex', 'uvec4' );
this.skinWeightNode = new AttributeNode( 'skinWeight', 'vec4' );
this.bindMatrixNode = new Matrix4Node( skinnedMesh.bindMatrix );
this.bindMatrixInverseNode = new Matrix4Node( skinnedMesh.bindMatrixInverse );
this.boneMatricesNode = new BufferNode( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );
}
generate( builder ) {
// inout nodes
const position = new PositionNode( PositionNode.LOCAL );
const normal = new NormalNode( NormalNode.LOCAL );
const index = this.skinIndexNode;
const weight = this.skinWeightNode;
const bindMatrix = this.bindMatrixNode;
const bindMatrixInverse = this.bindMatrixInverseNode;
const boneMatrices = this.boneMatricesNode;
Skinning( {
position,
normal,
index,
weight,
bindMatrix,
bindMatrixInverse,
boneMatrices
}, builder );
}
update() {
this.skinnedMesh.skeleton.update();
}
}
export default SkinningNode;

View File

@@ -0,0 +1,41 @@
import AttributeNode from '../core/AttributeNode.js';
class UVNode extends AttributeNode {
constructor( index = 0 ) {
super( null, 'vec2' );
this.index = index;
}
getAttributeName( /*builder*/ ) {
const index = this.index;
return 'uv' + ( index > 0 ? index + 1 : '' );
}
serialize( data ) {
super.serialize( data );
data.index = this.index;
}
deserialize( data ) {
super.deserialize( data );
this.index = data.index;
}
}
UVNode.prototype.isUVNode = true;
export default UVNode;

View File

@@ -0,0 +1,23 @@
import InputNode from './InputNode.js';
class ArrayInputNode extends InputNode {
constructor( nodes = [] ) {
super();
this.nodes = nodes;
}
getNodeType( builder ) {
return this.nodes[ 0 ].getNodeType( builder );
}
}
ArrayInputNode.prototype.isArrayInputNode = true;
export default ArrayInputNode;

View File

@@ -0,0 +1,54 @@
import Node from './Node.js';
import VaryNode from './VaryNode.js';
class AttributeNode extends Node {
constructor( attributeName, nodeType ) {
super( nodeType );
this._attributeName = attributeName;
}
getHash( builder ) {
return this.getAttributeName( builder );
}
setAttributeName( attributeName ) {
this._attributeName = attributeName;
return this;
}
getAttributeName( /*builder*/ ) {
return this._attributeName;
}
generate( builder ) {
const attribute = builder.getAttribute( this.getAttributeName( builder ), this.getNodeType( builder ) );
if ( builder.isShaderStage( 'vertex' ) ) {
return attribute.name;
} else {
const nodeVary = new VaryNode( this );
return nodeVary.build( builder, attribute.type );
}
}
}
export default AttributeNode;

View File

@@ -0,0 +1,38 @@
import Node from './Node.js';
class BypassNode extends Node {
constructor( returnNode, callNode ) {
super();
this.outputNode = returnNode;
this.callNode = callNode;
}
getNodeType( builder ) {
return this.outputNode.getNodeType( builder );
}
generate( builder, output ) {
const snippet = this.callNode.build( builder, 'void' );
if ( snippet !== '' ) {
builder.addFlowCode( snippet );
}
return this.outputNode.build( builder, output );
}
}
BypassNode.prototype.isBypassNode = true;
export default BypassNode;

View File

@@ -0,0 +1,78 @@
import Node from './Node.js';
class CodeNode extends Node {
constructor( code = '', nodeType = 'code' ) {
super( nodeType );
this.code = code;
this.useKeywords = false;
this._includes = [];
}
setIncludes( includes ) {
this._includes = includes;
return this;
}
getIncludes( /*builder*/ ) {
return this._includes;
}
generate( builder ) {
if ( this.useKeywords === true ) {
const contextKeywords = builder.context.keywords;
if ( contextKeywords !== undefined ) {
const nodeData = builder.getDataFromNode( this, builder.shaderStage );
if ( nodeData.keywords === undefined ) {
nodeData.keywords = [];
}
if ( nodeData.keywords.indexOf( contextKeywords ) === - 1 ) {
contextKeywords.include( builder, this.code );
nodeData.keywords.push( contextKeywords );
}
}
}
const includes = this.getIncludes( builder );
for ( const include of includes ) {
include.build( builder );
}
const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) );
nodeCode.code = this.code;
return nodeCode.code;
}
}
CodeNode.prototype.isCodeNode = true;
export default CodeNode;

View File

@@ -0,0 +1,38 @@
import Node from './Node.js';
class ContextNode extends Node {
constructor( node, context = {} ) {
super();
this.node = node;
this.context = context;
}
getNodeType( builder ) {
return this.node.getNodeType( builder );
}
generate( builder, output ) {
const previousContext = builder.getContext();
builder.setContext( Object.assign( {}, builder.context, this.context ) );
const snippet = this.node.build( builder, output );
builder.setContext( previousContext );
return snippet;
}
}
ContextNode.prototype.isContextNode = true;
export default ContextNode;

View File

@@ -0,0 +1,32 @@
import TempNode from './TempNode.js';
class ExpressionNode extends TempNode {
constructor( snipped = '', nodeType = 'void' ) {
super( nodeType );
this.snipped = snipped;
}
generate( builder ) {
const type = this.getNodeType( builder );
const snipped = this.snipped;
if ( type === 'void' ) {
builder.addFlowCode( snipped );
} else {
return `( ${ snipped } )`;
}
}
}
export default ExpressionNode;

View File

@@ -0,0 +1,67 @@
import TempNode from './TempNode.js';
class FunctionCallNode extends TempNode {
constructor( functionNode = null, parameters = {} ) {
super();
this.functionNode = functionNode;
this.parameters = parameters;
}
setParameters( parameters ) {
this.parameters = parameters;
return this;
}
getParameters() {
return this.parameters;
}
getNodeType( builder ) {
return this.functionNode.getNodeType( builder );
}
generate( builder ) {
const params = [];
const functionNode = this.functionNode;
const inputs = functionNode.getInputs( builder );
const parameters = this.parameters;
for ( const inputNode of inputs ) {
const node = parameters[ inputNode.name ];
if ( node !== undefined ) {
params.push( node.build( builder, inputNode.type ) );
} else {
throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` );
}
}
const functionName = functionNode.build( builder, 'property' );
return `${functionName}( ${params.join( ', ' )} )`;
}
}
export default FunctionCallNode;

View File

@@ -0,0 +1,87 @@
import CodeNode from './CodeNode.js';
import FunctionCallNode from './FunctionCallNode.js';
class FunctionNode extends CodeNode {
constructor( code = '' ) {
super( code );
this.useKeywords = true;
}
getNodeType( builder ) {
return this.getNodeFunction( builder ).type;
}
getInputs( builder ) {
return this.getNodeFunction( builder ).inputs;
}
getNodeFunction( builder ) {
const nodeData = builder.getDataFromNode( this );
let nodeFunction = nodeData.nodeFunction;
if ( nodeFunction === undefined ) {
nodeFunction = builder.parser.parseFunction( this.code );
nodeData.nodeFunction = nodeFunction;
}
return nodeFunction;
}
call( parameters = {} ) {
return new FunctionCallNode( this, parameters );
}
generate( builder, output ) {
super.generate( builder );
const nodeFunction = this.getNodeFunction( builder );
const name = nodeFunction.name;
const type = nodeFunction.type;
const nodeCode = builder.getCodeFromNode( this, type );
if ( name !== '' ) {
// use a custom property name
nodeCode.name = name;
}
const propertyName = builder.getPropertyName( nodeCode );
nodeCode.code = this.getNodeFunction( builder ).getCode( propertyName );
if ( output === 'property' ) {
return propertyName;
} else {
return builder.format( `${ propertyName }()`, type, output );
}
}
}
export default FunctionNode;

View File

@@ -0,0 +1,66 @@
import Node from './Node.js';
class InputNode extends Node {
constructor( inputType ) {
super( inputType );
this.inputType = inputType;
this.constant = false;
}
setConst( value ) {
this.constant = value;
return this;
}
getConst() {
return this.constant;
}
getInputType( /* builder */ ) {
return this.inputType;
}
generateConst( builder ) {
return builder.getConst( this.getNodeType( builder ), this.value );
}
generate( builder, output ) {
const type = this.getNodeType( builder );
if ( this.constant === true ) {
return builder.format( this.generateConst( builder ), type, output );
} else {
const inputType = this.getInputType( builder );
const nodeUniform = builder.getUniformFromNode( this, builder.shaderStage, inputType );
const propertyName = builder.getPropertyName( nodeUniform );
return builder.format( propertyName, type, output );
}
}
}
InputNode.prototype.isInputNode = true;
export default InputNode;

View File

@@ -0,0 +1,240 @@
import { NodeUpdateType } from './constants.js';
import { getNodesKeys } from './NodeUtils.js';
import { MathUtils } from 'three';
class Node {
constructor( nodeType = null ) {
this.nodeType = nodeType;
this.updateType = NodeUpdateType.None;
this.uuid = MathUtils.generateUUID();
}
get type() {
return this.constructor.name;
}
getHash( /*builder*/ ) {
return this.uuid;
}
getUpdateType( /*builder*/ ) {
return this.updateType;
}
getNodeType( /*builder*/ ) {
return this.nodeType;
}
update( /*frame*/ ) {
console.warn( 'Abstract function.' );
}
generate( /*builder, output*/ ) {
console.warn( 'Abstract function.' );
}
analyze( builder ) {
const hash = this.getHash( builder );
const sharedNode = builder.getNodeFromHash( hash );
if ( sharedNode !== undefined && this !== sharedNode ) {
return sharedNode.analyze( builder );
}
const nodeData = builder.getDataFromNode( this );
nodeData.dependenciesCount = nodeData.dependenciesCount === undefined ? 1 : nodeData.dependenciesCount + 1;
const nodeKeys = getNodesKeys( this );
for ( const property of nodeKeys ) {
this[ property ].analyze( builder );
}
}
build( builder, output = null ) {
const hash = this.getHash( builder );
const sharedNode = builder.getNodeFromHash( hash );
if ( sharedNode !== undefined && this !== sharedNode ) {
return sharedNode.build( builder, output );
}
builder.addNode( this );
builder.addStack( this );
const nodeData = builder.getDataFromNode( this );
const isGenerateOnce = this.generate.length === 1;
let snippet = null;
if ( isGenerateOnce ) {
const type = this.getNodeType( builder );
snippet = nodeData.snippet;
if ( snippet === undefined ) {
snippet = this.generate( builder ) || '';
nodeData.snippet = snippet;
}
snippet = builder.format( snippet, type, output );
} else {
snippet = this.generate( builder, output ) || '';
}
builder.removeStack( this );
return snippet;
}
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;
export default Node;

View File

@@ -0,0 +1,14 @@
class NodeAttribute {
constructor( name, type ) {
this.name = name;
this.type = type;
}
}
NodeAttribute.prototype.isNodeAttribute = true;
export default NodeAttribute;

View File

@@ -0,0 +1,641 @@
import NodeUniform from './NodeUniform.js';
import NodeAttribute from './NodeAttribute.js';
import NodeVary from './NodeVary.js';
import NodeVar from './NodeVar.js';
import NodeCode from './NodeCode.js';
import NodeKeywords from './NodeKeywords.js';
import { NodeUpdateType } from './constants.js';
import { REVISION, LinearEncoding } from 'three';
export const shaderStages = [ 'fragment', 'vertex' ];
export const vector = [ 'x', 'y', 'z', 'w' ];
class NodeBuilder {
constructor( object, renderer, parser ) {
this.object = object;
this.material = object.material;
this.renderer = renderer;
this.parser = parser;
this.nodes = [];
this.updateNodes = [];
this.hashNodes = {};
this.vertexShader = null;
this.fragmentShader = null;
this.flowNodes = { vertex: [], fragment: [] };
this.flowCode = { vertex: '', fragment: '' };
this.uniforms = { vertex: [], fragment: [], index: 0 };
this.codes = { vertex: [], fragment: [] };
this.attributes = [];
this.varys = [];
this.vars = { vertex: [], fragment: [] };
this.flow = { code: '' };
this.stack = [];
this.context = {
keywords: new NodeKeywords(),
material: object.material
};
this.nodesData = new WeakMap();
this.flowsData = new WeakMap();
this.shaderStage = null;
this.node = null;
}
addStack( node ) {
/*
if ( this.stack.indexOf( node ) !== - 1 ) {
console.warn( 'Recursive node: ', node );
}
*/
this.stack.push( node );
}
removeStack( node ) {
const lastStack = this.stack.pop();
if ( lastStack !== node ) {
throw new Error( 'NodeBuilder: Invalid node stack!' );
}
}
addNode( node ) {
if ( this.nodes.indexOf( node ) === - 1 ) {
const updateType = node.getUpdateType( this );
if ( updateType !== NodeUpdateType.None ) {
this.updateNodes.push( node );
}
this.nodes.push( node );
this.hashNodes[ node.getHash( this ) ] = node;
}
}
getMethod( method ) {
return method;
}
getNodeFromHash( hash ) {
return this.hashNodes[ hash ];
}
addFlow( shaderStage, node ) {
this.flowNodes[ shaderStage ].push( node );
return node;
}
setContext( context ) {
this.context = context;
}
getContext() {
return this.context;
}
getTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) {
console.warn( 'Abstract function.' );
}
getCubeTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) {
console.warn( 'Abstract function.' );
}
// rename to generate
getConst( type, value ) {
if ( type === 'float' ) return value + ( value % 1 ? '' : '.0' );
if ( type === 'vec2' ) return `${ this.getType( 'vec2' ) }( ${value.x}, ${value.y} )`;
if ( type === 'vec3' ) return `${ this.getType( 'vec3' ) }( ${value.x}, ${value.y}, ${value.z} )`;
if ( type === 'vec4' ) return `${ this.getType( 'vec4' ) }( ${value.x}, ${value.y}, ${value.z}, ${value.w} )`;
if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${value.r}, ${value.g}, ${value.b} )`;
throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );
}
getType( type ) {
return type;
}
generateMethod( method ) {
return method;
}
getAttribute( name, type ) {
const attributes = this.attributes;
// find attribute
for ( const attribute of attributes ) {
if ( attribute.name === name ) {
return attribute;
}
}
// create a new if no exist
const attribute = new NodeAttribute( name, type );
attributes.push( attribute );
return attribute;
}
getPropertyName( node/*, shaderStage*/ ) {
return node.name;
}
isVector( type ) {
return /vec\d/.test( type );
}
isMatrix( type ) {
return /mat\d/.test( type );
}
isShaderStage( shaderStage ) {
return this.shaderStage === shaderStage;
}
getTextureEncodingFromMap( map ) {
let encoding;
if ( map && map.isTexture ) {
encoding = map.encoding;
} else if ( map && map.isWebGLRenderTarget ) {
encoding = map.texture.encoding;
} else {
encoding = LinearEncoding;
}
return encoding;
}
getVectorType( type ) {
if ( type === 'color' ) return 'vec3';
if ( type === 'texture' ) return 'vec4';
return type;
}
getTypeFromLength( type ) {
if ( type === 1 ) return 'float';
if ( type === 2 ) return 'vec2';
if ( type === 3 ) return 'vec3';
if ( type === 4 ) return 'vec4';
return 0;
}
getTypeLength( type ) {
const vecType = this.getVectorType( type );
const vecNum = /vec([2-4])/.exec( vecType );
if ( vecNum !== null ) return Number( vecNum[ 1 ] );
if ( vecType === 'float' || vecType === 'bool' ) return 1;
return 0;
}
getVectorFromMatrix( type ) {
return 'vec' + type.slice( 3 );
}
getDataFromNode( node, shaderStage = this.shaderStage ) {
let nodeData = this.nodesData.get( node );
if ( nodeData === undefined ) {
nodeData = { vertex: {}, fragment: {} };
this.nodesData.set( node, nodeData );
}
return shaderStage !== null ? nodeData[ shaderStage ] : nodeData;
}
getUniformFromNode( node, shaderStage, type ) {
const nodeData = this.getDataFromNode( node, shaderStage );
let nodeUniform = nodeData.uniform;
if ( nodeUniform === undefined ) {
const index = this.uniforms.index ++;
nodeUniform = new NodeUniform( 'nodeUniform' + index, type, node );
this.uniforms[ shaderStage ].push( nodeUniform );
nodeData.uniform = nodeUniform;
}
return nodeUniform;
}
getVarFromNode( node, type, shaderStage = this.shaderStage ) {
const nodeData = this.getDataFromNode( node, shaderStage );
let nodeVar = nodeData.variable;
if ( nodeVar === undefined ) {
const vars = this.vars[ shaderStage ];
const index = vars.length;
nodeVar = new NodeVar( 'nodeVar' + index, type );
vars.push( nodeVar );
nodeData.variable = nodeVar;
}
return nodeVar;
}
getVaryFromNode( node, type ) {
const nodeData = this.getDataFromNode( node, null );
let nodeVary = nodeData.vary;
if ( nodeVary === undefined ) {
const varys = this.varys;
const index = varys.length;
nodeVary = new NodeVary( 'nodeVary' + index, type );
varys.push( nodeVary );
nodeData.vary = nodeVary;
}
return nodeVary;
}
getCodeFromNode( node, type, shaderStage = this.shaderStage ) {
const nodeData = this.getDataFromNode( node );
let nodeCode = nodeData.code;
if ( nodeCode === undefined ) {
const codes = this.codes[ shaderStage ];
const index = codes.length;
nodeCode = new NodeCode( 'nodeCode' + index, type );
codes.push( nodeCode );
nodeData.code = nodeCode;
}
return nodeCode;
}
addFlowCode( code ) {
this.flow.code += code;
}
getFlowData( shaderStage, node ) {
return this.flowsData.get( node );
}
flowNode( node ) {
this.node = node;
const output = node.getNodeType( this );
const flowData = this.flowChildNode( node, output );
this.flowsData.set( node, flowData );
this.node = null;
return flowData;
}
flowChildNode( node, output = null ) {
const previousFlow = this.flow;
const flow = {
code: '',
};
this.flow = flow;
flow.result = node.build( this, output );
this.flow = previousFlow;
return flow;
}
flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {
const previousShaderStage = this.shaderStage;
this.setShaderStage( shaderStage );
const flowData = this.flowChildNode( node, output );
if ( propertyName !== null ) {
flowData.code += `${propertyName} = ${flowData.result};\n\t`;
}
this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code;
this.setShaderStage( previousShaderStage );
return flowData;
}
getAttributes( /*shaderStage*/ ) {
console.warn( 'Abstract function.' );
}
getVarys( /*shaderStage*/ ) {
console.warn( 'Abstract function.' );
}
getVars( shaderStage ) {
let snippet = '';
const vars = this.vars[ shaderStage ];
for ( let index = 0; index < vars.length; index ++ ) {
const variable = vars[ index ];
snippet += `${variable.type} ${variable.name}; `;
}
return snippet;
}
getUniforms( /*shaderStage*/ ) {
console.warn( 'Abstract function.' );
}
getCodes( shaderStage ) {
const codes = this.codes[ shaderStage ];
let code = '';
for ( const nodeCode of codes ) {
code += nodeCode.code + '\n';
}
return code;
}
getHash() {
return this.vertexShader + this.fragmentShader;
}
getShaderStage() {
return this.shaderStage;
}
setShaderStage( shaderStage ) {
this.shaderStage = shaderStage;
}
buildCode() {
console.warn( 'Abstract function.' );
}
build() {
// stage 1: analyze nodes to possible optimization and validation
for ( const shaderStage of shaderStages ) {
this.setShaderStage( shaderStage );
const flowNodes = this.flowNodes[ shaderStage ];
for ( const node of flowNodes ) {
node.analyze( this );
}
}
// stage 2: pre-build vertex code used in fragment shader
if ( this.context.vertex && this.context.vertex.isNode ) {
this.flowNodeFromShaderStage( 'vertex', this.context.vertex );
}
// stage 3: generate shader
for ( const shaderStage of shaderStages ) {
this.setShaderStage( shaderStage );
const flowNodes = this.flowNodes[ shaderStage ];
for ( const node of flowNodes ) {
this.flowNode( node, shaderStage );
}
}
this.setShaderStage( null );
// stage 4: build code for a specific output
this.buildCode();
return this;
}
format( snippet, fromType, toType ) {
fromType = this.getVectorType( fromType );
toType = this.getVectorType( toType );
const typeToType = `${fromType} to ${toType}`;
switch ( typeToType ) {
case 'int to float' : return `${ this.getType( 'float' ) }( ${ snippet } )`;
case 'int to vec2' : return `${ this.getType( 'vec2' ) }( ${ this.getType( 'float' ) }( ${ snippet } ) )`;
case 'int to vec3' : return `${ this.getType( 'vec3' ) }( ${ this.getType( 'float' ) }( ${ snippet } ) )`;
case 'int to vec4' : return `${ this.getType( 'vec4' ) }( ${ this.getType( 'vec3' ) }( ${ this.getType( 'float' ) }( ${ snippet } ) ), 1.0 )`;
case 'float to int' : return `${ this.getType( 'int' ) }( ${ snippet } )`;
case 'float to vec2' : return `${ this.getType( 'vec2' ) }( ${ snippet } )`;
case 'float to vec3' : return `${ this.getType( 'vec3' ) }( ${ snippet } )`;
case 'float to vec4' : return `${ this.getType( 'vec4' ) }( ${ this.getType( 'vec3' ) }( ${ snippet } ), 1.0 )`;
case 'vec2 to int' : return `${ this.getType( 'int' ) }( ${ snippet }.x )`;
case 'vec2 to float' : return `${ snippet }.x`;
case 'vec2 to vec3' : return `${ this.getType( 'vec3' ) }( ${ snippet }, 0.0 )`;
case 'vec2 to vec4' : return `${ this.getType( 'vec4' ) }( ${ snippet }.xy, 0.0, 1.0 )`;
case 'vec3 to int' : return `${ this.getType( 'int' ) }( ${ snippet }.x )`;
case 'vec3 to float' : return `${ snippet }.x`;
case 'vec3 to vec2' : return `${ snippet }.xy`;
case 'vec3 to vec4' : return `${ this.getType( 'vec4' ) }( ${ snippet }, 1.0 )`;
case 'vec4 to int' : return `${ this.getType( 'int' ) }( ${ snippet }.x )`;
case 'vec4 to float' : return `${ snippet }.x`;
case 'vec4 to vec2' : return `${ snippet }.xy`;
case 'vec4 to vec3' : return `${ snippet }.xyz`;
case 'mat3 to int' : return `${ this.getType( 'int' ) }( ${ snippet } * ${ this.getType( 'vec3' ) }( 1.0 ) ).x`;
case 'mat3 to float' : return `( ${ snippet } * ${ this.getType( 'vec3' ) }( 1.0 ) ).x`;
case 'mat3 to vec2' : return `( ${ snippet } * ${ this.getType( 'vec3' ) }( 1.0 ) ).xy`;
case 'mat3 to vec3' : return `( ${ snippet } * ${ this.getType( 'vec3' ) }( 1.0 ) ).xyz`;
case 'mat3 to vec4' : return `${ this.getType( 'vec4' ) }( ${ snippet } * ${ this.getType( 'vec3' ) }( 1.0 ), 1.0 )`;
case 'mat4 to int' : return `${ this.getType( 'int' ) }( ${ snippet } * ${ this.getType( 'vec4' ) }( 1.0 ) ).x`;
case 'mat4 to float' : return `( ${ snippet } * ${ this.getType( 'vec4' ) }( 1.0 ) ).x`;
case 'mat4 to vec2' : return `( ${ snippet } * ${ this.getType( 'vec4' ) }( 1.0 ) ).xy`;
case 'mat4 to vec3' : return `( ${ snippet } * ${ this.getType( 'vec4' ) }( 1.0 ) ).xyz`;
case 'mat4 to vec4' : return `( ${ snippet } * ${ this.getType( 'vec4' ) }( 1.0 ) )`;
}
return snippet;
}
getSignature() {
return `// Three.js r${ REVISION } - NodeMaterial System\n`;
}
}
export default NodeBuilder;

View File

@@ -0,0 +1,15 @@
class NodeCode {
constructor( name, type, code = '' ) {
this.name = name;
this.type = type;
this.code = code;
Object.defineProperty( this, 'isNodeCode', { value: true } );
}
}
export default NodeCode;

View File

@@ -0,0 +1,59 @@
import { NodeUpdateType } from './constants.js';
class NodeFrame {
constructor() {
this.time = 0;
this.deltaTime = 0;
this.frameId = 0;
this.startTime = null;
this.updateMap = new WeakMap();
this.renderer = null;
this.material = null;
this.camera = null;
this.object = null;
}
updateNode( node ) {
if ( node.updateType === NodeUpdateType.Frame ) {
if ( this.updateMap.get( node ) !== this.frameId ) {
this.updateMap.set( node, this.frameId );
node.update( this );
}
} else if ( node.updateType === NodeUpdateType.Object ) {
node.update( this );
}
}
update() {
this.frameId ++;
if ( this.lastTime === undefined ) this.lastTime = performance.now();
this.deltaTime = ( performance.now() - this.lastTime ) / 1000;
this.lastTime = performance.now();
this.time += this.deltaTime;
}
}
export default NodeFrame;

View File

@@ -0,0 +1,22 @@
class NodeFunction {
constructor( type, inputs, name = '', presicion = '' ) {
this.type = type;
this.inputs = inputs;
this.name = name;
this.presicion = presicion;
}
getCode( /*name = this.name*/ ) {
console.warn( 'Abstract function.' );
}
}
NodeFunction.isNodeFunction = true;
export default NodeFunction;

View File

@@ -0,0 +1,17 @@
class NodeFunctionInput {
constructor( type, name, count = null, qualifier = '', isConst = false ) {
this.type = type;
this.name = name;
this.count = count;
this.qualifier = qualifier;
this.isConst = isConst;
}
}
NodeFunctionInput.isNodeFunctionInput = true;
export default NodeFunctionInput;

View File

@@ -0,0 +1,80 @@
class NodeKeywords {
constructor() {
this.keywords = [];
this.nodes = [];
this.keywordsCallback = {};
}
getNode( name ) {
let node = this.nodes[ name ];
if ( node === undefined && this.keywordsCallback[ name ] !== undefined ) {
node = this.keywordsCallback[ name ]( name );
this.nodes[ name ] = node;
}
return node;
}
addKeyword( name, callback ) {
this.keywords.push( name );
this.keywordsCallback[ name ] = callback;
return this;
}
parse( code ) {
const keywordNames = this.keywords;
const regExp = new RegExp( `\\b${keywordNames.join( '\\b|\\b' )}\\b`, 'g' );
const codeKeywords = code.match( regExp );
const keywordNodes = [];
if ( codeKeywords !== null ) {
for ( const keyword of codeKeywords ) {
const node = this.getNode( keyword );
if ( node !== undefined && keywordNodes.indexOf( node ) === - 1 ) {
keywordNodes.push( node );
}
}
}
return keywordNodes;
}
include( builder, code ) {
const keywordNodes = this.parse( code );
for ( const keywordNode of keywordNodes ) {
keywordNode.build( builder );
}
}
}
export default NodeKeywords;

View File

@@ -0,0 +1,11 @@
class NodeParser {
parseFunction( /*source*/ ) {
console.warn( 'Abstract function.' );
}
}
export default NodeParser;

View File

@@ -0,0 +1,28 @@
class NodeUniform {
constructor( name, type, node, needsUpdate = undefined ) {
this.name = name;
this.type = type;
this.node = node;
this.needsUpdate = needsUpdate;
}
get value() {
return this.node.value;
}
set value( val ) {
this.node.value = val;
}
}
NodeUniform.prototype.isNodeUniform = true;
export default NodeUniform;

View File

@@ -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;
};

View File

@@ -0,0 +1,14 @@
class NodeVar {
constructor( name, type ) {
this.name = name;
this.type = type;
}
}
NodeVar.prototype.isNodeVar = true;
export default NodeVar;

View File

@@ -0,0 +1,14 @@
class NodeVary {
constructor( name, type ) {
this.name = name;
this.type = type;
}
}
NodeVary.prototype.isNodeVary = true;
export default NodeVary;

View File

@@ -0,0 +1,36 @@
import Node from './Node.js';
class PropertyNode extends Node {
constructor( name = null, nodeType = 'vec4' ) {
super( nodeType );
this.name = name;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
generate( builder ) {
const nodeVary = builder.getVarFromNode( this, this.getNodeType( builder ) );
const name = this.name;
if ( name !== null ) {
nodeVary.name = name;
}
return builder.getPropertyName( nodeVary );
}
}
export default PropertyNode;

View File

@@ -0,0 +1,42 @@
import Node from './Node.js';
class TempNode extends Node {
constructor( type ) {
super( type );
}
build( builder, output ) {
const type = builder.getVectorType( this.getNodeType( builder, output ) );
const nodeData = builder.getDataFromNode( this );
if ( builder.context.temp !== false && type !== 'void ' && output !== 'void' && nodeData.dependenciesCount > 1 ) {
if ( nodeData.snippet === undefined ) {
const snippet = super.build( builder, type );
const nodeVar = builder.getVarFromNode( this, type );
const propertyName = builder.getPropertyName( nodeVar );
builder.addFlowCode( `${propertyName} = ${snippet}` );
nodeData.snippet = snippet;
nodeData.propertyName = propertyName;
}
return builder.format( nodeData.propertyName, type, output );
}
return super.build( builder, output );
}
}
export default TempNode;

View File

@@ -0,0 +1,51 @@
import Node from './Node.js';
class VarNode extends Node {
constructor( node, name = null, nodeType = null ) {
super( nodeType );
this.node = node;
this.name = name;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
getNodeType( builder ) {
return super.getNodeType( builder ) || this.node.getNodeType( builder );
}
generate( builder ) {
const type = builder.getVectorType( this.getNodeType( builder ) );
const node = this.node;
const name = this.name;
const snippet = node.build( builder, type );
const nodeVar = builder.getVarFromNode( this, type );
if ( name !== null ) {
nodeVar.name = name;
}
const propertyName = builder.getPropertyName( nodeVar );
builder.addFlowCode( `${propertyName} = ${snippet}` );
return propertyName;
}
}
export default VarNode;

View File

@@ -0,0 +1,54 @@
import Node from './Node.js';
import { NodeShaderStage } from './constants.js';
class VaryNode extends Node {
constructor( node, name = null ) {
super();
this.node = node;
this.name = name;
}
getHash( builder ) {
return this.name || super.getHash( builder );
}
getNodeType( builder ) {
// VaryNode is auto type
return this.node.getNodeType( builder );
}
generate( builder ) {
const type = this.getNodeType( builder );
const node = this.node;
const name = this.name;
const nodeVary = builder.getVaryFromNode( this, type );
if ( name !== null ) {
nodeVary.name = name;
}
const propertyName = builder.getPropertyName( nodeVary, NodeShaderStage.Vertex );
// force node run in vertex stage
builder.flowNodeFromShaderStage( NodeShaderStage.Vertex, node, type, propertyName );
return builder.getPropertyName( nodeVary );
}
}
export default VaryNode;

View File

@@ -0,0 +1,21 @@
export const NodeShaderStage = {
Vertex: 'vertex',
Fragment: 'fragment'
};
export const NodeUpdateType = {
None: 'none',
Frame: 'frame',
Object: 'object'
};
export const NodeType = {
Boolean: 'bool',
Integer: 'int',
Float: 'float',
Vector2: 'vec2',
Vector3: 'vec3',
Vector4: 'vec4',
Matrix3: 'mat3',
Matrix4: 'mat4'
};

View File

@@ -0,0 +1,96 @@
import TempNode from '../core/Node.js';
import { ShaderNode,
vec3,
pow, mul, sub, mix, join,
lessThanEqual } from '../ShaderNode.js';
import { LinearEncoding, sRGBEncoding } from 'three';
export const LinearToLinear = new ShaderNode( ( inputs ) => {
return inputs.value;
} );
export const LinearTosRGB = new ShaderNode( ( inputs ) => {
const { value } = inputs;
const rgb = value.rgb;
const a = sub( mul( pow( value.rgb, vec3( 0.41666 ) ), 1.055 ), vec3( 0.055 ) );
const b = mul( rgb, 12.92 );
const factor = vec3( lessThanEqual( rgb, vec3( 0.0031308 ) ) );
const rgbResult = mix( a, b, factor );
return join( rgbResult.r, rgbResult.g, rgbResult.b, value.a );
} );
const EncodingLib = {
LinearToLinear,
LinearTosRGB
};
class ColorSpaceNode extends TempNode {
static LINEAR_TO_LINEAR = 'LinearToLinear';
static LINEAR_TO_SRGB = 'LinearTosRGB';
constructor( method, node ) {
super( 'vec4' );
this.method = method;
this.node = node;
}
fromEncoding( encoding ) {
let method = null;
if ( encoding === LinearEncoding ) {
method = 'Linear';
} else if ( encoding === sRGBEncoding ) {
method = 'sRGB';
}
this.method = 'LinearTo' + method;
return this;
}
generate( builder ) {
const type = this.getNodeType( builder );
const method = this.method;
const node = this.node;
if ( method !== ColorSpaceNode.LINEAR_TO_LINEAR ) {
const encodingFunctionNode = EncodingLib[ method ];
return encodingFunctionNode( {
value: node
} ).build( builder, type );
} else {
return node.build( builder, type );
}
}
}
export default ColorSpaceNode;

View File

@@ -0,0 +1,97 @@
import PositionNode from '../accessors/PositionNode.js';
import NormalNode from '../accessors/NormalNode.js';
import UVNode from '../accessors/UVNode.js';
import MathNode from '../math/MathNode.js';
import OperatorNode from '../math/OperatorNode.js';
import FloatNode from '../inputs/FloatNode.js';
import TempNode from '../core/TempNode.js';
import ModelNode from '../accessors/ModelNode.js';
import SplitNode from '../utils/SplitNode.js';
import JoinNode from '../utils/JoinNode.js';
import { ShaderNode, cond, add, mul, dFdx, dFdy, cross, max, dot, normalize, inversesqrt, equal } from '../ShaderNode.js';
import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
// Normal Mapping Without Precomputed Tangents
// http://www.thetenthplanet.de/archives/1180
const perturbNormal2ArbNode = new ShaderNode( ( inputs ) => {
const { eye_pos, surf_norm, mapN, faceDirection, uv } = inputs;
const q0 = dFdx( eye_pos.xyz );
const q1 = dFdy( eye_pos.xyz );
const st0 = dFdx( uv.st );
const st1 = dFdy( uv.st );
const N = surf_norm; // normalized
const q1perp = cross( q1, N );
const q0perp = cross( N, q0 );
const T = add( mul( q1perp, st0.x ), mul( q0perp, st1.x ) );
const B = add( mul( q1perp, st0.y ), mul( q0perp, st1.y ) );
const det = max( dot( T, T ), dot( B, B ) );
const scale = cond( equal( det, 0 ), 0, mul( faceDirection, inversesqrt( det ) ) );
return normalize( add( mul( T, mul( mapN.x, scale ) ), mul( B, mul( mapN.y, scale ) ), mul( N, mapN.z ) ) );
} );
class NormalMapNode extends TempNode {
constructor( node, scaleNode = null ) {
super( 'vec3' );
this.node = node;
this.scaleNode = scaleNode;
this.normalMapType = TangentSpaceNormalMap;
}
generate( builder ) {
const type = this.getNodeType( builder );
const { normalMapType, scaleNode } = this;
const normalOP = new OperatorNode( '*', this.node, new FloatNode( 2.0 ).setConst( true ) );
let normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) );
if ( scaleNode !== null ) {
const normalMapScale = new OperatorNode( '*', new SplitNode( normalMap, 'xy'), scaleNode );
normalMap = new JoinNode( [ normalMapScale, new SplitNode( normalMap, 'z' ) ] );
}
if ( normalMapType === ObjectSpaceNormalMap ) {
const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), normalMap );
const normal = new MathNode( MathNode.NORMALIZE, vertexNormalNode );
return normal.build( builder, type );
} else if ( normalMapType === TangentSpaceNormalMap ) {
const perturbNormal2ArbCall = perturbNormal2ArbNode( {
eye_pos: new PositionNode( PositionNode.VIEW ),
surf_norm: new NormalNode( NormalNode.VIEW ),
mapN: normalMap,
faceDirection: new FloatNode( 1.0 ).setConst( true ),
uv: new UVNode()
} );
return perturbNormal2ArbCall.build( builder, type );
}
}
}
export default NormalMapNode;

View File

@@ -0,0 +1,122 @@
import { ShaderNode,
add, addTo, sub, mul, div, saturate, dot, pow, pow2, exp2, normalize, max, sqrt, negate,
cond, greaterThan, and,
transformedNormalView, positionViewDirection,
diffuseColor, specularColor, roughness,
PI, RECIPROCAL_PI, EPSILON
} from '../ShaderNode.js';
export const F_Schlick = new ShaderNode( ( inputs ) => {
const { f0, f90, dotVH } = inputs;
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
const fresnel = exp2( mul( sub( mul( - 5.55473, dotVH ), 6.98316 ), dotVH ) );
return add( mul( f0, sub( 1.0, fresnel ) ), mul( f90, fresnel ) );
} ); // validated
export const BRDF_Lambert = new ShaderNode( ( inputs ) => {
return mul( RECIPROCAL_PI, inputs.diffuseColor ); // punctual light
} ); // validated
export const getDistanceAttenuation = new ShaderNode( ( inputs ) => {
const { lightDistance, cutoffDistance, decayExponent } = inputs;
return cond(
and( greaterThan( cutoffDistance, 0 ), greaterThan( decayExponent, 0 ) ),
pow( saturate( add( div( negate( lightDistance ), cutoffDistance ), 1.0 ) ), decayExponent ),
1.0
);
} ); // validated
//
// STANDARD
//
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
export const V_GGX_SmithCorrelated = new ShaderNode( ( inputs ) => {
const { alpha, dotNL, dotNV } = inputs;
const a2 = pow2( alpha );
const gv = mul( dotNL, sqrt( add( a2, mul( sub( 1.0, a2 ), pow2( dotNV ) ) ) ) );
const gl = mul( dotNV, sqrt( add( a2, mul( sub( 1.0, a2 ), pow2( dotNL ) ) ) ) );
return div( 0.5, max( add( gv, gl ), EPSILON ) );
} ); // validated
// Microfacet Models for Refraction through Rough Surfaces - equation (33)
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// alpha is "roughness squared" in Disneys reparameterization
export const D_GGX = new ShaderNode( ( inputs ) => {
const { alpha, dotNH } = inputs;
const a2 = pow2( alpha );
const denom = add( mul( pow2( dotNH ), sub( a2, 1.0 ) ), 1.0 ); // avoid alpha = 0 with dotNH = 1
return mul( RECIPROCAL_PI, div( a2, pow2( denom ) ) );
} ); // validated
// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
export const BRDF_GGX = new ShaderNode( ( inputs ) => {
const { lightDirection, f0, f90, roughness } = inputs;
const alpha = pow2( roughness ); // UE4's roughness
const halfDir = normalize( add( lightDirection, positionViewDirection ) );
const dotNL = saturate( dot( transformedNormalView, lightDirection ) );
const dotNV = saturate( dot( transformedNormalView, positionViewDirection ) );
const dotNH = saturate( dot( transformedNormalView, halfDir ) );
const dotVH = saturate( dot( positionViewDirection, halfDir ) );
const F = F_Schlick( { f0, f90, dotVH } );
const V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
const D = D_GGX( { alpha, dotNH } );
return mul( F, mul( V, D ) );
} ); // validated
export const RE_Direct_Physical = new ShaderNode( ( inputs ) => {
const { lightDirection, lightColor, directDiffuse, directSpecular } = inputs;
const dotNL = saturate( dot( transformedNormalView, lightDirection ) );
let irradiance = mul( dotNL, lightColor );
irradiance = mul( irradiance, PI ); // punctual light
addTo( directDiffuse, mul( irradiance, BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
addTo( directSpecular, mul( irradiance, BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness } ) ) );
} );
export const PhysicalLightingModel = new ShaderNode( ( inputs/*, builder*/ ) => {
// PHYSICALLY_CORRECT_LIGHTS <-> builder.renderer.physicallyCorrectLights === true
RE_Direct_Physical( inputs );
} );

View File

@@ -0,0 +1,27 @@
import { ShaderNode,
add, max, min, abs, dFdx, dFdy,
normalGeometry
} from '../ShaderNode.js';
export const getGeometryRoughness = new ShaderNode( () => {
const dxy = max( abs( dFdx( normalGeometry ) ), abs( dFdy( normalGeometry ) ) );
const geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );
return geometryRoughness;
} );
export const getRoughness = new ShaderNode( ( inputs ) => {
const { roughness } = inputs;
const geometryRoughness = getGeometryRoughness();
let roughnessFactor = max( roughness, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.
roughnessFactor = add( roughnessFactor, geometryRoughness );
roughnessFactor = min( roughnessFactor, 1.0 );
return roughnessFactor;
} );

View File

@@ -0,0 +1,25 @@
import InputNode from '../core/InputNode.js';
class BufferNode extends InputNode {
constructor( value, bufferType, bufferCount = 0 ) {
super( 'buffer' );
this.value = value;
this.bufferType = bufferType;
this.bufferCount = bufferCount;
}
getNodeType( /* builder */ ) {
return this.bufferType;
}
}
BufferNode.prototype.isBufferNode = true;
export default BufferNode;

View File

@@ -0,0 +1,43 @@
import InputNode from '../core/InputNode.js';
import { Color } from 'three';
class ColorNode extends InputNode {
constructor( value = new Color() ) {
super( 'color' );
this.value = value;
}
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;
export default ColorNode;

View File

@@ -0,0 +1,33 @@
import InputNode from '../core/InputNode.js';
class FloatNode extends InputNode {
constructor( value = 0 ) {
super( 'float' );
this.value = value;
}
serialize( data ) {
super.serialize( data );
data.value = this.value;
}
deserialize( data ) {
super.serialize( data );
data.value = this.value;
}
}
FloatNode.prototype.isFloatNode = true;
export default FloatNode;

View File

@@ -0,0 +1,33 @@
import InputNode from '../core/InputNode.js';
class IntNode extends InputNode {
constructor( value = 0 ) {
super( 'int' );
this.value = value;
}
serialize( data ) {
super.serialize( data );
data.value = this.value;
}
deserialize( data ) {
super.serialize( data );
this.value = data.value;
}
}
IntNode.prototype.isIntNode = true;
export default IntNode;

View File

@@ -0,0 +1,18 @@
import InputNode from '../core/InputNode.js';
import { Matrix3 } from 'three';
class Matrix3Node extends InputNode {
constructor( value = new Matrix3() ) {
super( 'mat3' );
this.value = value;
}
}
Matrix3Node.prototype.isMatrix3Node = true;
export default Matrix3Node;

View File

@@ -0,0 +1,18 @@
import InputNode from '../core/InputNode.js';
import { Matrix4 } from 'three';
class Matrix4Node extends InputNode {
constructor( value = new Matrix4() ) {
super( 'mat4' );
this.value = value;
}
}
Matrix4Node.prototype.isMatrix4Node = true;
export default Matrix4Node;

View File

@@ -0,0 +1,89 @@
import InputNode from '../core/InputNode.js';
import UVNode from '../accessors/UVNode.js';
class TextureNode extends InputNode {
constructor( value = null, uvNode = new UVNode(), biasNode = null ) {
super( 'texture' );
this.value = value;
this.uvNode = uvNode;
this.biasNode = biasNode;
}
generate( builder, output ) {
const texture = this.value;
if ( ! texture || texture.isTexture !== true ) {
throw new Error( 'TextureNode: Need a three.js texture.' );
}
const type = this.getNodeType( builder );
const textureProperty = super.generate( builder, type );
if ( output === 'sampler2D' || output === 'texture2D' ) {
return textureProperty;
} else if ( output === 'sampler' ) {
return textureProperty + '_sampler';
} else {
const nodeData = builder.getDataFromNode( this );
let snippet = nodeData.snippet;
if ( snippet === undefined ) {
const uvSnippet = this.uvNode.build( builder, 'vec2' );
const biasNode = this.biasNode;
let biasSnippet = null;
if ( biasNode !== null ) {
biasSnippet = biasNode.build( builder, 'float' );
}
snippet = builder.getTexture( textureProperty, uvSnippet, biasSnippet );
nodeData.snippet = snippet;
}
return builder.format( snippet, 'vec4', output );
}
}
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;
export default TextureNode;

View File

@@ -0,0 +1,41 @@
import InputNode from '../core/InputNode.js';
import { Vector2 } from 'three';
class Vector2Node extends InputNode {
constructor( value = new Vector2() ) {
super( 'vec2' );
this.value = value;
}
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;
export default Vector2Node;

View File

@@ -0,0 +1,43 @@
import InputNode from '../core/InputNode.js';
import { Vector3 } from 'three';
class Vector3Node extends InputNode {
constructor( value = new Vector3() ) {
super( 'vec3' );
this.value = value;
}
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;
export default Vector3Node;

View File

@@ -0,0 +1,45 @@
import InputNode from '../core/InputNode.js';
import { Vector4 } from 'three';
class Vector4Node extends InputNode {
constructor( value = new Vector4() ) {
super( 'vec4' );
this.value = value;
}
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;
export default Vector4Node;

View File

@@ -0,0 +1,59 @@
import ContextNode from '../core/ContextNode.js';
import VarNode from '../core/VarNode.js';
import Vector3Node from '../inputs/Vector3Node.js';
import OperatorNode from '../math/OperatorNode.js';
import { PhysicalLightingModel } from '../functions/BSDFs.js';
class LightContextNode extends ContextNode {
constructor( node ) {
super( node );
}
getNodeType( /*builder*/ ) {
return 'vec3';
}
generate( builder ) {
const material = builder.material;
let lightingModel = null;
if ( material.isMeshStandardMaterial === true ) {
lightingModel = PhysicalLightingModel;
}
const directDiffuse = new VarNode( new Vector3Node(), 'DirectDiffuse', 'vec3' );
const directSpecular = new VarNode( new Vector3Node(), 'DirectSpecular', 'vec3' );
this.context.directDiffuse = directDiffuse;
this.context.directSpecular = directSpecular;
if ( lightingModel !== null ) {
this.context.lightingModel = lightingModel;
}
// add code
const type = this.getNodeType( builder );
super.generate( builder, type );
const totalLight = new OperatorNode( '+', directDiffuse, directSpecular );
return totalLight.build( builder, type );
}
}
export default LightContextNode;

View File

@@ -0,0 +1,79 @@
import Node from '../core/Node.js';
import Object3DNode from '../accessors/Object3DNode.js';
import PositionNode from '../accessors/PositionNode.js';
import ColorNode from '../inputs/ColorNode.js';
import FloatNode from '../inputs/FloatNode.js';
import OperatorNode from '../math/OperatorNode.js';
import MathNode from '../math/MathNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { getDistanceAttenuation } from '../functions/BSDFs.js';
import { Color } from 'three';
class LightNode extends Node {
constructor( light = null ) {
super( 'vec3' );
this.updateType = NodeUpdateType.Object;
this.light = light;
this.colorNode = new ColorNode( new Color() );
this.lightCutoffDistanceNode = new FloatNode( 0 );
this.lightDecayExponentNode = new FloatNode( 0 );
}
update( /* frame */ ) {
this.colorNode.value.copy( this.light.color ).multiplyScalar( this.light.intensity );
this.lightCutoffDistanceNode.value = this.light.distance;
this.lightDecayExponentNode.value = this.light.decay;
}
generate( builder ) {
const lightPositionView = new Object3DNode( Object3DNode.VIEW_POSITION );
const positionView = new PositionNode( PositionNode.VIEW );
const lVector = new OperatorNode( '-', lightPositionView, positionView );
const lightDirection = new MathNode( MathNode.NORMALIZE, lVector );
const lightDistance = new MathNode( MathNode.LENGTH, lVector );
const lightAttenuation = getDistanceAttenuation( {
lightDistance,
cutoffDistance: this.lightCutoffDistanceNode,
decayExponent: this.lightDecayExponentNode
} );
const lightColor = new OperatorNode( '*', this.colorNode, lightAttenuation );
lightPositionView.object3d = this.light;
const lightingModelFunction = builder.context.lightingModel;
if ( lightingModelFunction !== undefined ) {
const directDiffuse = builder.context.directDiffuse;
const directSpecular = builder.context.directSpecular;
lightingModelFunction( {
lightDirection,
lightColor,
directDiffuse,
directSpecular
}, builder );
}
}
}
export default LightNode;

View File

@@ -0,0 +1,44 @@
import Node from '../core/Node.js';
import LightNode from './LightNode.js';
class LightsNode extends Node {
constructor( lightNodes = [] ) {
super( 'vec3' );
this.lightNodes = lightNodes;
}
generate( builder ) {
const lightNodes = this.lightNodes;
for ( const lightNode of lightNodes ) {
lightNode.build( builder );
}
return 'vec3( 0.0 )';
}
static fromLights( lights ) {
const lightNodes = [];
for ( const light of lights ) {
lightNodes.push( new LightNode( light ) );
}
return new LightsNode( lightNodes );
}
}
export default LightsNode;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,46 @@
import NodeMaterial from './NodeMaterial.js';
import { LineBasicMaterial } from 'three';
const defaultValues = new LineBasicMaterial();
class LineBasicNodeMaterial extends NodeMaterial {
constructor( parameters ) {
super();
this.colorNode = null;
this.opacityNode = null;
this.alphaTestNode = null;
this.lightNode = null;
this.positionNode = null;
this.setDefaultValues( defaultValues );
this.setValues( parameters );
}
copy( source ) {
this.colorNode = source.colorNode;
this.opacityNode = source.opacityNode;
this.alphaTestNode = source.alphaTestNode;
this.lightNode = source.lightNode;
this.positionNode = source.positionNode;
return super.copy( source );
}
}
LineBasicNodeMaterial.prototype.isNodeMaterial = true;
export default LineBasicNodeMaterial;

View File

@@ -0,0 +1,33 @@
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,
MeshBasicNodeMaterial,
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 );
};

View File

@@ -0,0 +1,46 @@
import NodeMaterial from './NodeMaterial.js';
import { MeshBasicMaterial } from 'three';
const defaultValues = new MeshBasicMaterial();
class MeshBasicNodeMaterial extends NodeMaterial {
constructor( parameters ) {
super();
this.colorNode = null;
this.opacityNode = null;
this.alphaTestNode = null;
this.lightNode = null;
this.positionNode = null;
this.setDefaultValues( defaultValues );
this.setValues( parameters );
}
copy( source ) {
this.colorNode = source.colorNode;
this.opacityNode = source.opacityNode;
this.alphaTestNode = source.alphaTestNode;
this.lightNode = source.lightNode;
this.positionNode = source.positionNode;
return super.copy( source );
}
}
MeshBasicNodeMaterial.prototype.isNodeMaterial = true;
export default MeshBasicNodeMaterial;

View File

@@ -0,0 +1,68 @@
import NodeMaterial from './NodeMaterial.js';
import { MeshStandardMaterial } from 'three';
const defaultValues = new MeshStandardMaterial();
export default class MeshStandardNodeMaterial extends NodeMaterial {
constructor( parameters ) {
super();
this.colorNode = null;
this.opacityNode = null;
this.alphaTestNode = null;
this.normalNode = null;
this.emissiveNode = null;
this.metalnessNode = null;
this.roughnessNode = null;
this.clearcoatNode = null;
this.clearcoatRoughnessNode = null;
this.envNode = null;
this.lightNode = null;
this.positionNode = null;
this.setDefaultValues( defaultValues );
this.setValues( parameters );
}
copy( source ) {
this.colorNode = source.colorNode;
this.opacityNode = source.opacityNode;
this.alphaTestNode = source.alphaTestNode;
this.normalNode = source.normalNode;
this.emissiveNode = source.emissiveNode;
this.metalnessNode = source.metalnessNode;
this.roughnessNode = source.roughnessNode;
this.clearcoatNode = source.clearcoatNode;
this.clearcoatRoughnessNode = source.clearcoatRoughnessNode;
this.envNode = source.envNode;
this.lightNode = source.lightNode;
this.positionNode = source.positionNode;
return super.copy( source );
}
}
MeshStandardNodeMaterial.prototype.isNodeMaterial = true;

View File

@@ -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;

View File

@@ -0,0 +1,50 @@
import NodeMaterial from './NodeMaterial.js';
import { PointsMaterial } from 'three';
const defaultValues = new PointsMaterial();
class PointsNodeMaterial extends NodeMaterial {
constructor( parameters ) {
super();
this.colorNode = null;
this.opacityNode = null;
this.alphaTestNode = null;
this.lightNode = null;
this.sizeNode = null;
this.positionNode = null;
this.setDefaultValues( defaultValues );
this.setValues( parameters );
}
copy( source ) {
this.colorNode = source.colorNode;
this.opacityNode = source.opacityNode;
this.alphaTestNode = source.alphaTestNode;
this.lightNode = source.lightNode;
this.sizeNode = source.sizeNode;
this.positionNode = source.positionNode;
return super.copy( source );
}
}
PointsNodeMaterial.prototype.isNodeMaterial = true;
export default PointsNodeMaterial;

View File

@@ -0,0 +1,60 @@
import Node from '../core/Node.js';
import PropertyNode from '../core/PropertyNode.js';
import ContextNode from '../core/ContextNode.js';
class CondNode extends Node {
constructor( node, ifNode, elseNode ) {
super();
this.node = node;
this.ifNode = ifNode;
this.elseNode = elseNode;
}
getNodeType( builder ) {
const ifType = this.ifNode.getNodeType( builder );
const elseType = this.elseNode.getNodeType( builder );
if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) {
return elseType;
}
return ifType;
}
generate( builder ) {
const type = this.getNodeType( builder );
const context = { temp: false };
const nodeProperty = new PropertyNode( null, type ).build( builder );
const nodeSnippet = new ContextNode( this.node/*, context*/ ).build( builder, 'bool' ),
ifSnippet = new ContextNode( this.ifNode, context ).build( builder, type ),
elseSnippet = new ContextNode( this.elseNode, context ).build( builder, type );
builder.addFlowCode( `if ( ${nodeSnippet} ) {
\t\t${nodeProperty} = ${ifSnippet};
\t} else {
\t\t${nodeProperty} = ${elseSnippet};
\t}` );
return nodeProperty;
}
}
export default CondNode;

View File

@@ -0,0 +1,258 @@
import TempNode from '../core/TempNode.js';
import ExpressionNode from '../core/ExpressionNode.js';
import JoinNode from '../utils/JoinNode.js';
import SplitNode from '../utils/SplitNode.js';
import OperatorNode from './OperatorNode.js';
class MathNode extends TempNode {
// 1 input
static RAD = 'radians';
static DEG = 'degrees';
static EXP = 'exp';
static EXP2 = 'exp2';
static LOG = 'log';
static LOG2 = 'log2';
static SQRT = 'sqrt';
static INV_SQRT = 'inversesqrt';
static FLOOR = 'floor';
static CEIL = 'ceil';
static NORMALIZE = 'normalize';
static FRACT = 'fract';
static SIN = 'sin';
static COS = 'cos';
static TAN = 'tan';
static ASIN = 'asin';
static ACOS = 'acos';
static ATAN = 'atan';
static ABS = 'abs';
static SIGN = 'sign';
static LENGTH = 'length';
static NEGATE = 'negate';
static INVERT = 'invert';
static DFDX = 'dFdx';
static DFDY = 'dFdy';
static SATURATE = 'saturate';
static ROUND = 'round';
// 2 inputs
static MIN = 'min';
static MAX = 'max';
static MOD = 'mod';
static STEP = 'step';
static REFLECT = 'reflect';
static DISTANCE = 'distance';
static DOT = 'dot';
static CROSS = 'cross';
static POW = 'pow';
static TRANSFORM_DIRECTION = 'transformDirection';
// 3 inputs
static MIX = 'mix';
static CLAMP = 'clamp';
static REFRACT = 'refract';
static SMOOTHSTEP = 'smoothstep';
static FACEFORWARD = 'faceforward';
constructor( method, aNode, bNode = null, cNode = null ) {
super();
this.method = method;
this.aNode = aNode;
this.bNode = bNode;
this.cNode = cNode;
}
getInputType( builder ) {
const aType = this.aNode.getNodeType( builder );
const bType = this.bNode ? this.bNode.getNodeType( builder ) : null;
const cType = this.cNode ? this.cNode.getNodeType( builder ) : null;
const aLen = builder.getTypeLength( aType );
const bLen = builder.getTypeLength( bType );
const cLen = builder.getTypeLength( cType );
if ( aLen > bLen && aLen > cLen ) {
return aType;
} else if ( bLen > cLen ) {
return bType;
} else if ( cLen > aLen ) {
return cType;
}
return aType;
}
getNodeType( builder ) {
const method = this.method;
if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) {
return 'float';
} else if ( method === MathNode.CROSS ) {
return 'vec3';
} else {
return this.getInputType( builder );
}
}
generate( builder, output ) {
const method = this.method;
const type = this.getNodeType( builder );
const inputType = this.getInputType( builder );
const a = this.aNode;
const b = this.bNode;
const c = this.cNode;
const isWebGL = builder.renderer.isWebGLRenderer === true;
if ( isWebGL && ( method === MathNode.DFDX || method === MathNode.DFDY ) && output === 'vec3' ) {
// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988
return new JoinNode( [
new MathNode( method, new SplitNode( a, 'x' ) ),
new MathNode( method, new SplitNode( a, 'y' ) ),
new MathNode( method, new SplitNode( a, 'z' ) )
] ).build( builder );
} else if ( method === MathNode.TRANSFORM_DIRECTION ) {
// dir can be either a direction vector or a normal vector
// upper-left 3x3 of matrix is assumed to be orthogonal
let tA = a;
let tB = b;
if ( builder.isMatrix( tA.getNodeType( builder ) ) ) {
tB = new ExpressionNode( `${ builder.getType( 'vec4' ) }( ${ tB.build( builder, 'vec3' ) }, 0.0 )`, 'vec4' );
} else {
tA = new ExpressionNode( `${ builder.getType( 'vec4' ) }( ${ tA.build( builder, 'vec3' ) }, 0.0 )`, 'vec4' );
}
const mulNode = new SplitNode( new OperatorNode( '*', tA, tB ), 'xyz' );
return new MathNode( MathNode.NORMALIZE, mulNode ).build( builder );
} else if ( method === MathNode.SATURATE ) {
return builder.format( `clamp( ${ a.build( builder, inputType ) }, 0.0, 1.0 )`, type, output );
} else if ( method === MathNode.NEGATE ) {
return builder.format( '( -' + a.build( builder, inputType ) + ' )', type, output );
} else if ( method === MathNode.INVERT ) {
return builder.format( '( 1.0 - ' + a.build( builder, inputType ) + ' )', type, output );
} else {
const params = [];
if ( method === MathNode.CROSS ) {
params.push(
a.build( builder, type ),
b.build( builder, type )
);
} else if ( method === MathNode.STEP ) {
params.push(
a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ),
b.build( builder, inputType )
);
} else if ( ( isWebGL && ( method === MathNode.MIN || method === MathNode.MAX ) ) || method === MathNode.MOD ) {
params.push(
a.build( builder, inputType ),
b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? 'float' : inputType )
);
} else if ( method === MathNode.REFRACT ) {
params.push(
a.build( builder, inputType ),
b.build( builder, inputType ),
c.build( builder, 'float' )
);
} else if ( method === MathNode.MIX ) {
params.push(
a.build( builder, inputType ),
b.build( builder, inputType ),
c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType )
);
} else {
params.push( a.build( builder, inputType ) );
if ( c !== null ) {
params.push( b.build( builder, inputType ), c.build( builder, inputType ) );
} else if ( b !== null ) {
params.push( b.build( builder, inputType ) );
}
}
return builder.format( `${ builder.getMethod( method ) }( ${params.join( ', ' )} )`, type, output );
}
}
serialize( data ) {
super.serialize( data );
data.method = this.method;
}
deserialize( data ) {
super.deserialize( data );
this.method = data.method;
}
}
export default MathNode;

View File

@@ -0,0 +1,194 @@
import TempNode from '../core/TempNode.js';
class OperatorNode extends TempNode {
constructor( op, aNode, bNode, ...params ) {
super();
this.op = op;
if ( params.length > 0 ) {
let finalBNode = bNode;
for ( let i = 0; i < params.length; i ++ ) {
finalBNode = new OperatorNode( op, finalBNode, params[ i ] );
}
bNode = finalBNode;
}
this.aNode = aNode;
this.bNode = bNode;
}
getNodeType( builder, output ) {
const op = this.op;
const aNode = this.aNode;
const bNode = this.bNode;
const typeA = aNode.getNodeType( builder );
const typeB = bNode.getNodeType( builder );
if ( typeA === 'void' || typeB === 'void' ) {
return 'void';
} else if ( op === '=' || op === '%' ) {
return typeA;
} else if ( op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) {
return 'int';
} else if ( op === '==' || op === '&&' || op === '||' || op === '^^' ) {
return 'bool';
} else if ( op === '<=' || op === '>=' || op === '<' || op === '>' ) {
const length = builder.getTypeLength( output );
return length > 1 ? `bvec${ length }` : 'bool';
} else {
if ( typeA === 'float' && builder.isMatrix( typeB ) ) {
return typeB;
} else if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) {
// matrix x vector
return builder.getVectorFromMatrix( typeA );
} else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) {
// vector x matrix
return builder.getVectorFromMatrix( typeB );
} else if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
// anytype x anytype: use the greater length vector
return typeB;
}
return typeA;
}
}
generate( builder, output ) {
const op = this.op;
const aNode = this.aNode;
const bNode = this.bNode;
const type = this.getNodeType( builder, output );
let typeA = null;
let typeB = null;
if ( type !== 'void' ) {
typeA = aNode.getNodeType( builder );
typeB = bNode.getNodeType( builder );
if ( op === '=' ) {
typeB = typeA;
} else if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) {
// matrix x vector
typeB = builder.getVectorFromMatrix( typeA );
} else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) {
// vector x matrix
typeA = builder.getVectorFromMatrix( typeB );
} else {
// anytype x anytype
typeA = typeB = type;
}
} else {
typeA = typeB = type;
}
const a = aNode.build( builder, typeA );
const b = bNode.build( builder, typeB );
const outputLength = builder.getTypeLength( output );
if ( output !== 'void' ) {
if ( op === '=' ) {
builder.addFlowCode( `${a} ${this.op} ${b}` );
return a;
} else if ( op === '>' && outputLength > 1 ) {
return builder.format( `${ builder.getMethod( 'greaterThan' ) }( ${a}, ${b} )`, type, output );
} else if ( op === '<=' && outputLength > 1 ) {
return builder.format( `${ builder.getMethod( 'lessThanEqual' ) }( ${a}, ${b} )`, type, output );
} else {
return builder.format( `( ${a} ${this.op} ${b} )`, type, output );
}
} else if ( typeA !== 'void' ) {
return builder.format( `${a} ${this.op} ${b}`, type, output );
}
}
serialize( data ) {
super.serialize( data );
data.op = this.op;
}
deserialize( data ) {
super.deserialize( data );
this.op = data.op;
}
}
export default OperatorNode;

View File

@@ -0,0 +1,137 @@
import NodeFunction from '../core/NodeFunction.js';
import NodeFunctionInput from '../core/NodeFunctionInput.js';
const declarationRegexp = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i;
const propertiesRegexp = /[a-z_0-9]+/ig;
const pragmaMain = '#pragma main';
const parse = ( source ) => {
source = source.trim();
const pragmaMainIndex = source.indexOf( pragmaMain );
const mainCode = pragmaMainIndex !== - 1 ? source.slice( pragmaMainIndex + pragmaMain.length ) : source;
const declaration = mainCode.match( declarationRegexp );
if ( declaration !== null && declaration.length === 5 ) {
// tokenizer
const inputsCode = declaration[ 4 ];
const propsMatches = [];
let nameMatch = null;
while ( ( nameMatch = propertiesRegexp.exec( inputsCode ) ) !== null ) {
propsMatches.push( nameMatch );
}
// parser
const inputs = [];
let i = 0;
while ( i < propsMatches.length ) {
const isConst = propsMatches[ i ][ 0 ] === 'const';
if ( isConst === true ) {
i ++;
}
let qualifier = propsMatches[ i ][ 0 ];
if ( qualifier === 'in' || qualifier === 'out' || qualifier === 'inout' ) {
i ++;
} else {
qualifier = '';
}
const type = propsMatches[ i ++ ][ 0 ];
let count = Number.parseInt( propsMatches[ i ][ 0 ] );
if ( Number.isNaN( count ) === false ) i ++;
else count = null;
const name = propsMatches[ i ++ ][ 0 ];
inputs.push( new NodeFunctionInput( type, name, count, qualifier, isConst ) );
}
//
const blockCode = mainCode.substring( declaration[ 0 ].length );
const name = declaration[ 3 ] !== undefined ? declaration[ 3 ] : '';
const type = declaration[ 2 ];
const presicion = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
const headerCode = pragmaMainIndex !== - 1 ? source.slice( 0, pragmaMainIndex ) : '';
return {
type,
inputs,
name,
presicion,
inputsCode,
blockCode,
headerCode
};
} else {
throw new Error( 'FunctionNode: Function is not a GLSL code.' );
}
};
class GLSLNodeFunction extends NodeFunction {
constructor( source ) {
const { type, inputs, name, presicion, inputsCode, blockCode, headerCode } = parse( source );
super( type, inputs, name, presicion );
this.inputsCode = inputsCode;
this.blockCode = blockCode;
this.headerCode = headerCode;
}
getCode( name = this.name ) {
const headerCode = this.headerCode;
const presicion = this.presicion;
let declarationCode = `${ this.type } ${ name } ( ${ this.inputsCode.trim() } )`;
if ( presicion !== '' ) {
declarationCode = `${ presicion } ${ declarationCode }`;
}
return headerCode + declarationCode + this.blockCode;
}
}
export default GLSLNodeFunction;

View File

@@ -0,0 +1,14 @@
import NodeParser from '../core/NodeParser.js';
import GLSLNodeFunction from './GLSLNodeFunction.js';
class GLSLNodeParser extends NodeParser {
parseFunction( source ) {
return new GLSLNodeFunction( source );
}
}
export default GLSLNodeParser;

View File

@@ -0,0 +1,89 @@
import NodeFunction from '../core/NodeFunction.js';
import NodeFunctionInput from '../core/NodeFunctionInput.js';
const declarationRegexp = /^fn\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*\-\>\s*([a-z_0-9]+)?/i;
const propertiesRegexp = /[a-z_0-9]+/ig;
const parse = ( source ) => {
source = source.trim();
const declaration = source.match( declarationRegexp );
if ( declaration !== null && declaration.length === 4 ) {
// tokenizer
const inputsCode = declaration[ 2 ];
const propsMatches = [];
let nameMatch = null;
while ( ( nameMatch = propertiesRegexp.exec( inputsCode ) ) !== null ) {
propsMatches.push( nameMatch );
}
// parser
const inputs = [];
let i = 0;
while ( i < propsMatches.length ) {
const name = propsMatches[ i ++ ][ 0 ];
const type = propsMatches[ i ++ ][ 0 ];
propsMatches[ i ++ ][ 0 ]; // precision
inputs.push( new NodeFunctionInput( type, name ) );
}
//
const blockCode = source.substring( declaration[ 0 ].length );
const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
const type = declaration[ 3 ];
return {
type,
inputs,
name,
inputsCode,
blockCode
};
} else {
throw new Error( 'FunctionNode: Function is not a WGSL code.' );
}
};
class WGSLNodeFunction extends NodeFunction {
constructor( source ) {
const { type, inputs, name, inputsCode, blockCode } = parse( source );
super( type, inputs, name );
this.inputsCode = inputsCode;
this.blockCode = blockCode;
}
getCode( name = this.name ) {
return `fn ${ name } ( ${ this.inputsCode.trim() } ) -> ${ this.type }` + this.blockCode;
}
}
export default WGSLNodeFunction;

View File

@@ -0,0 +1,14 @@
import NodeParser from '../core/NodeParser.js';
import WGSLNodeFunction from './WGSLNodeFunction.js';
class WGSLNodeParser extends NodeParser {
parseFunction( source ) {
return new WGSLNodeFunction( source );
}
}
export default WGSLNodeParser;

View File

@@ -0,0 +1,36 @@
import Node from '../core/Node.js';
import UVNode from '../accessors/UVNode.js';
import { ShaderNode, add, mul, floor, mod, sign } from '../ShaderNode.js';
const checkerShaderNode = new ShaderNode( ( inputs ) => {
const uv = mul( inputs.uv, 2.0 );
const cx = floor( uv.x );
const cy = floor( uv.y );
const result = mod( add( cx, cy ), 2.0 );
return sign( result );
} );
class CheckerNode extends Node {
constructor( uvNode = new UVNode() ) {
super( 'float' );
this.uvNode = uvNode;
}
generate( builder ) {
return checkerShaderNode( { uv: this.uvNode } ).build( builder );
}
}
export default CheckerNode;

View File

@@ -0,0 +1,31 @@
import Node from '../core/Node.js';
class ArrayElementNode extends Node {
constructor( node, indexNode ) {
super();
this.node = node;
this.indexNode = indexNode;
}
getNodeType( builder ) {
return this.node.getNodeType( builder );
}
generate( builder ) {
const nodeSnippet = this.node.build( builder );
const indexSnippet = this.indexNode.build( builder, 'int' );
return `${nodeSnippet}[ ${indexSnippet} ]`;
}
}
export default ArrayElementNode;

View File

@@ -0,0 +1,33 @@
import Node from '../core/Node.js';
class ConvertNode extends Node {
constructor( node, convertTo ) {
super();
this.node = node;
this.convertTo = convertTo;
}
getNodeType( /*builder*/ ) {
return this.convertTo;
}
generate( builder ) {
const convertTo = this.convertTo;
const convertToSnippet = builder.getType( convertTo );
const nodeSnippet = this.node.build( builder, convertTo );
return `${ convertToSnippet }( ${ nodeSnippet } )`;
}
}
export default ConvertNode;

View File

@@ -0,0 +1,42 @@
import Node from '../core/Node.js';
class JoinNode extends Node {
constructor( nodes = [] ) {
super();
this.nodes = nodes;
}
getNodeType( builder ) {
return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) );
}
generate( builder ) {
const type = this.getNodeType( builder );
const nodes = this.nodes;
const snippetValues = [];
for ( let i = 0; i < nodes.length; i ++ ) {
const input = nodes[ i ];
const inputSnippet = input.build( builder );
snippetValues.push( inputSnippet );
}
return `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`;
}
}
export default JoinNode;

View File

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

View File

@@ -0,0 +1,86 @@
import Node from '../core/Node.js';
import { vector } from '../core/NodeBuilder.js';
class SplitNode extends Node {
constructor( node, components = 'x' ) {
super();
this.node = node;
this.components = components;
}
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 );
}
generate( builder ) {
const node = this.node;
const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) );
if ( nodeTypeLength > 1 ) {
let type = null;
const componentsLength = this.getVectorLength();
if ( componentsLength >= nodeTypeLength ) {
// need expand the input node
type = builder.getTypeFromLength( this.getVectorLength() );
}
const nodeSnippet = node.build( builder, type );
return `${nodeSnippet}.${this.components}`;
} else {
// ignore components if node is a float
return node.build( builder );
}
}
serialize( data ) {
super.serialize( data );
data.components = this.components;
}
deserialize( data ) {
super.deserialize( data );
this.components = data.components;
}
}
export default SplitNode;

View File

@@ -0,0 +1,58 @@
import Node from '../core/Node.js';
import FloatNode from '../inputs/FloatNode.js';
import UVNode from '../accessors/UVNode.js';
import MathNode from '../math/MathNode.js';
import OperatorNode from '../math/OperatorNode.js';
import SplitNode from '../utils/SplitNode.js';
import JoinNode from '../utils/JoinNode.js';
class SpriteSheetUVNode extends Node {
constructor( countNode, uvNode = new UVNode(), frameNode = new FloatNode( 0 ).setConst( true ) ) {
super( 'vec2' );
this.countNode = countNode;
this.uvNode = uvNode;
this.frameNode = frameNode;
}
generate( builder ) {
const count = this.countNode;
const uv = this.uvNode;
const frame = this.frameNode;
const one = new FloatNode( 1 ).setConst( true );
const width = new SplitNode( count, 'x' );
const height = new SplitNode( count, 'y' );
const total = new OperatorNode( '*', width, height );
const roundFrame = new MathNode( MathNode.FLOOR, new MathNode( MathNode.MOD, frame, total ) );
const frameNum = new OperatorNode( '+', roundFrame, one );
const cell = new MathNode( MathNode.MOD, roundFrame, width );
const row = new MathNode( MathNode.CEIL, new OperatorNode( '/', frameNum, width ) );
const rowInv = new OperatorNode( '-', height, row );
const scale = new OperatorNode( '/', one, count );
const uvFrameOffset = new JoinNode( [
new OperatorNode( '*', cell, new SplitNode( scale, 'x' ) ),
new OperatorNode( '*', rowInv, new SplitNode( scale, 'y' ) )
] );
const uvScale = new OperatorNode( '*', uv, scale );
const uvFrame = new OperatorNode( '+', uvScale, uvFrameOffset );
return uvFrame.build( builder, this.getNodeType( builder ) );
}
}
export default SpriteSheetUVNode;

View File

@@ -0,0 +1,64 @@
import FloatNode from '../inputs/FloatNode.js';
import { NodeUpdateType } from '../core/constants.js';
class TimerNode extends FloatNode {
static LOCAL = 'local';
static GLOBAL = 'global';
static DELTA = 'delta';
constructor( scope = TimerNode.LOCAL ) {
super();
this.scope = scope;
this.scale = 1;
this.updateType = NodeUpdateType.Frame;
}
update( frame ) {
const scope = this.scope;
const scale = this.scale;
if ( scope === TimerNode.LOCAL ) {
this.value += frame.deltaTime * scale;
} else if ( scope === TimerNode.DELTA ) {
this.value = frame.deltaTime * scale;
} else {
// global
this.value = frame.time * scale;
}
}
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;