import TempNode from '../core/TempNode.js'; import { sub, mul, div } from './OperatorNode.js'; import { addNodeClass } from '../core/Node.js'; import { addNodeElement, nodeObject, nodeProxy, float, vec3, vec4 } from '../shadernode/ShaderNode.js'; class MathNode extends TempNode { 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.isMatrix( aType ) ? 0 : builder.getTypeLength( aType ); const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType ); const cLen = builder.isMatrix( cType ) ? 0 : 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 if ( method === MathNode.ALL ) { return 'bool'; } else if ( method === MathNode.EQUALS ) { return builder.changeComponentType( this.aNode.getNodeType( builder ), 'bool' ); } else if ( method === MathNode.MOD ) { return this.aNode.getNodeType( builder ); } 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 ( 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 = vec4( vec3( tB ), 0.0 ); } else { tA = vec4( vec3( tA ), 0.0 ); } const mulNode = mul( tA, tB ).xyz; return normalize( mulNode ).build( builder, output ); } else if ( method === MathNode.NEGATE ) { return builder.format( '( - ' + a.build( builder, inputType ) + ' )', type, output ); } else if ( method === MathNode.ONE_MINUS ) { return sub( 1.0, a ).build( builder, output ); } else if ( method === MathNode.RECIPROCAL ) { return div( 1.0, a ).build( builder, output ); } else if ( method === MathNode.DIFFERENCE ) { return abs( sub( a, b ) ).build( builder, output ); } else { const params = []; if ( method === MathNode.CROSS || method === MathNode.MOD ) { 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 ( b !== null ) params.push( b.build( builder, inputType ) ); if ( c !== null ) params.push( c.build( builder, inputType ) ); } return builder.format( `${ builder.getMethod( method, type ) }( ${params.join( ', ' )} )`, type, output ); } } serialize( data ) { super.serialize( data ); data.method = this.method; } deserialize( data ) { super.deserialize( data ); this.method = data.method; } } // 1 input MathNode.ALL = 'all'; MathNode.ANY = 'any'; MathNode.EQUALS = 'equals'; MathNode.RADIANS = 'radians'; MathNode.DEGREES = 'degrees'; MathNode.EXP = 'exp'; MathNode.EXP2 = 'exp2'; MathNode.LOG = 'log'; MathNode.LOG2 = 'log2'; MathNode.SQRT = 'sqrt'; MathNode.INVERSE_SQRT = 'inversesqrt'; MathNode.FLOOR = 'floor'; MathNode.CEIL = 'ceil'; MathNode.NORMALIZE = 'normalize'; MathNode.FRACT = 'fract'; MathNode.SIN = 'sin'; MathNode.COS = 'cos'; MathNode.TAN = 'tan'; MathNode.ASIN = 'asin'; MathNode.ACOS = 'acos'; MathNode.ATAN = 'atan'; MathNode.ABS = 'abs'; MathNode.SIGN = 'sign'; MathNode.LENGTH = 'length'; MathNode.NEGATE = 'negate'; MathNode.ONE_MINUS = 'oneMinus'; MathNode.DFDX = 'dFdx'; MathNode.DFDY = 'dFdy'; MathNode.ROUND = 'round'; MathNode.RECIPROCAL = 'reciprocal'; MathNode.TRUNC = 'trunc'; MathNode.FWIDTH = 'fwidth'; MathNode.BITCAST = 'bitcast'; // 2 inputs MathNode.ATAN2 = 'atan2'; MathNode.MIN = 'min'; MathNode.MAX = 'max'; MathNode.MOD = 'mod'; MathNode.STEP = 'step'; MathNode.REFLECT = 'reflect'; MathNode.DISTANCE = 'distance'; MathNode.DIFFERENCE = 'difference'; MathNode.DOT = 'dot'; MathNode.CROSS = 'cross'; MathNode.POW = 'pow'; MathNode.TRANSFORM_DIRECTION = 'transformDirection'; // 3 inputs MathNode.MIX = 'mix'; MathNode.CLAMP = 'clamp'; MathNode.REFRACT = 'refract'; MathNode.SMOOTHSTEP = 'smoothstep'; MathNode.FACEFORWARD = 'faceforward'; export default MathNode; export const EPSILON = float( 1e-6 ); export const INFINITY = float( 1e6 ); export const PI = float( Math.PI ); export const PI2 = float( Math.PI * 2 ); export const all = nodeProxy( MathNode, MathNode.ALL ); export const any = nodeProxy( MathNode, MathNode.ANY ); export const equals = nodeProxy( MathNode, MathNode.EQUALS ); export const radians = nodeProxy( MathNode, MathNode.RADIANS ); export const degrees = nodeProxy( MathNode, MathNode.DEGREES ); export const exp = nodeProxy( MathNode, MathNode.EXP ); export const exp2 = nodeProxy( MathNode, MathNode.EXP2 ); export const log = nodeProxy( MathNode, MathNode.LOG ); export const log2 = nodeProxy( MathNode, MathNode.LOG2 ); export const sqrt = nodeProxy( MathNode, MathNode.SQRT ); export const inverseSqrt = nodeProxy( MathNode, MathNode.INVERSE_SQRT ); export const floor = nodeProxy( MathNode, MathNode.FLOOR ); export const ceil = nodeProxy( MathNode, MathNode.CEIL ); export const normalize = nodeProxy( MathNode, MathNode.NORMALIZE ); export const fract = nodeProxy( MathNode, MathNode.FRACT ); export const sin = nodeProxy( MathNode, MathNode.SIN ); export const cos = nodeProxy( MathNode, MathNode.COS ); export const tan = nodeProxy( MathNode, MathNode.TAN ); export const asin = nodeProxy( MathNode, MathNode.ASIN ); export const acos = nodeProxy( MathNode, MathNode.ACOS ); export const atan = nodeProxy( MathNode, MathNode.ATAN ); export const abs = nodeProxy( MathNode, MathNode.ABS ); export const sign = nodeProxy( MathNode, MathNode.SIGN ); export const length = nodeProxy( MathNode, MathNode.LENGTH ); export const negate = nodeProxy( MathNode, MathNode.NEGATE ); export const oneMinus = nodeProxy( MathNode, MathNode.ONE_MINUS ); export const dFdx = nodeProxy( MathNode, MathNode.DFDX ); export const dFdy = nodeProxy( MathNode, MathNode.DFDY ); export const round = nodeProxy( MathNode, MathNode.ROUND ); export const reciprocal = nodeProxy( MathNode, MathNode.RECIPROCAL ); export const trunc = nodeProxy( MathNode, MathNode.TRUNC ); export const fwidth = nodeProxy( MathNode, MathNode.FWIDTH ); export const bitcast = nodeProxy( MathNode, MathNode.BITCAST ); export const atan2 = nodeProxy( MathNode, MathNode.ATAN2 ); export const min = nodeProxy( MathNode, MathNode.MIN ); export const max = nodeProxy( MathNode, MathNode.MAX ); export const mod = nodeProxy( MathNode, MathNode.MOD ); export const step = nodeProxy( MathNode, MathNode.STEP ); export const reflect = nodeProxy( MathNode, MathNode.REFLECT ); export const distance = nodeProxy( MathNode, MathNode.DISTANCE ); export const difference = nodeProxy( MathNode, MathNode.DIFFERENCE ); export const dot = nodeProxy( MathNode, MathNode.DOT ); export const cross = nodeProxy( MathNode, MathNode.CROSS ); export const pow = nodeProxy( MathNode, MathNode.POW ); export const pow2 = nodeProxy( MathNode, MathNode.POW, 2 ); export const pow3 = nodeProxy( MathNode, MathNode.POW, 3 ); export const pow4 = nodeProxy( MathNode, MathNode.POW, 4 ); export const transformDirection = nodeProxy( MathNode, MathNode.TRANSFORM_DIRECTION ); export const cbrt = ( a ) => mul( sign( a ), pow( abs( a ), 1.0 / 3.0 ) ); export const lengthSq = ( a ) => dot( a, a ); export const mix = nodeProxy( MathNode, MathNode.MIX ); export const clamp = ( value, low = 0, high = 1 ) => nodeObject( new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) ) ); export const saturate = ( value ) => clamp( value ); export const refract = nodeProxy( MathNode, MathNode.REFRACT ); export const smoothstep = nodeProxy( MathNode, MathNode.SMOOTHSTEP ); export const faceForward = nodeProxy( MathNode, MathNode.FACEFORWARD ); export const mixElement = ( t, e1, e2 ) => mix( e1, e2, t ); export const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x ); addNodeElement( 'all', all ); addNodeElement( 'any', any ); addNodeElement( 'equals', equals ); addNodeElement( 'radians', radians ); addNodeElement( 'degrees', degrees ); addNodeElement( 'exp', exp ); addNodeElement( 'exp2', exp2 ); addNodeElement( 'log', log ); addNodeElement( 'log2', log2 ); addNodeElement( 'sqrt', sqrt ); addNodeElement( 'inverseSqrt', inverseSqrt ); addNodeElement( 'floor', floor ); addNodeElement( 'ceil', ceil ); addNodeElement( 'normalize', normalize ); addNodeElement( 'fract', fract ); addNodeElement( 'sin', sin ); addNodeElement( 'cos', cos ); addNodeElement( 'tan', tan ); addNodeElement( 'asin', asin ); addNodeElement( 'acos', acos ); addNodeElement( 'atan', atan ); addNodeElement( 'abs', abs ); addNodeElement( 'sign', sign ); addNodeElement( 'length', length ); addNodeElement( 'lengthSq', lengthSq ); addNodeElement( 'negate', negate ); addNodeElement( 'oneMinus', oneMinus ); addNodeElement( 'dFdx', dFdx ); addNodeElement( 'dFdy', dFdy ); addNodeElement( 'round', round ); addNodeElement( 'reciprocal', reciprocal ); addNodeElement( 'trunc', trunc ); addNodeElement( 'fwidth', fwidth ); addNodeElement( 'atan2', atan2 ); addNodeElement( 'min', min ); addNodeElement( 'max', max ); addNodeElement( 'mod', mod ); addNodeElement( 'step', step ); addNodeElement( 'reflect', reflect ); addNodeElement( 'distance', distance ); addNodeElement( 'dot', dot ); addNodeElement( 'cross', cross ); addNodeElement( 'pow', pow ); addNodeElement( 'pow2', pow2 ); addNodeElement( 'pow3', pow3 ); addNodeElement( 'pow4', pow4 ); addNodeElement( 'transformDirection', transformDirection ); addNodeElement( 'mix', mixElement ); addNodeElement( 'clamp', clamp ); addNodeElement( 'refract', refract ); addNodeElement( 'smoothstep', smoothstepElement ); addNodeElement( 'faceForward', faceForward ); addNodeElement( 'difference', difference ); addNodeElement( 'saturate', saturate ); addNodeElement( 'cbrt', cbrt ); addNodeClass( 'MathNode', MathNode );