three
Version:
JavaScript 3D library
427 lines (280 loc) • 14 kB
JavaScript
// core nodes
import PropertyNode from './core/PropertyNode.js';
import VarNode from './core/VarNode.js';
import AttributeNode from './core/AttributeNode.js';
import ConstNode from './core/ConstNode.js';
import UniformNode from './core/UniformNode.js';
// accessor nodes
import BufferNode from './accessors/BufferNode.js';
import PositionNode from './accessors/PositionNode.js';
import NormalNode from './accessors/NormalNode.js';
import CameraNode from './accessors/CameraNode.js';
import ModelNode from './accessors/ModelNode.js';
import TextureNode from './accessors/TextureNode.js';
import UVNode from './accessors/UVNode.js';
// math nodes
import OperatorNode from './math/OperatorNode.js';
import CondNode from './math/CondNode.js';
import MathNode from './math/MathNode.js';
// util nodes
import ArrayElementNode from './utils/ArrayElementNode.js';
import ConvertNode from './utils/ConvertNode.js';
import JoinNode from './utils/JoinNode.js';
import SplitNode from './utils/SplitNode.js';
// utils
import { getValueFromType } from './core/NodeUtils.js';
const NodeHandler = {
construct( NodeClosure, params ) {
const inputs = params.shift();
return NodeClosure( new 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 new ShaderNodeObject( new SplitNode( node, prop ) );
} else if ( /^\d+$/.test( prop ) === true ) {
// accessing array
return new ShaderNodeObject( new ArrayElementNode( node, uint( Number( prop ) ) ) );
}
}
return node[ prop ];
}
};
const nodeObjects = new WeakMap();
const ShaderNodeObject = function ( obj ) {
const type = typeof obj;
if ( ( type === 'number' ) || ( type === 'boolean' ) ) {
return new ShaderNodeObject( getAutoTypedConstNode( obj ) );
} 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 );
nodeObjects.set( nodeObject, nodeObject );
}
return nodeObject;
}
}
return obj;
};
const ShaderNodeObjects = function ( objects ) {
for ( const name in objects ) {
objects[ name ] = new ShaderNodeObject( objects[ name ] );
}
return objects;
};
const getShaderNodeArray = ( array ) => {
const len = array.length;
for ( let i = 0; i < len; i ++ ) {
array[ i ] = new ShaderNodeObject( array[ i ] );
}
return array;
};
const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null ) {
if ( scope === null ) {
return ( ...params ) => {
return new ShaderNodeObject( new NodeClass( ...getShaderNodeArray( params ) ) );
};
} else if ( factor === null ) {
return ( ...params ) => {
return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ) ) );
};
} else {
factor = new ShaderNodeObject( factor );
return ( ...params ) => {
return new ShaderNodeObject( new NodeClass( scope, ...getShaderNodeArray( params ), factor ) );
};
}
};
const ShaderNodeScript = function ( jsFunc ) {
return ( inputs, builder ) => {
new ShaderNodeObjects( inputs );
return new ShaderNodeObject( jsFunc( inputs, builder ) );
};
};
const bools = [ false, true ];
const uints = [ 0, 1, 2, 3 ];
const ints = [ -1, -2 ];
const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2), Math.PI / 2 ];
const boolsCacheMap = new Map();
for ( let bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) );
const uintsCacheMap = new Map();
for ( let uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) );
const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) );
for ( let int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) );
const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) );
for ( let float of floats ) floatsCacheMap.set( float, new ConstNode( float ) );
for ( let float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) );
const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] );
const getAutoTypedConstNode = ( value ) => {
if ( constNodesCacheMap.has( value ) ) {
return constNodesCacheMap.get( value );
} else if ( value.isNode === true ) {
return value;
} else {
return new ConstNode( value );
}
};
const ConvertType = function ( type, cacheMap = null ) {
return ( ...params ) => {
if ( params.length === 0 ) {
return nodeObject( new ConstNode( getValueFromType( type ), type ) );
} else {
if ( type === 'color' && params[ 0 ].isNode !== true ) {
params = [ getValueFromType( type, ...params ) ];
}
if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) {
return cacheMap.get( params[ 0 ] );
}
const nodes = params.map( getAutoTypedConstNode );
return nodeObject( new ConvertNode( nodes.length === 1 ? nodes[ 0 ] : new JoinNode( nodes ), type ) );
}
};
};
//
// Node Material Shader Syntax
//
export const ShaderNode = new Proxy( ShaderNodeScript, NodeHandler );
export const nodeObject = ( val ) => {
return new ShaderNodeObject( val );
};
export const uniform = ( value ) => {
// TODO: get ConstNode from .traverse() in the future
value = value.isNode === true ? value.node?.value || value.value : value;
return nodeObject( new UniformNode( value, value.nodeType ) );
};
export const label = ( node, name ) => {
node = nodeObject( node );
if ( node.isVarNode === true ) {
node.name = name;
return node;
}
return nodeObject( new VarNode( node, name ) );
};
export const temp = ( node ) => nodeObject( new VarNode( nodeObject( node ) ) );
export const color = new ConvertType( 'color' );
export const float = new ConvertType( 'float', floatsCacheMap );
export const int = new ConvertType( 'int', intsCacheMap );
export const uint = new ConvertType( 'uint', uintsCacheMap );
export const bool = new ConvertType( 'bool', boolsCacheMap );
export const vec2 = new ConvertType( 'vec2' );
export const ivec2 = new ConvertType( 'ivec2' );
export const uvec2 = new ConvertType( 'uvec2' );
export const bvec2 = new ConvertType( 'bvec2' );
export const vec3 = new ConvertType( 'vec3' );
export const ivec3 = new ConvertType( 'ivec3' );
export const uvec3 = new ConvertType( 'uvec3' );
export const bvec3 = new ConvertType( 'bvec3' );
export const vec4 = new ConvertType( 'vec4' );
export const ivec4 = new ConvertType( 'ivec4' );
export const uvec4 = new ConvertType( 'uvec4' );
export const bvec4 = new ConvertType( 'bvec4' );
export const mat3 = new ConvertType( 'mat3' );
export const imat3 = new ConvertType( 'imat3' );
export const umat3 = new ConvertType( 'umat3' );
export const bmat3 = new ConvertType( 'bmat3' );
export const mat4 = new ConvertType( 'mat4' );
export const imat4 = new ConvertType( 'imat4' );
export const umat4 = new ConvertType( 'umat4' );
export const bmat4 = new ConvertType( 'bmat4' );
export const join = ( ...params ) => nodeObject( new JoinNode( getShaderNodeArray( params ) ) );
export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) );
export const attribute = ( ...params ) => nodeObject( new AttributeNode( ...params ) );
export const buffer = ( ...params ) => nodeObject( new BufferNode( ...params ) );
export const texture = ( ...params ) => nodeObject( new TextureNode( ...params ) );
export const sampler = ( texture ) => nodeObject( new ConvertNode( texture.isNode === true ? texture : new TextureNode( texture ), 'sampler' ) );
export const cond = ( ...params ) => nodeObject( new CondNode( ...getShaderNodeArray( params ) ) );
export const addTo = ( varNode, ...params ) => {
varNode.node = add( varNode.node, ...getShaderNodeArray( params ) );
return nodeObject( varNode );
};
export const add = new ShaderNodeProxy( OperatorNode, '+' );
export const sub = new ShaderNodeProxy( OperatorNode, '-' );
export const mul = new ShaderNodeProxy( OperatorNode, '*' );
export const div = new ShaderNodeProxy( OperatorNode, '/' );
export const remainder = new ShaderNodeProxy( OperatorNode, '%' );
export const equal = new ShaderNodeProxy( OperatorNode, '==' );
export const assign = new ShaderNodeProxy( OperatorNode, '=' );
export const lessThan = new ShaderNodeProxy( OperatorNode, '<' );
export const greaterThan = new ShaderNodeProxy( OperatorNode, '>' );
export const lessThanEqual = new ShaderNodeProxy( OperatorNode, '<=' );
export const greaterThanEqual = new ShaderNodeProxy( OperatorNode, '>=' );
export const and = new ShaderNodeProxy( OperatorNode, '&&' );
export const or = new ShaderNodeProxy( OperatorNode, '||' );
export const xor = new ShaderNodeProxy( OperatorNode, '^^' );
export const bitAnd = new ShaderNodeProxy( OperatorNode, '&' );
export const bitOr = new ShaderNodeProxy( OperatorNode, '|' );
export const bitXor = new ShaderNodeProxy( OperatorNode, '^' );
export const shiftLeft = new ShaderNodeProxy( OperatorNode, '<<' );
export const shiftRight = new ShaderNodeProxy( OperatorNode, '>>' );
export const element = new ShaderNodeProxy( ArrayElementNode );
export const normalGeometry = new ShaderNodeObject( new NormalNode( NormalNode.GEOMETRY ) );
export const normalLocal = new ShaderNodeObject( new NormalNode( NormalNode.LOCAL ) );
export const normalWorld = new ShaderNodeObject( new NormalNode( NormalNode.WORLD ) );
export const normalView = new ShaderNodeObject( new NormalNode( NormalNode.VIEW ) );
export const transformedNormalView = new ShaderNodeObject( new VarNode( new NormalNode( NormalNode.VIEW ), 'TransformedNormalView', 'vec3' ) );
export const positionLocal = new ShaderNodeObject( new PositionNode( PositionNode.LOCAL ) );
export const positionWorld = new ShaderNodeObject( new PositionNode( PositionNode.WORLD ) );
export const positionView = new ShaderNodeObject( new PositionNode( PositionNode.VIEW ) );
export const positionViewDirection = new ShaderNodeObject( new PositionNode( PositionNode.VIEW_DIRECTION ) );
export const viewMatrix = new ShaderNodeObject( new ModelNode( ModelNode.VIEW_MATRIX ) );
export const cameraPosition = new ShaderNodeObject( new CameraNode( CameraNode.POSITION ) );
export const diffuseColor = new ShaderNodeObject( new PropertyNode( 'DiffuseColor', 'vec4' ) );
export const roughness = new ShaderNodeObject( new PropertyNode( 'Roughness', 'float' ) );
export const metalness = new ShaderNodeObject( new PropertyNode( 'Metalness', 'float' ) );
export const alphaTest = new ShaderNodeObject( new PropertyNode( 'AlphaTest', 'float' ) );
export const specularColor = new ShaderNodeObject( new PropertyNode( 'SpecularColor', 'color' ) );
export const abs = new ShaderNodeProxy( MathNode, 'abs' );
export const acos = new ShaderNodeProxy( MathNode, 'acos' );
export const asin = new ShaderNodeProxy( MathNode, 'asin' );
export const atan = new ShaderNodeProxy( MathNode, 'atan' );
export const ceil = new ShaderNodeProxy( MathNode, 'ceil' );
export const clamp = new ShaderNodeProxy( MathNode, 'clamp' );
export const cos = new ShaderNodeProxy( MathNode, 'cos' );
export const cross = new ShaderNodeProxy( MathNode, 'cross' );
export const degrees = new ShaderNodeProxy( MathNode, 'degrees' );
export const dFdx = new ShaderNodeProxy( MathNode, 'dFdx' );
export const dFdy = new ShaderNodeProxy( MathNode, 'dFdy' );
export const distance = new ShaderNodeProxy( MathNode, 'distance' );
export const dot = new ShaderNodeProxy( MathNode, 'dot' );
export const exp = new ShaderNodeProxy( MathNode, 'exp' );
export const exp2 = new ShaderNodeProxy( MathNode, 'exp2' );
export const faceforward = new ShaderNodeProxy( MathNode, 'faceforward' );
export const floor = new ShaderNodeProxy( MathNode, 'floor' );
export const fract = new ShaderNodeProxy( MathNode, 'fract' );
export const invert = new ShaderNodeProxy( MathNode, 'invert' );
export const inversesqrt = new ShaderNodeProxy( MathNode, 'inversesqrt' );
export const length = new ShaderNodeProxy( MathNode, 'length' );
export const log = new ShaderNodeProxy( MathNode, 'log' );
export const log2 = new ShaderNodeProxy( MathNode, 'log2' );
export const max = new ShaderNodeProxy( MathNode, 'max' );
export const min = new ShaderNodeProxy( MathNode, 'min' );
export const mix = new ShaderNodeProxy( MathNode, 'mix' );
export const mod = new ShaderNodeProxy( MathNode, 'mod' );
export const negate = new ShaderNodeProxy( MathNode, 'negate' );
export const normalize = new ShaderNodeProxy( MathNode, 'normalize' );
export const pow = new ShaderNodeProxy( MathNode, 'pow' );
export const pow2 = new ShaderNodeProxy( MathNode, 'pow', 2 );
export const pow3 = new ShaderNodeProxy( MathNode, 'pow', 3 );
export const pow4 = new ShaderNodeProxy( MathNode, 'pow', 4 );
export const radians = new ShaderNodeProxy( MathNode, 'radians' );
export const reflect = new ShaderNodeProxy( MathNode, 'reflect' );
export const refract = new ShaderNodeProxy( MathNode, 'refract' );
export const round = new ShaderNodeProxy( MathNode, 'round' );
export const saturate = new ShaderNodeProxy( MathNode, 'saturate' );
export const sign = new ShaderNodeProxy( MathNode, 'sign' );
export const sin = new ShaderNodeProxy( MathNode, 'sin' );
export const smoothstep = new ShaderNodeProxy( MathNode, 'smoothstep' );
export const sqrt = new ShaderNodeProxy( MathNode, 'sqrt' );
export const step = new ShaderNodeProxy( MathNode, 'step' );
export const tan = new ShaderNodeProxy( MathNode, 'tan' );
export const transformDirection = new ShaderNodeProxy( MathNode, 'transformDirection' );
export const EPSILON = float( 1e-6 );
export const INFINITY = float( 1e6 );