UNPKG

three

Version:

JavaScript 3D library

2,002 lines (1,383 loc) 1.72 MB
/** * @license * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ import { Color, Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, EventDispatcher, MathUtils, WebGLCoordinateSystem, WebGPUCoordinateSystem, ColorManagement, SRGBTransfer, NoToneMapping, StaticDrawUsage, InterleavedBuffer, InterleavedBufferAttribute, DynamicDrawUsage, NoColorSpace, UnsignedIntType, IntType, NearestFilter, Sphere, BackSide, Euler, CubeReflectionMapping, CubeRefractionMapping, TangentSpaceNormalMap, ObjectSpaceNormalMap, InstancedInterleavedBuffer, InstancedBufferAttribute, DataArrayTexture, FloatType, FramebufferTexture, LinearMipmapLinearFilter, DepthTexture, Material, NormalBlending, LineBasicMaterial, LineDashedMaterial, NoBlending, MeshNormalMaterial, SRGBColorSpace, WebGLCubeRenderTarget, BoxGeometry, Mesh, Scene, LinearFilter, CubeCamera, CubeTexture, EquirectangularReflectionMapping, EquirectangularRefractionMapping, AddOperation, MixOperation, MultiplyOperation, MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, OrthographicCamera, PerspectiveCamera, RenderTarget, CubeUVReflectionMapping, BufferGeometry, BufferAttribute, LinearSRGBColorSpace, RGBAFormat, HalfFloatType, Texture, MeshStandardMaterial, MeshPhysicalMaterial, MeshToonMaterial, MeshMatcapMaterial, SpriteMaterial, PointsMaterial, ShadowMaterial, Uint32BufferAttribute, Uint16BufferAttribute, arrayNeedsUint32, DoubleSide, Camera, DepthStencilFormat, DepthFormat, UnsignedInt248Type, UnsignedByteType, Plane, Object3D, LinearMipMapLinearFilter, Float32BufferAttribute, UVMapping, LessCompare, VSMShadowMap, RGFormat, BasicShadowMap, SphereGeometry, LinearMipmapNearestFilter, NearestMipmapLinearFilter, Float16BufferAttribute, REVISION, ArrayCamera, PlaneGeometry, FrontSide, CustomBlending, AddEquation, ZeroFactor, CylinderGeometry, Quaternion, WebXRController, RAD2DEG, PCFShadowMap, Frustum, DataTexture, RedIntegerFormat, RedFormat, ShortType, ByteType, UnsignedShortType, RGIntegerFormat, RGBIntegerFormat, RGBFormat, RGBAIntegerFormat, warnOnce, createCanvasElement, ReverseSubtractEquation, SubtractEquation, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, DstAlphaFactor, DstColorFactor, SrcAlphaSaturateFactor, SrcAlphaFactor, SrcColorFactor, OneFactor, CullFaceNone, CullFaceBack, CullFaceFront, 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, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, NearestMipmapNearestFilter, NotEqualCompare, GreaterCompare, GreaterEqualCompare, EqualCompare, LessEqualCompare, AlwaysCompare, NeverCompare, 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, ArrowHelper, AttachedBindMode, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BasicDepthPacking, BatchedMesh, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxHelper, BufferGeometryLoader, Cache, CameraHelper, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CircleGeometry, Clock, ColorKeyframeTrack, CompressedArrayTexture, CompressedCubeTexture, CompressedTexture, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, Controls, CubeTextureLoader, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceFrontBack, Curve, CurvePath, CustomToneMapping, 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, MeshDepthMaterial, MeshDistanceMaterial, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NormalAnimationBlendMode, NumberKeyframeTrack, OctahedronGeometry, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, PCFSoftShadowMap, Path, PlaneHelper, PointLightHelper, Points, PolarGridHelper, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RGBADepthPacking, RGBDepthPacking, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGDepthPacking, RawShaderMaterial, Ray, Raycaster, RenderTarget3D, RenderTargetArray, RingGeometry, ShaderMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, Skeleton, SkeletonHelper, SkinnedMesh, Source, Spherical, SphericalHarmonics3, SplineCurve, SpotLightHelper, Sprite, StaticCopyUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, TOUCH, TetrahedronGeometry, TextureLoader, TextureUtils, TimestampQuery, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, VectorKeyframeTrack, VideoFrameTexture, VideoTexture, WebGL3DRenderTarget, WebGLArrayRenderTarget, 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' ]; /** * This class is used by {@link WebGPURenderer} as management component. * It's primary purpose is to determine whether render objects require a * refresh right before they are going to be rendered or not. */ class NodeMaterialObserver { /** * Constructs a new node material observer. * * @param {NodeBuilder} builder - The node builder. */ constructor( builder ) { /** * A node material can be used by more than one render object so the * monitor must maintain a list of render objects. * * @type {WeakMap<RenderObject,Object>} */ this.renderObjects = new WeakMap(); /** * Whether the material uses node objects or not. * * @type {boolean} */ this.hasNode = this.containsNode( builder ); /** * Whether the node builder's 3D object is animated or not. * * @type {boolean} */ this.hasAnimation = builder.object.isSkinnedMesh === true; /** * A list of all possible material uniforms * * @type {Array<string>} */ this.refreshUniforms = refreshUniforms; /** * Holds the current render ID from the node frame. * * @type {number} * @default 0 */ this.renderId = 0; } /** * Returns `true` if the given render object is verified for the first time of this observer. * * @param {RenderObject} renderObject - The render object. * @return {boolean} Whether the given render object is verified for the first time of this observer. */ firstInitialization( renderObject ) { const hasInitialized = this.renderObjects.has( renderObject ); if ( hasInitialized === false ) { this.getRenderObjectData( renderObject ); return true; } return false; } /** * Returns monitoring data for the given render object. * * @param {RenderObject} renderObject - The render object. * @return {Object} The monitoring data. */ getRenderObjectData( renderObject ) { let data = this.renderObjects.get( renderObject ); if ( data === undefined ) { const { geometry, material, object } = renderObject; data = { material: this.getMaterialData( material ), geometry: { id: geometry.id, 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; } /** * Returns an attribute data structure holding the attributes versions for * monitoring. * * @param {Object} attributes - The geometry attributes. * @return {Object} An object for monitoring the versions of attributes. */ getAttributesData( attributes ) { const attributesData = {}; for ( const name in attributes ) { const attribute = attributes[ name ]; attributesData[ name ] = { version: attribute.version }; } return attributesData; } /** * Returns `true` if the node builder's material uses * node properties. * * @param {NodeBuilder} builder - The current node builder. * @return {boolean} Whether the node builder's material uses node properties or not. */ 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; } /** * Returns a material data structure holding the material property values for * monitoring. * * @param {Material} material - The material. * @return {Object} An object for monitoring material properties. */ 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; } /** * Returns `true` if the given render object has not changed its state. * * @param {RenderObject} renderObject - The render object. * @return {boolean} Whether the given render object has changed its state or not. */ 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 ( storedGeometryData.id !== geometry.id ) { storedGeometryData.id = geometry.id; return false; } 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; } /** * Checks if the given render object requires a refresh. * * @param {RenderObject} renderObject - The render object. * @param {NodeFrame} nodeFrame - The current node frame. * @return {boolean} Whether the given render object requires a refresh or not. */ 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 ); } /** * Computes a hash for the given string. * * @method * @param {string} str - The string to be hashed. * @return {number} The hash. */ const hashString = ( str ) => cyrb53( str ); /** * Computes a hash for the given array. * * @method * @param {Array<number>} array - The array to be hashed. * @return {number} The hash. */ const hashArray = ( array ) => cyrb53( array ); /** * Computes a hash for the given list of parameters. * * @method * @param {...number} params - A list of parameters. * @return {number} The hash. */ const hash$1 = ( ...params ) => cyrb53( params ); /** * Computes a cache key for the given node. * * @method * @param {Object|Node} object - The object to be hashed. * @param {boolean} [force=false] - Whether to force a cache key computation or not. * @return {number} The hash. */ 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( cyrb53( property.slice( 0, -4 ) ), childNode.getCacheKey( force ) ); } return cyrb53( values ); } /** * This generator function can be used to iterate over the node children * of the given object. * * @generator * @param {Object} node - The object to be hashed. * @param {boolean} [toJSON=false] - Whether to return JSON or not. * @yields {Object} A result node holding the property, index (if available) and the child node. */ 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' ] ] ); const dataFromObject = /*@__PURE__*/ new WeakMap(); /** * Returns the data type for the given the length. * * @method * @param {number} length - The length. * @return {string} The data type. */ function getTypeFromLength( length ) { return typeFromLength.get( length ); } /** * Returns the typed array for the given data type. * * @method * @param {string} type - The data type. * @return {TypedArray} The typed array. */ function getTypedArrayFromType( type ) { // Handle component type for vectors and matrices if ( /[iu]?vec\d/.test( type ) ) { // Handle int vectors if ( type.startsWith( 'ivec' ) ) return Int32Array; // Handle uint vectors if ( type.startsWith( 'uvec' ) ) return Uint32Array; // Default to float vectors return Float32Array; } // Handle matrices (always float) if ( /mat\d/.test( type ) ) return Float32Array; // Basic types if ( /float/.test( type ) ) return Float32Array; if ( /uint/.test( type ) ) return Uint32Array; if ( /int/.test( type ) ) return Int32Array; throw new Error( `THREE.NodeUtils: Unsupported type: ${type}` ); } /** * Returns the length for the given data type. * * @method * @param {string} type - The data type. * @return {number} The 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 ( /mat2/.test( type ) ) return 4; if ( /mat3/.test( type ) ) return 9; if ( /mat4/.test( type ) ) return 16; console.error( 'THREE.TSL: Unsupported type:', type ); } /** * Returns the data type for the given value. * * @method * @param {any} value - The value. * @return {?string} The data 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.isMatrix2 === true ) { return 'mat2'; } 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; } /** * Returns the value/object for the given data type and parameters. * * @method * @param {string} type - The given type. * @param {...any} params - A parameter list. * @return {any} The value/object. */ 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 === 'mat2' ) { return new Matrix2( ...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; } /** * Gets the object data that can be shared between different rendering steps. * * @param {Object} object - The object to get the data for. * @return {Object} The object data. */ function getDataFromObject( object ) { let data = dataFromObject.get( object ); if ( data === undefined ) { data = {}; dataFromObject.set( object, data ); } return data; } /** * Converts the given array buffer to a Base64 string. * * @method * @param {ArrayBuffer} arrayBuffer - The array buffer. * @return {string} The Base64 string. */ 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 ); } /** * Converts the given Base64 string to an array buffer. * * @method * @param {string} base64 - The Base64 string. * @return {ArrayBuffer} The array buffer. */ 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, getDataFromObject: getDataFromObject, getLengthFromType: getLengthFromType, getNodeChildren: getNodeChildren, getTypeFromLength: getTypeFromLength, getTypedArrayFromType: getTypedArrayFromType, getValueFromType: getValueFromType, getValueType: getValueType, hash: hash$1, hashArray: hashArray, hashString: hashString }); /** * Possible shader stages. * * @property {string} VERTEX The vertex shader stage. * @property {string} FRAGMENT The fragment shader stage. */ const NodeShaderStage = { VERTEX: 'vertex', FRAGMENT: 'fragment' }; /** * Update types of a node. * * @property {string} NONE The update method is not executed. * @property {string} FRAME The update method is executed per frame. * @property {string} RENDER The update method is executed per render. A frame might be produced by multiple render calls so this value allows more detailed updates than FRAME. * @property {string} OBJECT The update method is executed per {@link Object3D} that uses the node for rendering. */ const NodeUpdateType = { NONE: 'none', FRAME: 'frame', RENDER: 'render', OBJECT: 'object' }; /** * Data types of a node. * * @property {string} BOOLEAN Boolean type. * @property {string} INTEGER Integer type. * @property {string} FLOAT Float type. * @property {string} VECTOR2 Two-dimensional vector type. * @property {string} VECTOR3 Three-dimensional vector type. * @property {string} VECTOR4 Four-dimensional vector type. * @property {string} MATRIX2 2x2 matrix type. * @property {string} MATRIX3 3x3 matrix type. * @property {string} MATRIX4 4x4 matrix type. */ const NodeType = { BOOLEAN: 'bool', INTEGER: 'int', FLOAT: 'float', VECTOR2: 'vec2', VECTOR3: 'vec3', VECTOR4: 'vec4', MATRIX2: 'mat2', MATRIX3: 'mat3', MATRIX4: 'mat4' }; /** * Access types of a node. These are relevant for compute and storage usage. * * @property {string} READ_ONLY Read-only access * @property {string} WRITE_ONLY Write-only access. * @property {string} READ_WRITE Read and write access. */ 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; /** * Base class for all nodes. * * @augments EventDispatcher */ class Node extends EventDispatcher { static get type() { return 'Node'; } /** * Constructs a new node. * * @param {?string} nodeType - The node type. */ constructor( nodeType = null ) { super(); /** * The node type. This represents the result type of the node (e.g. `float` or `vec3`). * * @type {?string} * @default null */ this.nodeType = nodeType; /** * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateType = NodeUpdateType.NONE; /** * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateBeforeType = NodeUpdateType.NONE; /** * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateAfterType = NodeUpdateType.NONE; /** * The UUID of the node. * * @type {string} * @readonly */ this.uuid = MathUtils.generateUUID(); /** * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`. * * @type {number} * @readonly * @default 0 */ this.version = 0; /** * Whether this node is global or not. This property is relevant for the internal * node caching system. All nodes which should be declared just once should * set this flag to `true` (a typical example is {@link AttributeNode}). * * @type {boolean} * @default false */ this.global = false; /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isNode = true; // private /** * The cache key of this node. * * @private * @type {?number} * @default null */ this._cacheKey = null; /** * The cache key 's version. * * @private * @type {number} * @default 0 */ this._cacheKeyVersion = 0; Object.defineProperty( this, 'id', { value: _nodeId ++ } ); } /** * Set this property to `true` when the node should be regenerated. * * @type {boolean} * @default false * @param {boolean} value */ set needsUpdate( value ) { if ( value === true ) { this.version ++; } } /** * The type of the class. The value is usually the constructor name. * * @type {string} * @readonly */ get type() { return this.constructor.type; } /** * Convenient method for defining {@link Node#update}. * * @param {Function} callback - The update method. * @param {string} updateType - The update type. * @return {Node} A reference to this node. */ onUpdate( callback, updateType ) { this.updateType = updateType; this.update = callback.bind( this.getSelf() ); return this; } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `FRAME`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onFrameUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.FRAME ); } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `RENDER`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onRenderUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.RENDER ); } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `OBJECT`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onObjectUpdate( callback ) { return this.onUpdate( callback, NodeUpdateType.OBJECT ); } /** * Convenient method for defining {@link Node#updateReference}. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onReference( callback ) { this.updateReference = callback.bind( this.getSelf() ); return this; } /** * The `this` reference might point to a Proxy so this method can be used * to get the reference to the actual node instance. * * @return {Node} A reference to the node. */ getSelf() { // Returns non-node object. return this.self || this; } /** * Nodes might refer to other objects like materials. This method allows to dynamically update the reference * to such objects based on a given state (e.g. the current node frame or builder). * * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. * @return {any} The updated reference. */ updateReference( /*state*/ ) { return this; } /** * By default this method returns the value of the {@link Node#global} flag. This method * can be overwritten in derived classes if an analytical way is required to determine the * global status. * * @param {NodeBuilder} builder - The current node builder. * @return {boolean} Whether this node is global or not. */ isGlobal( /*builder*/ ) { return this.global; } /** * Generator function that can be used to iterate over the child nodes. * * @generator * @yields {Node} A child node. */ * getChildren() { for ( const { childNode } of getNodeChildren( this ) ) { yield childNode; } } /** * Calling this method dispatches the `dispose` event. This event can be used * to register event listeners for clean up tasks. */ dispose() { this.dispatchEvent( { type: 'dispose' } ); } /** * Callback for {@link Node#traverse}. * * @callback traverseCallback * @param {Node} node - The current node. */ /** * Can be used to traverse through the node's hierarchy. * * @param {traverseCallback} callback - A callback that is executed per node. */ traverse( callback ) { callback( this ); for ( const childNode of this.getChildren() ) { childNode.traverse( callback ); } } /** * Returns the cache key for this node. * * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced. * @return {number} The cache key of the node. */ getCacheKey( force = false ) { force = force || this.version !== this._cacheKeyVersion; if ( force === true || this._cacheKey === null ) { this._cacheKey = hash$1( getCacheKey$1( this, force ), this.customCacheKey() ); this._cacheKeyVersion = this.version; } return this._cacheKey; } /** * Generate a custom cache key for this node. * * @return {number} The cache key of the node. */ customCacheKey() { return 0; } /** * Returns the references to this node which is by default `this`. * * @return {Node} A reference to this node. */ getScope() { return this; } /** * Returns the hash of the node which is used to identify the node. By default it's * the {@link Node#uuid} however derived node classes might have to overwrite this method * depending on their implementation. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The hash. */ getHash( /*builder*/ ) { return this.uuid; } /** * Returns the update type of {@link Node#update}. * * @return {NodeUpdateType} The update type. */ getUpdateType() { return this.updateType; } /** * Returns the update type of {@link Node#updateBefore}. * * @return {NodeUpdateType} The update type. */ getUpdateBeforeType() { return this.updateBeforeType; } /** * Returns the update type of {@link Node#updateAfter}. * * @return {NodeUpdateType} The update type. */ getUpdateAfterType() { return this.updateAfterType; } /** * Certain types are composed of multiple elements. For example a `vec3` * is composed of three `float` values. This method returns the type of * these elements. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The type of the node. */ getElementType( builder ) { const type = this.getNodeType( builder ); const elementType = builder.getElementType( type ); return elementType; } /** * Returns the node member type for the given name. * * @param {NodeBuilder} builder - The current node builder. * @param {string} name - The name of the member. * @return {string} The type of the node. */ getMemberType( /*builder, name*/ ) { return 'void'; } /** * Returns the node's type. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The type of the node. */ getNodeType( builder ) { const nodeProperties = builder.getNodeProperties( this ); if ( nodeProperties.outputNode ) { return nodeProperties.outputNode.getNodeType( builder ); } return this.nodeType; } /** * This method is used during the build process of a node and ensures * equal nodes are not built multiple times but just once. For example if * `attribute( 'uv' )` is used multiple times by the user, the build * process makes sure to process just the first node. * * @param {NodeBuilder} builder - The current node builder. * @return {Node} The shared node if possible. Otherwise `this` is returned. */ getShared( builder ) { const hash = this.getHash( builder ); const nodeFromHash = builder.getNodeFromHash( hash ); return nodeFromHash || this; } /** * Represents the setup stage which is the first step of the build process, see {@link Node#build} method. * This method is often overwritten in derived modules to prepare the node which is used as the output/result. * The output node must be returned in the `return` statement. * * @param {NodeBuilder} builder - The current node builder. * @return {?Node} The output node. */ 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 or null return nodeProperties.outputNode || null; } /** * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method. * This stage analyzes the node hierarchy and ensures descendent nodes are built. * * @param {NodeBuilder} builder - The current node builder. */ 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 ); } } } } /** * Represents the generate stage which is the third step of the build process, see {@link Node#build} method. * This state builds the output node and returns the resulting shader string. * * @param {NodeBuilder} builder - The current node builder. * @param {?string} output - Can be used to define the output type. * @return {?string} The generated shader string. */ generate( builder, output ) { const { outputNode } = builder.getNodeProperties( this ); if ( outputNode && outputNode.isNode === true ) { return outputNode.build( builder, output ); } } /** * The method can be implemented to update the node's internal state before it is used to render an object. * The {@link Node#updateBeforeType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ updateBefore( /*frame*/ ) { console.warn( 'Abstract function.' ); } /** * The method can be implemented to update the node's internal state after it was used to render an object. * The {@link Node#updateAfterType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ updateAfter( /*frame*/ ) { console.warn( 'Abstract function.' ); } /** * The method can be implemented to update the node's internal state when it is used to render an object. * The {@link Node#updateType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ update( /*frame*/ ) { console.warn( 'Abstract function.' ); } /** * This method performs the build of a node. The behavior of this method as well as its return value depend * on the current build stage (setup, analyze or generate). * * @param {NodeBuilder} builder - The current node builder. * @param {?string} output - Can be used to define the output type. * @return {?string} When this method is executed in the setup or analyze stage, `null` is returned. In the generate stage, the generated shader string. */ 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; const outputNode = this.setup( builder ); // return a node or null const isNodeOutput = outputNode && outputNode.isNode === true; /*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) { // !! no outputNode !! //outputNode = builder.stack; }*/ for ( const childNode of Object.values( properties ) ) { if ( childNode && childNode.isNode === true ) { childNode.build( builder ); } } if ( isNodeOutput ) { outputNode.build( builder ); } properties.outputNode = outputNode; } } 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 ) { if ( nodeData.generated === undefined ) { nodeData.generated = true; result = this.generate( builder ) || ''; nodeData.snippet = result; } else { console.warn( 'THREE.Node: Recursion detected.', this ); 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; } /** * Returns the child nodes as a JSON object. * * @return {Array<Object>} An iterable list of serialized child objects as JSON. */ getSerializeChildren() { return getNodeChildren( this ); } /** * Serializes the node to JSON. * * @param {Object} json - The output JSON object. */ 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; } } /** * Deserializes the node from the given JSON. * * @param {Object} json - The JSON object. */ 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 ]; } } } } /** * Serializes the node into the three.js JSON Object/Scene format. * * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects. * @return {Object} The serialized node. */ 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; } } /** * Base class for representing element access on an array-like * node data structures. * * @augments Node */ class ArrayElementNode extends Node { // @TODO: If extending from TempNode it breaks webgpu_compute static get type() { return 'ArrayElementNode'; } /** * Constructs an array element node. * * @param {Node} node - The array-like node. * @param {Node} indexNode - The index node that defines the element access. */ constructor( node, indexNode ) { super(); /** * The array-like node. * * @type {Node} */ this.node = node; /** * The index node that defines the element access. * * @type {Node} */ this.indexNode = indexNode; /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isArrayElementNode = true; } /** * This method is overwritten since the node type is inferred from the array-like node. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The node type. */ getNodeType( builder ) { return this.node.getElementType( builder ); } generate( builder ) { const indexType = this.indexNode.getNodeType( builder ); const nodeSnippet = this.node.build( builder ); const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' ); return `${ nodeSnippet }[ ${ indexSnippet } ]`; } } /** * This module is part of the TSL core and usually not used in app level code. * It represents a convert operation during the shader generation process * meaning it converts the data type of