UNPKG

three

Version:

JavaScript 3D library

740 lines (514 loc) 17 kB
import { WebGLCoordinateSystem } from '../../constants.js'; import TempNode from '../core/TempNode.js'; import { addMethodChaining, int, nodeProxy } from '../tsl/TSLCore.js'; /** * This node represents basic mathematical and logical operations like addition, * subtraction or comparisons (e.g. `equal()`). * * @augments TempNode */ class OperatorNode extends TempNode { static get type() { return 'OperatorNode'; } /** * Constructs a new operator node. * * @param {string} op - The operator. * @param {Node} aNode - The first input. * @param {Node} bNode - The second input. * @param {...Node} params - Additional input parameters. */ constructor( op, aNode, bNode, ...params ) { super(); if ( params.length > 0 ) { let finalOp = new OperatorNode( op, aNode, bNode ); for ( let i = 0; i < params.length - 1; i ++ ) { finalOp = new OperatorNode( op, finalOp, params[ i ] ); } aNode = finalOp; bNode = params[ params.length - 1 ]; } /** * The operator. * * @type {string} */ this.op = op; /** * The first input. * * @type {Node} */ this.aNode = aNode; /** * The second input. * * @type {Node} */ this.bNode = bNode; /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isOperatorNode = true; } /** * This method is overwritten since the node type is inferred from the operator * and the input node types. * * @param {NodeBuilder} builder - The current node builder. * @param {string} output - The current output string. * @return {string} The node type. */ getNodeType( builder, output ) { const op = this.op; const aNode = this.aNode; const bNode = this.bNode; const typeA = aNode.getNodeType( builder ); const typeB = typeof bNode !== 'undefined' ? bNode.getNodeType( builder ) : null; if ( typeA === 'void' || typeB === 'void' ) { return 'void'; } else if ( op === '%' ) { return typeA; } else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) { return builder.getIntegerType( typeA ); } else if ( op === '!' || op === '==' || op === '!=' || op === '&&' || op === '||' || op === '^^' ) { return 'bool'; } else if ( op === '<' || op === '>' || op === '<=' || op === '>=' ) { const typeLength = output ? builder.getTypeLength( output ) : Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) ); return typeLength > 1 ? `bvec${ typeLength }` : 'bool'; } else { // Handle matrix operations if ( builder.isMatrix( typeA ) ) { if ( typeB === 'float' ) { return typeA; // matrix * scalar = matrix } else if ( builder.isVector( typeB ) ) { return builder.getVectorFromMatrix( typeA ); // matrix * vector } else if ( builder.isMatrix( typeB ) ) { return typeA; // matrix * matrix } } else if ( builder.isMatrix( typeB ) ) { if ( typeA === 'float' ) { return typeB; // scalar * matrix = matrix } else if ( builder.isVector( typeA ) ) { return builder.getVectorFromMatrix( typeB ); // vector * matrix } } // Handle non-matrix cases 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 = typeof bNode !== 'undefined' ? bNode.getNodeType( builder ) : null; if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) { if ( builder.isVector( typeA ) ) { typeB = typeA; } else if ( typeA !== typeB ) { typeA = typeB = 'float'; } } else if ( op === '>>' || op === '<<' ) { typeA = type; typeB = builder.changeComponentType( typeB, 'uint' ); } else if ( op === '%' ) { typeA = type; typeB = builder.isInteger( typeA ) && builder.isInteger( typeB ) ? typeB : typeA; } else if ( builder.isMatrix( typeA ) ) { if ( typeB === 'float' ) { // Keep matrix type for typeA, but ensure typeB stays float typeB = 'float'; } else if ( builder.isVector( typeB ) ) { // matrix x vector typeB = builder.getVectorFromMatrix( typeA ); } else if ( builder.isMatrix( typeB ) ) { // matrix x matrix - keep both types } else { typeA = typeB = type; } } else if ( builder.isMatrix( typeB ) ) { if ( typeA === 'float' ) { // Keep matrix type for typeB, but ensure typeA stays float typeA = 'float'; } else if ( builder.isVector( typeA ) ) { // vector x matrix typeA = builder.getVectorFromMatrix( typeB ); } else { typeA = typeB = type; } } else { // anytype x anytype typeA = typeB = type; } } else { typeA = typeB = type; } const a = aNode.build( builder, typeA ); const b = typeof bNode !== 'undefined' ? bNode.build( builder, typeB ) : null; const outputLength = builder.getTypeLength( output ); const fnOpSnippet = builder.getFunctionOperator( op ); if ( output !== 'void' ) { const isGLSL = builder.renderer.coordinateSystem === WebGLCoordinateSystem; if ( op === '==' ) { if ( isGLSL ) { if ( outputLength > 1 ) { return builder.format( `${ builder.getMethod( 'equal', output ) }( ${ a }, ${ b } )`, type, output ); } else { return builder.format( `( ${ a } ${ op } ${ b } )`, type, output ); } } else { // WGSL if ( outputLength > 1 || ! builder.isVector( typeA ) ) { return builder.format( `( ${ a } == ${ b } )`, type, output ); } else { return builder.format( `all( ${ a } == ${ b } )`, type, output ); } } } else if ( op === '<' && outputLength > 1 ) { if ( isGLSL ) { return builder.format( `${ builder.getMethod( 'lessThan', output ) }( ${ a }, ${ b } )`, type, output ); } else { // WGSL return builder.format( `( ${ a } < ${ b } )`, type, output ); } } else if ( op === '<=' && outputLength > 1 ) { if ( isGLSL ) { return builder.format( `${ builder.getMethod( 'lessThanEqual', output ) }( ${ a }, ${ b } )`, type, output ); } else { // WGSL return builder.format( `( ${ a } <= ${ b } )`, type, output ); } } else if ( op === '>' && outputLength > 1 ) { if ( isGLSL ) { return builder.format( `${ builder.getMethod( 'greaterThan', output ) }( ${ a }, ${ b } )`, type, output ); } else { // WGSL return builder.format( `( ${ a } > ${ b } )`, type, output ); } } else if ( op === '>=' && outputLength > 1 ) { if ( isGLSL ) { return builder.format( `${ builder.getMethod( 'greaterThanEqual', output ) }( ${ a }, ${ b } )`, type, output ); } else { // WGSL return builder.format( `( ${ a } >= ${ b } )`, type, output ); } } else if ( op === '%' ) { if ( builder.isInteger( typeB ) ) { return builder.format( `( ${ a } % ${ b } )`, type, output ); } else { return builder.format( `${ builder.getMethod( 'mod', type ) }( ${ a }, ${ b } )`, type, output ); } } else if ( op === '!' || op === '~' ) { return builder.format( `(${op}${a})`, typeA, output ); } else if ( fnOpSnippet ) { return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); } else { // Handle matrix operations if ( builder.isMatrix( typeA ) && typeB === 'float' ) { return builder.format( `( ${ b } ${ op } ${ a } )`, type, output ); } else if ( typeA === 'float' && builder.isMatrix( typeB ) ) { return builder.format( `${ a } ${ op } ${ b }`, type, output ); } else { let snippet = `( ${ a } ${ op } ${ b } )`; if ( ! isGLSL && type === 'bool' && builder.isVector( typeA ) && builder.isVector( typeB ) ) { snippet = `all${ snippet }`; } return builder.format( snippet, type, output ); } } } else if ( typeA !== 'void' ) { if ( fnOpSnippet ) { return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); } else { if ( builder.isMatrix( typeA ) && typeB === 'float' ) { return builder.format( `${ b } ${ op } ${ a }`, type, output ); } else { return builder.format( `${ a } ${ 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; /** * Returns the addition of two or more value. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @param {...Node} params - Additional input parameters. * @returns {OperatorNode} */ export const add = /*@__PURE__*/ nodeProxy( OperatorNode, '+' ).setParameterLength( 2, Infinity ).setName( 'add' ); /** * Returns the subtraction of two or more value. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @param {...Node} params - Additional input parameters. * @returns {OperatorNode} */ export const sub = /*@__PURE__*/ nodeProxy( OperatorNode, '-' ).setParameterLength( 2, Infinity ).setName( 'sub' ); /** * Returns the multiplication of two or more value. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @param {...Node} params - Additional input parameters. * @returns {OperatorNode} */ export const mul = /*@__PURE__*/ nodeProxy( OperatorNode, '*' ).setParameterLength( 2, Infinity ).setName( 'mul' ); /** * Returns the division of two or more value. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @param {...Node} params - Additional input parameters. * @returns {OperatorNode} */ export const div = /*@__PURE__*/ nodeProxy( OperatorNode, '/' ).setParameterLength( 2, Infinity ).setName( 'div' ); /** * Computes the remainder of dividing the first node by the second one. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const mod = /*@__PURE__*/ nodeProxy( OperatorNode, '%' ).setParameterLength( 2 ).setName( 'mod' ); /** * Checks if two nodes are equal. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const equal = /*@__PURE__*/ nodeProxy( OperatorNode, '==' ).setParameterLength( 2 ).setName( 'equal' ); /** * Checks if two nodes are not equal. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const notEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '!=' ).setParameterLength( 2 ).setName( 'notEqual' ); /** * Checks if the first node is less than the second. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const lessThan = /*@__PURE__*/ nodeProxy( OperatorNode, '<' ).setParameterLength( 2 ).setName( 'lessThan' ); /** * Checks if the first node is greater than the second. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const greaterThan = /*@__PURE__*/ nodeProxy( OperatorNode, '>' ).setParameterLength( 2 ).setName( 'greaterThan' ); /** * Checks if the first node is less than or equal to the second. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const lessThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '<=' ).setParameterLength( 2 ).setName( 'lessThanEqual' ); /** * Checks if the first node is greater than or equal to the second. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const greaterThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '>=' ).setParameterLength( 2 ).setName( 'greaterThanEqual' ); /** * Performs a logical AND operation on multiple nodes. * * @tsl * @function * @param {...Node} nodes - The input nodes to be combined using AND. * @returns {OperatorNode} */ export const and = /*@__PURE__*/ nodeProxy( OperatorNode, '&&' ).setParameterLength( 2, Infinity ).setName( 'and' ); /** * Performs a logical OR operation on multiple nodes. * * @tsl * @function * @param {...Node} nodes - The input nodes to be combined using OR. * @returns {OperatorNode} */ export const or = /*@__PURE__*/ nodeProxy( OperatorNode, '||' ).setParameterLength( 2, Infinity ).setName( 'or' ); /** * Performs logical NOT on a node. * * @tsl * @function * @param {Node} value - The value. * @returns {OperatorNode} */ export const not = /*@__PURE__*/ nodeProxy( OperatorNode, '!' ).setParameterLength( 1 ).setName( 'not' ); /** * Performs logical XOR on two nodes. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const xor = /*@__PURE__*/ nodeProxy( OperatorNode, '^^' ).setParameterLength( 2 ).setName( 'xor' ); /** * Performs bitwise AND on two nodes. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const bitAnd = /*@__PURE__*/ nodeProxy( OperatorNode, '&' ).setParameterLength( 2 ).setName( 'bitAnd' ); /** * Performs bitwise NOT on a node. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const bitNot = /*@__PURE__*/ nodeProxy( OperatorNode, '~' ).setParameterLength( 2 ).setName( 'bitNot' ); /** * Performs bitwise OR on two nodes. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const bitOr = /*@__PURE__*/ nodeProxy( OperatorNode, '|' ).setParameterLength( 2 ).setName( 'bitOr' ); /** * Performs bitwise XOR on two nodes. * * @tsl * @function * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const bitXor = /*@__PURE__*/ nodeProxy( OperatorNode, '^' ).setParameterLength( 2 ).setName( 'bitXor' ); /** * Shifts a node to the left. * * @tsl * @function * @param {Node} a - The node to shift. * @param {Node} b - The value to shift. * @returns {OperatorNode} */ export const shiftLeft = /*@__PURE__*/ nodeProxy( OperatorNode, '<<' ).setParameterLength( 2 ).setName( 'shiftLeft' ); /** * Shifts a node to the right. * * @tsl * @function * @param {Node} a - The node to shift. * @param {Node} b - The value to shift. * @returns {OperatorNode} */ export const shiftRight = /*@__PURE__*/ nodeProxy( OperatorNode, '>>' ).setParameterLength( 2 ).setName( 'shiftRight' ); addMethodChaining( 'add', add ); addMethodChaining( 'sub', sub ); addMethodChaining( 'mul', mul ); addMethodChaining( 'div', div ); addMethodChaining( 'mod', mod ); addMethodChaining( 'equal', equal ); addMethodChaining( 'notEqual', notEqual ); addMethodChaining( 'lessThan', lessThan ); addMethodChaining( 'greaterThan', greaterThan ); addMethodChaining( 'lessThanEqual', lessThanEqual ); addMethodChaining( 'greaterThanEqual', greaterThanEqual ); addMethodChaining( 'and', and ); addMethodChaining( 'or', or ); addMethodChaining( 'not', not ); addMethodChaining( 'xor', xor ); addMethodChaining( 'bitAnd', bitAnd ); addMethodChaining( 'bitNot', bitNot ); addMethodChaining( 'bitOr', bitOr ); addMethodChaining( 'bitXor', bitXor ); addMethodChaining( 'shiftLeft', shiftLeft ); addMethodChaining( 'shiftRight', shiftRight ); /** * @tsl * @function * @deprecated since r168. Use {@link mod} instead. * * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const remainder = ( a, b ) => { // @deprecated, r168 console.warn( 'THREE.TSL: "remainder()" is deprecated. Use "mod( int( ... ) )" instead.' ); return mod( a, b ); }; /** * @tsl * @function * @deprecated since r175. Use {@link mod} instead. * * @param {Node} a - The first input. * @param {Node} b - The second input. * @returns {OperatorNode} */ export const modInt = ( a, b ) => { // @deprecated, r175 console.warn( 'THREE.TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.' ); return mod( int( a ), int( b ) ); }; addMethodChaining( 'remainder', remainder ); addMethodChaining( 'modInt', modInt );