UNPKG

three

Version:

JavaScript 3D library

2,348 lines (1,295 loc) 982 kB
/** * @license * Copyright 2010-2024 Three.js Authors * SPDX-License-Identifier: MIT */ import { Color, Vector2, Vector3, Vector4, Matrix3, Matrix4, EventDispatcher, MathUtils, ColorManagement, SRGBTransfer, NoToneMapping, StaticDrawUsage, InterleavedBuffer, DynamicDrawUsage, InterleavedBufferAttribute, NoColorSpace, UnsignedIntType, IntType, WebGLCoordinateSystem, BackSide, CubeReflectionMapping, CubeRefractionMapping, WebGPUCoordinateSystem, TangentSpaceNormalMap, ObjectSpaceNormalMap, InstancedInterleavedBuffer, InstancedBufferAttribute, DataArrayTexture, FloatType, FramebufferTexture, LinearMipmapLinearFilter, DepthTexture, Material, NormalBlending, PointsMaterial, LineBasicMaterial, LineDashedMaterial, NoBlending, MeshNormalMaterial, WebGLCubeRenderTarget, BoxGeometry, Mesh, Scene, LinearFilter, CubeCamera, CubeTexture, EquirectangularReflectionMapping, EquirectangularRefractionMapping, AddOperation, MixOperation, MultiplyOperation, MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, Texture, MeshStandardMaterial, MeshPhysicalMaterial, MeshToonMaterial, MeshMatcapMaterial, SpriteMaterial, ShadowMaterial, Uint32BufferAttribute, Uint16BufferAttribute, DoubleSide, DepthStencilFormat, DepthFormat, UnsignedInt248Type, UnsignedByteType, RenderTarget, Plane, Object3D, HalfFloatType, LinearMipMapLinearFilter, OrthographicCamera, BufferGeometry, Float32BufferAttribute, BufferAttribute, UVMapping, Euler, LinearSRGBColorSpace, LessCompare, VSMShadowMap, RGFormat, BasicShadowMap, SphereGeometry, CubeUVReflectionMapping, PerspectiveCamera, RGBAFormat, LinearMipmapNearestFilter, NearestMipmapLinearFilter, Float16BufferAttribute, REVISION, SRGBColorSpace, PCFShadowMap, FrontSide, Frustum, DataTexture, RedIntegerFormat, RedFormat, RGIntegerFormat, RGBIntegerFormat, RGBFormat, RGBAIntegerFormat, UnsignedShortType, ByteType, ShortType, createCanvasElement, AddEquation, SubtractEquation, ReverseSubtractEquation, ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor, OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor, CullFaceNone, CullFaceBack, CullFaceFront, CustomBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NotEqualDepth, GreaterDepth, GreaterEqualDepth, EqualDepth, LessEqualDepth, LessDepth, AlwaysDepth, NeverDepth, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedInt5999Type, AlphaFormat, LuminanceFormat, LuminanceAlphaFormat, RGB_S3TC_DXT1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_PVRTC_2BPPV1_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_BPTC_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping, NearestFilter, NearestMipmapNearestFilter, NeverCompare, AlwaysCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, warnOnce, NotEqualStencilFunc, GreaterStencilFunc, GreaterEqualStencilFunc, EqualStencilFunc, LessEqualStencilFunc, LessStencilFunc, AlwaysStencilFunc, NeverStencilFunc, DecrementWrapStencilOp, IncrementWrapStencilOp, DecrementStencilOp, IncrementStencilOp, InvertStencilOp, ReplaceStencilOp, ZeroStencilOp, KeepStencilOp, MaxEquation, MinEquation, SpotLight, PointLight, DirectionalLight, RectAreaLight, AmbientLight, HemisphereLight, LightProbe, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping, Group, Loader, FileLoader, MaterialLoader, ObjectLoader } from './three.core.js'; export { AdditiveAnimationBlendMode, AnimationAction, AnimationClip, AnimationLoader, AnimationMixer, AnimationObjectGroup, AnimationUtils, ArcCurve, ArrayCamera, ArrowHelper, AttachedBindMode, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BasicDepthPacking, BatchedMesh, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxHelper, BufferGeometryLoader, Cache, Camera, CameraHelper, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CircleGeometry, Clock, ColorKeyframeTrack, CompressedArrayTexture, CompressedCubeTexture, CompressedTexture, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, Controls, CubeTextureLoader, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceFrontBack, Curve, CurvePath, CustomToneMapping, CylinderGeometry, Cylindrical, Data3DTexture, DataTextureLoader, DataUtils, DefaultLoadingManager, DetachedBindMode, DirectionalLightHelper, DiscreteInterpolant, DodecahedronGeometry, DynamicCopyUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, ExtrudeGeometry, Fog, FogExp2, GLBufferAttribute, GLSL1, GLSL3, GridHelper, HemisphereLightHelper, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, ImageUtils, InstancedBufferGeometry, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, KeyframeTrack, LOD, LatheGeometry, Layers, Light, Line, Line3, LineCurve, LineCurve3, LineLoop, LineSegments, LinearInterpolant, LinearMipMapNearestFilter, LinearTransfer, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, MOUSE, Matrix2, MeshDepthMaterial, MeshDistanceMaterial, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NormalAnimationBlendMode, NumberKeyframeTrack, OctahedronGeometry, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, PCFSoftShadowMap, Path, PlaneGeometry, PlaneHelper, PointLightHelper, Points, PolarGridHelper, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RGBADepthPacking, RGBDepthPacking, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGDepthPacking, RawShaderMaterial, Ray, Raycaster, RingGeometry, ShaderMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, Skeleton, SkeletonHelper, SkinnedMesh, Source, Sphere, Spherical, SphericalHarmonics3, SplineCurve, SpotLightHelper, Sprite, StaticCopyUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, TOUCH, TetrahedronGeometry, TextureLoader, TextureUtils, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, VectorKeyframeTrack, VideoTexture, WebGL3DRenderTarget, WebGLArrayRenderTarget, WebGLMultipleRenderTargets, WebGLRenderTarget, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroSlopeEnding } from './three.core.js'; const refreshUniforms = [ 'alphaMap', 'alphaTest', 'anisotropy', 'anisotropyMap', 'anisotropyRotation', 'aoMap', 'attenuationColor', 'attenuationDistance', 'bumpMap', 'clearcoat', 'clearcoatMap', 'clearcoatNormalMap', 'clearcoatNormalScale', 'clearcoatRoughness', 'color', 'dispersion', 'displacementMap', 'emissive', 'emissiveMap', 'envMap', 'gradientMap', 'ior', 'iridescence', 'iridescenceIOR', 'iridescenceMap', 'iridescenceThicknessMap', 'lightMap', 'map', 'matcap', 'metalness', 'metalnessMap', 'normalMap', 'normalScale', 'opacity', 'roughness', 'roughnessMap', 'sheen', 'sheenColor', 'sheenColorMap', 'sheenRoughnessMap', 'shininess', 'specular', 'specularColor', 'specularColorMap', 'specularIntensity', 'specularIntensityMap', 'specularMap', 'thickness', 'transmission', 'transmissionMap' ]; class NodeMaterialObserver { constructor( builder ) { this.renderObjects = new WeakMap(); this.hasNode = this.containsNode( builder ); this.hasAnimation = builder.object.isSkinnedMesh === true; this.refreshUniforms = refreshUniforms; this.renderId = 0; } firstInitialization( renderObject ) { const hasInitialized = this.renderObjects.has( renderObject ); if ( hasInitialized === false ) { this.getRenderObjectData( renderObject ); return true; } return false; } getRenderObjectData( renderObject ) { let data = this.renderObjects.get( renderObject ); if ( data === undefined ) { const { geometry, material, object } = renderObject; data = { material: this.getMaterialData( material ), geometry: { attributes: this.getAttributesData( geometry.attributes ), indexVersion: geometry.index ? geometry.index.version : null, drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count } }, worldMatrix: object.matrixWorld.clone() }; if ( object.center ) { data.center = object.center.clone(); } if ( object.morphTargetInfluences ) { data.morphTargetInfluences = object.morphTargetInfluences.slice(); } if ( renderObject.bundle !== null ) { data.version = renderObject.bundle.version; } if ( data.material.transmission > 0 ) { const { width, height } = renderObject.context; data.bufferWidth = width; data.bufferHeight = height; } this.renderObjects.set( renderObject, data ); } return data; } getAttributesData( attributes ) { const attributesData = {}; for ( const name in attributes ) { const attribute = attributes[ name ]; attributesData[ name ] = { version: attribute.version }; } return attributesData; } containsNode( builder ) { const material = builder.material; for ( const property in material ) { if ( material[ property ] && material[ property ].isNode ) return true; } if ( builder.renderer.nodes.modelViewMatrix !== null || builder.renderer.nodes.modelNormalViewMatrix !== null ) return true; return false; } getMaterialData( material ) { const data = {}; for ( const property of this.refreshUniforms ) { const value = material[ property ]; if ( value === null || value === undefined ) continue; if ( typeof value === 'object' && value.clone !== undefined ) { if ( value.isTexture === true ) { data[ property ] = { id: value.id, version: value.version }; } else { data[ property ] = value.clone(); } } else { data[ property ] = value; } } return data; } equals( renderObject ) { const { object, material, geometry } = renderObject; const renderObjectData = this.getRenderObjectData( renderObject ); // world matrix if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) { renderObjectData.worldMatrix.copy( object.matrixWorld ); return false; } // material const materialData = renderObjectData.material; for ( const property in materialData ) { const value = materialData[ property ]; const mtlValue = material[ property ]; if ( value.equals !== undefined ) { if ( value.equals( mtlValue ) === false ) { value.copy( mtlValue ); return false; } } else if ( mtlValue.isTexture === true ) { if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) { value.id = mtlValue.id; value.version = mtlValue.version; return false; } } else if ( value !== mtlValue ) { materialData[ property ] = mtlValue; return false; } } if ( materialData.transmission > 0 ) { const { width, height } = renderObject.context; if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) { renderObjectData.bufferWidth = width; renderObjectData.bufferHeight = height; return false; } } // geometry const storedGeometryData = renderObjectData.geometry; const attributes = geometry.attributes; const storedAttributes = storedGeometryData.attributes; const storedAttributeNames = Object.keys( storedAttributes ); const currentAttributeNames = Object.keys( attributes ); if ( storedAttributeNames.length !== currentAttributeNames.length ) { renderObjectData.geometry.attributes = this.getAttributesData( attributes ); return false; } // compare each attribute for ( const name of storedAttributeNames ) { const storedAttributeData = storedAttributes[ name ]; const attribute = attributes[ name ]; if ( attribute === undefined ) { // attribute was removed delete storedAttributes[ name ]; return false; } if ( storedAttributeData.version !== attribute.version ) { storedAttributeData.version = attribute.version; return false; } } // check index const index = geometry.index; const storedIndexVersion = storedGeometryData.indexVersion; const currentIndexVersion = index ? index.version : null; if ( storedIndexVersion !== currentIndexVersion ) { storedGeometryData.indexVersion = currentIndexVersion; return false; } // check drawRange if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) { storedGeometryData.drawRange.start = geometry.drawRange.start; storedGeometryData.drawRange.count = geometry.drawRange.count; return false; } // morph targets if ( renderObjectData.morphTargetInfluences ) { let morphChanged = false; for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) { if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) { morphChanged = true; } } if ( morphChanged ) return true; } // center if ( renderObjectData.center ) { if ( renderObjectData.center.equals( object.center ) === false ) { renderObjectData.center.copy( object.center ); return true; } } // bundle if ( renderObject.bundle !== null ) { renderObjectData.version = renderObject.bundle.version; } return true; } needsRefresh( renderObject, nodeFrame ) { if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) ) return true; const { renderId } = nodeFrame; if ( this.renderId !== renderId ) { this.renderId = renderId; return true; } const isStatic = renderObject.object.static === true; const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version; if ( isStatic || isBundle ) return false; const notEqual = this.equals( renderObject ) !== true; return notEqual; } } // cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated. // A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance. // Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. // See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480 // https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js function cyrb53( value, seed = 0 ) { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; if ( value instanceof Array ) { for ( let i = 0, val; i < value.length; i ++ ) { val = value[ i ]; h1 = Math.imul( h1 ^ val, 2654435761 ); h2 = Math.imul( h2 ^ val, 1597334677 ); } } else { for ( let i = 0, ch; i < value.length; i ++ ) { ch = value.charCodeAt( i ); h1 = Math.imul( h1 ^ ch, 2654435761 ); h2 = Math.imul( h2 ^ ch, 1597334677 ); } } h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 ); h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 ); h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 ); h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 ); return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 ); } const hashString = ( str ) => cyrb53( str ); const hashArray = ( array ) => cyrb53( array ); const hash$1 = ( ...params ) => cyrb53( params ); function getCacheKey$1( object, force = false ) { const values = []; if ( object.isNode === true ) { values.push( object.id ); object = object.getSelf(); } for ( const { property, childNode } of getNodeChildren( object ) ) { values.push( values, cyrb53( property.slice( 0, - 4 ) ), childNode.getCacheKey( force ) ); } return cyrb53( values ); } function* getNodeChildren( node, toJSON = false ) { for ( const property in node ) { // Ignore private properties. if ( property.startsWith( '_' ) === true ) continue; const object = node[ property ]; if ( Array.isArray( object ) === true ) { for ( let i = 0; i < object.length; i ++ ) { const child = object[ i ]; if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { yield { property, index: i, childNode: child }; } } } else if ( object && object.isNode === true ) { yield { property, childNode: object }; } else if ( typeof object === 'object' ) { for ( const subProperty in object ) { const child = object[ subProperty ]; if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { yield { property, index: subProperty, childNode: child }; } } } } } const typeFromLength = /*@__PURE__*/ new Map( [ [ 1, 'float' ], [ 2, 'vec2' ], [ 3, 'vec3' ], [ 4, 'vec4' ], [ 9, 'mat3' ], [ 16, 'mat4' ] ] ); function getTypeFromLength( length ) { return typeFromLength.get( length ); } function getLengthFromType( type ) { if ( /float|int|uint/.test( type ) ) return 1; if ( /vec2/.test( type ) ) return 2; if ( /vec3/.test( type ) ) return 3; if ( /vec4/.test( type ) ) return 4; if ( /mat3/.test( type ) ) return 9; if ( /mat4/.test( type ) ) return 16; console.error( 'THREE.TSL: Unsupported type:', type ); } function getValueType( value ) { if ( value === undefined || value === null ) return null; const typeOf = typeof value; if ( value.isNode === true ) { return 'node'; } else if ( typeOf === 'number' ) { return 'float'; } else if ( typeOf === 'boolean' ) { return 'bool'; } else if ( typeOf === 'string' ) { return 'string'; } else if ( typeOf === 'function' ) { return 'shader'; } else if ( value.isVector2 === true ) { return 'vec2'; } else if ( value.isVector3 === true ) { return 'vec3'; } else if ( value.isVector4 === true ) { return 'vec4'; } else if ( value.isMatrix3 === true ) { return 'mat3'; } else if ( value.isMatrix4 === true ) { return 'mat4'; } else if ( value.isColor === true ) { return 'color'; } else if ( value instanceof ArrayBuffer ) { return 'ArrayBuffer'; } return null; } function getValueFromType( type, ...params ) { const last4 = type ? type.slice( - 4 ) : undefined; if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format() if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ]; else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ]; else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ]; } if ( type === 'color' ) { return new Color( ...params ); } else if ( last4 === 'vec2' ) { return new Vector2( ...params ); } else if ( last4 === 'vec3' ) { return new Vector3( ...params ); } else if ( last4 === 'vec4' ) { return new Vector4( ...params ); } else if ( last4 === 'mat3' ) { return new Matrix3( ...params ); } else if ( last4 === 'mat4' ) { return new Matrix4( ...params ); } else if ( type === 'bool' ) { return params[ 0 ] || false; } else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) { return params[ 0 ] || 0; } else if ( type === 'string' ) { return params[ 0 ] || ''; } else if ( type === 'ArrayBuffer' ) { return base64ToArrayBuffer( params[ 0 ] ); } return null; } function arrayBufferToBase64( arrayBuffer ) { let chars = ''; const array = new Uint8Array( arrayBuffer ); for ( let i = 0; i < array.length; i ++ ) { chars += String.fromCharCode( array[ i ] ); } return btoa( chars ); } function base64ToArrayBuffer( base64 ) { return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer; } var NodeUtils = /*#__PURE__*/Object.freeze({ __proto__: null, arrayBufferToBase64: arrayBufferToBase64, base64ToArrayBuffer: base64ToArrayBuffer, getCacheKey: getCacheKey$1, getLengthFromType: getLengthFromType, getNodeChildren: getNodeChildren, getTypeFromLength: getTypeFromLength, getValueFromType: getValueFromType, getValueType: getValueType, hash: hash$1, hashArray: hashArray, hashString: hashString }); const NodeShaderStage = { VERTEX: 'vertex', FRAGMENT: 'fragment' }; const NodeUpdateType = { NONE: 'none', FRAME: 'frame', RENDER: 'render', OBJECT: 'object' }; const NodeType = { BOOLEAN: 'bool', INTEGER: 'int', FLOAT: 'float', VECTOR2: 'vec2', VECTOR3: 'vec3', VECTOR4: 'vec4', MATRIX2: 'mat2', MATRIX3: 'mat3', MATRIX4: 'mat4' }; const NodeAccess = { READ_ONLY: 'readOnly', WRITE_ONLY: 'writeOnly', READ_WRITE: 'readWrite', }; const defaultShaderStages = [ 'fragment', 'vertex' ]; const defaultBuildStages = [ 'setup', 'analyze', 'generate' ]; const shaderStages = [ ...defaultShaderStages, 'compute' ]; const vectorComponents = [ 'x', 'y', 'z', 'w' ]; let _nodeId = 0; class Node extends EventDispatcher { static get type() { return 'Node'; } constructor( nodeType = null ) { super(); this.nodeType = nodeType; this.updateType = NodeUpdateType.NONE; this.updateBeforeType = NodeUpdateType.NONE; this.updateAfterType = NodeUpdateType.NONE; this.uuid = MathUtils.generateUUID(); this.version = 0; this._cacheKey = null; this._cacheKeyVersion = 0; this.global = false; this.isNode = true; Object.defineProperty( this, 'id', { value: _nodeId ++ } ); } set needsUpdate( value ) { if ( value === true ) { this.version ++; } } get type() { return this.constructor.type; } onUpdate( callback, updateType ) { this.updateType = updateType; this.update = callback.bind( this.getSelf() ); return this; } onFrameUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.FRAME ); } onRenderUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.RENDER ); } onObjectUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.OBJECT ); } onReference( callback ) { this.updateReference = callback.bind( this.getSelf() ); return this; } getSelf() { // Returns non-node object. return this.self || this; } updateReference( /*state*/ ) { return this; } isGlobal( /*builder*/ ) { return this.global; } * getChildren() { for ( const { childNode } of getNodeChildren( this ) ) { yield childNode; } } dispose() { this.dispatchEvent( { type: 'dispose' } ); } traverse( callback ) { callback( this ); for ( const childNode of this.getChildren() ) { childNode.traverse( callback ); } } getCacheKey( force = false ) { force = force || this.version !== this._cacheKeyVersion; if ( force === true || this._cacheKey === null ) { this._cacheKey = getCacheKey$1( this, force ); this._cacheKeyVersion = this.version; } return this._cacheKey; } getScope() { return this; } getHash( /*builder*/ ) { return this.uuid; } getUpdateType() { return this.updateType; } getUpdateBeforeType() { return this.updateBeforeType; } getUpdateAfterType() { return this.updateAfterType; } getElementType( builder ) { const type = this.getNodeType( builder ); const elementType = builder.getElementType( type ); return elementType; } getNodeType( builder ) { const nodeProperties = builder.getNodeProperties( this ); if ( nodeProperties.outputNode ) { return nodeProperties.outputNode.getNodeType( builder ); } return this.nodeType; } getShared( builder ) { const hash = this.getHash( builder ); const nodeFromHash = builder.getNodeFromHash( hash ); return nodeFromHash || this; } setup( builder ) { const nodeProperties = builder.getNodeProperties( this ); let index = 0; for ( const childNode of this.getChildren() ) { nodeProperties[ 'node' + index ++ ] = childNode; } // return a outputNode if exists return null; } analyze( builder ) { const usageCount = builder.increaseUsage( this ); if ( usageCount === 1 ) { // node flow children const nodeProperties = builder.getNodeProperties( this ); for ( const childNode of Object.values( nodeProperties ) ) { if ( childNode && childNode.isNode === true ) { childNode.build( builder ); } } } } generate( builder, output ) { const { outputNode } = builder.getNodeProperties( this ); if ( outputNode && outputNode.isNode === true ) { return outputNode.build( builder, output ); } } updateBefore( /*frame*/ ) { console.warn( 'Abstract function.' ); } updateAfter( /*frame*/ ) { console.warn( 'Abstract function.' ); } update( /*frame*/ ) { console.warn( 'Abstract function.' ); } build( builder, output = null ) { const refNode = this.getShared( builder ); if ( this !== refNode ) { return refNode.build( builder, output ); } builder.addNode( this ); builder.addChain( this ); /* Build stages expected results: - "setup" -> Node - "analyze" -> null - "generate" -> String */ let result = null; const buildStage = builder.getBuildStage(); if ( buildStage === 'setup' ) { this.updateReference( builder ); const properties = builder.getNodeProperties( this ); if ( properties.initialized !== true ) { const stackNodesBeforeSetup = builder.stack.nodes.length; properties.initialized = true; properties.outputNode = this.setup( builder ); if ( properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup ) ; for ( const childNode of Object.values( properties ) ) { if ( childNode && childNode.isNode === true ) { childNode.build( builder ); } } } } else if ( buildStage === 'analyze' ) { this.analyze( builder ); } else if ( buildStage === 'generate' ) { const isGenerateOnce = this.generate.length === 1; if ( isGenerateOnce ) { const type = this.getNodeType( builder ); const nodeData = builder.getDataFromNode( this ); result = nodeData.snippet; if ( result === undefined ) { result = this.generate( builder ) || ''; nodeData.snippet = result; } else if ( nodeData.flowCodes !== undefined && builder.context.nodeBlock !== undefined ) { builder.addFlowCodeHierarchy( this, builder.context.nodeBlock ); } result = builder.format( result, type, output ); } else { result = this.generate( builder, output ) || ''; } } builder.removeChain( this ); builder.addSequentialNode( this ); return result; } getSerializeChildren() { return getNodeChildren( this ); } serialize( json ) { const nodeChildren = this.getSerializeChildren(); const inputNodes = {}; for ( const { property, index, childNode } of nodeChildren ) { if ( index !== undefined ) { if ( inputNodes[ property ] === undefined ) { inputNodes[ property ] = Number.isInteger( index ) ? [] : {}; } inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid; } else { inputNodes[ property ] = childNode.toJSON( json.meta ).uuid; } } if ( Object.keys( inputNodes ).length > 0 ) { json.inputNodes = inputNodes; } } deserialize( json ) { if ( json.inputNodes !== undefined ) { const nodes = json.meta.nodes; for ( const property in json.inputNodes ) { if ( Array.isArray( json.inputNodes[ property ] ) ) { const inputArray = []; for ( const uuid of json.inputNodes[ property ] ) { inputArray.push( nodes[ uuid ] ); } this[ property ] = inputArray; } else if ( typeof json.inputNodes[ property ] === 'object' ) { const inputObject = {}; for ( const subProperty in json.inputNodes[ property ] ) { const uuid = json.inputNodes[ property ][ subProperty ]; inputObject[ subProperty ] = nodes[ uuid ]; } this[ property ] = inputObject; } else { 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.6, type: 'Node', generator: 'Node.toJSON' } }; if ( isRoot !== true ) 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; } } class ArrayElementNode extends Node { static get type() { return 'ArrayElementNode'; } // @TODO: If extending from TempNode it breaks webgpu_compute constructor( node, indexNode ) { super(); this.node = node; this.indexNode = indexNode; this.isArrayElementNode = true; } getNodeType( builder ) { return this.node.getElementType( builder ); } generate( builder ) { const nodeSnippet = this.node.build( builder ); const indexSnippet = this.indexNode.build( builder, 'uint' ); return `${nodeSnippet}[ ${indexSnippet} ]`; } } class ConvertNode extends Node { static get type() { return 'ConvertNode'; } constructor( node, convertTo ) { super(); this.node = node; this.convertTo = convertTo; } getNodeType( builder ) { const requestType = this.node.getNodeType( builder ); let convertTo = null; for ( const overloadingType of this.convertTo.split( '|' ) ) { if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) { convertTo = overloadingType; } } return convertTo; } serialize( data ) { super.serialize( data ); data.convertTo = this.convertTo; } deserialize( data ) { super.deserialize( data ); this.convertTo = data.convertTo; } generate( builder, output ) { const node = this.node; const type = this.getNodeType( builder ); const snippet = node.build( builder, type ); return builder.format( snippet, type, output ); } } class TempNode extends Node { static get type() { return 'TempNode'; } constructor( type ) { super( type ); this.isTempNode = true; } hasDependencies( builder ) { return builder.getDataFromNode( this ).usageCount > 1; } build( builder, output ) { const buildStage = builder.getBuildStage(); if ( buildStage === 'generate' ) { const type = builder.getVectorType( this.getNodeType( builder, output ) ); const nodeData = builder.getDataFromNode( this ); if ( nodeData.propertyName !== undefined ) { return builder.format( nodeData.propertyName, type, output ); } else if ( type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) { const snippet = super.build( builder, type ); const nodeVar = builder.getVarFromNode( this, null, type ); const propertyName = builder.getPropertyName( nodeVar ); builder.addLineFlowCode( `${propertyName} = ${snippet}`, this ); nodeData.snippet = snippet; nodeData.propertyName = propertyName; return builder.format( nodeData.propertyName, type, output ); } } return super.build( builder, output ); } } class JoinNode extends TempNode { static get type() { return 'JoinNode'; } constructor( nodes = [], nodeType = null ) { super( nodeType ); this.nodes = nodes; } getNodeType( builder ) { if ( this.nodeType !== null ) { return builder.getVectorType( this.nodeType ); } return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) ); } generate( builder, output ) { const type = this.getNodeType( builder ); const nodes = this.nodes; const primitiveType = builder.getComponentType( type ); const snippetValues = []; for ( const input of nodes ) { let inputSnippet = input.build( builder ); const inputPrimitiveType = builder.getComponentType( input.getNodeType( builder ) ); if ( inputPrimitiveType !== primitiveType ) { inputSnippet = builder.format( inputSnippet, inputPrimitiveType, primitiveType ); } snippetValues.push( inputSnippet ); } const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`; return builder.format( snippet, type, output ); } } const stringVectorComponents = vectorComponents.join( '' ); class SplitNode extends Node { static get type() { return 'SplitNode'; } constructor( node, components = 'x' ) { super(); this.node = node; this.components = components; this.isSplitNode = true; } getVectorLength() { let vectorLength = this.components.length; for ( const c of this.components ) { vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength ); } return vectorLength; } getComponentType( builder ) { return builder.getComponentType( this.node.getNodeType( builder ) ); } getNodeType( builder ) { return builder.getTypeFromLength( this.components.length, this.getComponentType( builder ) ); } generate( builder, output ) { const node = this.node; const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); let snippet = null; if ( nodeTypeLength > 1 ) { let type = null; const componentsLength = this.getVectorLength(); if ( componentsLength >= nodeTypeLength ) { // needed expand the input node type = builder.getTypeFromLength( this.getVectorLength(), this.getComponentType( builder ) ); } const nodeSnippet = node.build( builder, type ); if ( this.components.length === nodeTypeLength && this.components === stringVectorComponents.slice( 0, this.components.length ) ) { // unnecessary swizzle snippet = builder.format( nodeSnippet, type, output ); } else { snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output ); } } else { // ignore .components if .node returns float/integer snippet = node.build( builder, output ); } return snippet; } serialize( data ) { super.serialize( data ); data.components = this.components; } deserialize( data ) { super.deserialize( data ); this.components = data.components; } } class SetNode extends TempNode { static get type() { return 'SetNode'; } constructor( sourceNode, components, targetNode ) { super(); this.sourceNode = sourceNode; this.components = components; this.targetNode = targetNode; } getNodeType( builder ) { return this.sourceNode.getNodeType( builder ); } generate( builder ) { const { sourceNode, components, targetNode } = this; const sourceType = this.getNodeType( builder ); const targetType = builder.getTypeFromLength( components.length, targetNode.getNodeType( builder ) ); const targetSnippet = targetNode.build( builder, targetType ); const sourceSnippet = sourceNode.build( builder, sourceType ); const length = builder.getTypeLength( sourceType ); const snippetValues = []; for ( let i = 0; i < length; i ++ ) { const component = vectorComponents[ i ]; if ( component === components[ 0 ] ) { snippetValues.push( targetSnippet ); i += components.length - 1; } else { snippetValues.push( sourceSnippet + '.' + component ); } } return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; } } class FlipNode extends TempNode { static get type() { return 'FlipNode'; } constructor( sourceNode, components ) { super(); this.sourceNode = sourceNode; this.components = components; } getNodeType( builder ) { return this.sourceNode.getNodeType( builder ); } generate( builder ) { const { components, sourceNode } = this; const sourceType = this.getNodeType( builder ); const sourceSnippet = sourceNode.build( builder ); const sourceCache = builder.getVarFromNode( this ); const sourceProperty = builder.getPropertyName( sourceCache ); builder.addLineFlowCode( sourceProperty + ' = ' + sourceSnippet, this ); const length = builder.getTypeLength( sourceType ); const snippetValues = []; let componentIndex = 0; for ( let i = 0; i < length; i ++ ) { const component = vectorComponents[ i ]; if ( component === components[ componentIndex ] ) { snippetValues.push( '1.0 - ' + ( sourceProperty + '.' + component ) ); componentIndex ++; } else { snippetValues.push( sourceProperty + '.' + component ); } } return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; } } class InputNode extends Node { static get type() { return 'InputNode'; } constructor( value, nodeType = null ) { super( nodeType ); this.isInputNode = true; this.value = value; this.precision = null; } getNodeType( /*builder*/ ) { if ( this.nodeType === null ) { return getValueType( this.value ); } return this.nodeType; } getInputType( builder ) { return this.getNodeType( builder ); } setPrecision( precision ) { this.precision = precision; return this; } serialize( data ) { super.serialize( data ); data.value = this.value; if ( this.value && this.value.toArray ) data.value = this.value.toArray(); data.valueType = getValueType( this.value ); data.nodeType = this.nodeType; if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value ); data.precision = this.precision; } deserialize( data ) { super.deserialize( data ); this.nodeType = data.nodeType; this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value; this.precision = data.precision || null; if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value ); } generate( /*builder, output*/ ) { console.warn( 'Abstract function.' ); } } class ConstNode extends InputNode { static get type() { return 'ConstNode'; } constructor( value, nodeType = null ) { super( value, nodeType ); this.isConstNode = true; } generateConst( builder ) { return builder.generateConst( this.getNodeType( builder ), this.value ); } generate( builder, output ) { const type = this.getNodeType( builder ); return builder.format( this.generateConst( builder ), type, output ); } } // let currentStack = null; const NodeElements = new Map(); function addMethodChaining( name, nodeElement ) { if ( NodeElements.has( name ) ) { console.warn( `Redefinition of method chaining ${ name }` ); return; } if ( typeof nodeElement !== 'function' ) throw new Error( `Node element ${ name } is not a function` ); NodeElements.set( name, nodeElement ); } const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' ); const parseSwizzleAndSort = ( props ) => parseSwizzle( props ).split( '' ).sort().join( '' ); const shaderNodeHandler = { setup( NodeClosure, params ) { const inputs = params.shift(); return NodeClosure( nodeObjects( inputs ), ...params ); }, get( node, prop, nodeObj ) { if ( typeof prop === 'string' && node[ prop ] === undefined ) { if ( node.isStackNode !== true && prop === 'assign' ) { return ( ...params ) => { currentStack.assign( nodeObj, ...params ); return nodeObj; }; } else if ( NodeElements.has( prop ) ) { const nodeElement = NodeElements.get( prop ); return node.isStackNode ? ( ...params ) => nodeObj.add( nodeElement( ...params ) ) : ( ...params ) => nodeElement( nodeObj, ...params ); } else if ( prop === 'self' ) { return node; } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) { const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) ); return node.isStackNode ? ( ...params ) => nodeObj.assign( params[ 0 ], nodeElement( ...params ) ) : ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) ); } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) { // accessing properties ( swizzle ) prop = parseSwizzle( prop ); return nodeObject( new SplitNode( nodeObj, prop ) ); } else if ( /^set[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { // set properties ( swizzle ) and sort to xyzw sequence prop = parseSwizzleAndSort( prop.slice( 3 ).toLowerCase() ); return ( value ) => nodeObject( new SetNode( node, prop, value ) ); } else if ( /^flip[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { // set properties ( swizzle ) and sort to xyzw sequence prop = parseSwizzleAndSort( prop.slice( 4 ).toLowerCase() ); return () => nodeObject( new FlipNode( nodeObject( node ), prop ) ); } else if ( prop === 'width' || prop === 'height' || prop === 'depth' ) { // accessing property if ( prop === 'width' ) prop = 'x'; else if ( prop === 'height' ) prop = 'y'; else if ( prop === 'depth' ) prop = 'z'; return nodeObject( new SplitNode( node, prop ) ); } else if ( /^\d+$/.test( prop ) === true ) { // accessing array return nodeObject( new ArrayElementNode( nodeObj, new ConstNode( Number( prop ), 'uint' ) ) ); } } return Reflect.get( node, prop, nodeObj ); }, set( node, prop, value, nodeObj ) { if ( typeof prop === 'string' && node[ prop ] === undefined ) { // setting properties if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true || prop === 'width' || prop === 'height' || prop === 'depth' || /^\d+$/.test( prop ) === true ) { nodeObj[ prop ].assign( value ); return true; } } return Reflect.set( node, prop, value, nodeObj ); } }; const nodeObjectsCacheMap = new WeakMap(); const nodeBuilderFunctionsCacheMap = new WeakMap(); const ShaderNodeObject = function ( obj, altType = null ) { const type = getValueType( obj ); if ( type === 'node' ) { let nodeObject = nodeObjectsCacheMap.get( obj ); if ( nodeObject === undefined ) { nodeObject = new Proxy( obj, shaderNodeHandler ); nodeObjectsCacheMap.set( obj, nodeObject ); nodeObjectsCacheMap.set( nodeObject, nodeObject ); } return nodeObject; } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) { return nodeObject( getConstNode( obj, altType ) ); } else if ( type === 'shader' ) { return Fn( obj ); } return obj; }; const ShaderNodeObjects = function ( objects, altType = null ) { for ( const name in objects ) { objects[ name ] = nodeObject( objects[ name ], altType ); } return objects; }; const ShaderNodeArray = function ( array, altType = null ) { const len = array.length; for ( let i = 0; i < len; i ++ ) { array[ i ] = nodeObject( array[ i ], altType ); } return array; }; const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) { const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node ); if ( scope === null ) { return ( ...params ) => { return assignNode( new NodeClass( ...nodeArray( params ) ) ); }; } else if ( factor !== null ) { factor = nodeObject( factor ); return ( ...params ) => { return assignNode( new NodeClass( scope, ...nodeArray( params ), factor ) ); }; } else { return ( ...params ) => { return assignNode( new NodeClass( scope, ...nodeArray( params ) ) ); }; } }; const ShaderNodeImmutable = function ( NodeClass, ...params ) { return nodeObject( new NodeClass( ...nodeArray( params ) ) ); }; class ShaderCallNodeInternal extends Node { constructor( shaderNode, inputNodes ) { super(); this.shaderNode = shaderNode; this.inputNodes = inputNodes; } getNodeType( builder ) { return this.shaderNode.nodeType || this.getOutputNode( builder ).getNodeType( builder ); } call( builder ) { const { shaderNode, inputNodes } = this; const properties = builder.getNodeProperties( shaderNode ); if ( properties.onceOutput ) return properties.onceOutput; // let result = null; if ( shaderNode.layout ) { let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get( builder.constructor ); if ( functionNodesCacheMap === undefined ) { functionNodesCacheMap = new WeakMap(); nodeBuilderFunctionsCacheMap.set( builder.constructor, functionNodesCacheMap ); } let functionNode = functionNodesCacheMap.get( shaderNode ); if ( functionNode === undefined ) { functionNode = nodeObject( builder.buildFunctionNode( shaderNode ) ); functionNodesCacheMap.set( shaderNode, functionNode ); } if ( builder.currentFunctionNode !== null ) { builder.currentFunctionNode.includes.push( functionNode ); } result = nodeObject( functionNode.call( inputNodes ) ); } else { const jsFunc = shaderNode.jsFunc; const outputNode = inputNodes !== null ? jsFunc( inputNodes, builder ) : jsFunc( builder ); result = nodeObject( outputNode ); } if ( shaderNode.once ) { properties.onceOutput = result; } return result; } getOutputNode( builder ) { const properties = builder.getNodeProperties( this ); if ( properties.outputNode === null ) { properties.outputNode = this.setupOutput( builder ); } return properties.outputNode; } setup( builder ) { return this.getOutputNode( builder ); } setupOutput( builder ) { builder.addStack(); builder.stack.outputNode = this.call( builder ); return builder.removeStack(); } generate( builder, output ) { const outputNode = this.getOutputNode( builder ); return outputNode.build( builder, output ); } } class ShaderNodeInternal extends Node { constructor( jsFunc, nodeType ) { super( nodeType ); this.jsFunc = jsFunc; this.layout = null; this.global = true; this.once = false; } setLayout( layout ) { this.layout = layout; return this; } call( inputs = null ) { nodeObjects( inputs ); return nodeObject( new ShaderCallNodeInternal( this, inputs ) ); } setup() { return this.call(); } } 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 ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) ); const uintsCacheMap = new Map(); for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) ); const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) ); for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) ); const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) ); for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) ); for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) ); const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] ); const getConstNode = ( value, type ) => { if ( constNodesCacheMap.has( value ) ) { return constNodesCacheMap.get( value ); } else if ( value.isNode === true ) { return value; } else { return new ConstNode( value, type ); } }; const safeGetNodeType = ( node ) => { try { return node.getNodeType(); } catch ( _ ) { return undefined; } }; const ConvertType = function ( type, cacheMap = null ) { return ( ...params ) => { if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => typeof param !== 'object' ) ) ) { params = [ getValueFromType( type, ...params ) ]; } if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) { return nodeObject( cacheMap.get( params[ 0 ] ) ); } if ( params.length === 1 ) { const node = getConstNode( params[ 0 ], type ); if ( safeGetNodeType( node ) === type ) return nodeObject( node ); return nodeObject( new ConvertNode( node, type ) ); } const nodes = params.map( param => getConstNode( param ) ); return nodeObject( new JoinNode( nodes, type ) ); }; }; // exports const defined = ( v ) => typeof v === 'object' && v !== null ? v.value : v; // TODO: remove boolean conversion and defined function // utils const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.no