diff --git a/editor/js/Animation.js b/editor/js/Animation.js index adad37911cac20..93748454ba6a27 100644 --- a/editor/js/Animation.js +++ b/editor/js/Animation.js @@ -367,7 +367,14 @@ function Animation( editor ) { clipRow.addEventListener( 'click', function () { - editor.select( root ); + if ( editor.selected !== root ) { + + signals.objectSelected.remove( selectDefaultClip ); + editor.select( root ); + signals.objectSelected.add( selectDefaultClip ); + + } + selectClip( clip, root ); update(); // Refresh to update highlighting @@ -578,10 +585,7 @@ function Animation( editor ) { } - updateTime(); - - // Auto-select clip when an object with animations is selected - signals.objectSelected.add( function ( object ) { + function selectDefaultClip( object ) { if ( object !== null && object.animations && object.animations.length > 0 ) { @@ -590,7 +594,12 @@ function Animation( editor ) { } - } ); + } + + updateTime(); + + // Auto-select clip when an object with animations is selected + signals.objectSelected.add( selectDefaultClip ); // Update when scene changes signals.editorCleared.add( clear ); diff --git a/editor/js/Menubar.File.js b/editor/js/Menubar.File.js index 298f773348fb3f..c0708f9e468fec 100644 --- a/editor/js/Menubar.File.js +++ b/editor/js/Menubar.File.js @@ -1,5 +1,5 @@ import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js'; -import { FileLoader } from 'three'; +import { FileLoader, PropertyBinding } from 'three'; function MenubarFile( editor ) { @@ -276,6 +276,15 @@ function MenubarFile( editor ) { option.onClick( async function () { const scene = editor.scene; + + if ( needsUniqueNames( scene ) ) { // see #25179 + + if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return; + + ensureUniqueNames( scene ); + + } + const animations = getAnimations( scene ); const optimizedAnimations = []; @@ -307,6 +316,15 @@ function MenubarFile( editor ) { option.onClick( async function () { const scene = editor.scene; + + if ( needsUniqueNames( scene ) ) { // see #25179 + + if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return; + + ensureUniqueNames( scene ); + + } + const animations = getAnimations( scene ); const optimizedAnimations = []; @@ -460,6 +478,107 @@ function MenubarFile( editor ) { } + function needsUniqueNames( scene ) { + + const usedNames = new Set(); + let duplicate = false; + let animated = false; + + scene.traverse( function ( object ) { + + if ( object.animations.length > 0 ) animated = true; + + if ( object.name === '' ) return; + + if ( usedNames.has( object.name ) ) duplicate = true; + + usedNames.add( object.name ); + + } ); + + return duplicate && animated; + + } + + // Gives every object a unique name and keeps the animation tracks that + // reference them by name in sync. The renamed scene mirrors the result of a + // glTF round-trip, where the loader makes all names unique, too. + + function ensureUniqueNames( scene ) { + + // Resolve each track's target object up front, scoped to the object that + // owns the clip. This disambiguates colliding names before they change. + + const trackBindings = []; + + scene.traverse( function ( owner ) { + + for ( const clip of owner.animations ) { + + for ( const track of clip.tracks ) { + + const nodeName = PropertyBinding.parseTrackName( track.name ).nodeName; + const target = PropertyBinding.findNode( owner, nodeName ); + + // References by UUID stay valid, so only track name-based ones. + + if ( target !== null && target.name === nodeName ) { + + trackBindings.push( { track, target, nodeName } ); + + } + + } + + } + + } ); + + // Assign a unique name to every named object. + + let changed = false; + const usedNames = new Set(); + + scene.traverse( function ( object ) { + + if ( object.name === '' ) return; + + if ( usedNames.has( object.name ) ) { + + let suffix = 1, name; + do { + + name = object.name + '_' + ( suffix ++ ); + + } while ( usedNames.has( name ) ); + + object.name = name; + changed = true; + + } + + usedNames.add( object.name ); + + } ); + + if ( changed === false ) return; + + // Point the affected tracks at their renamed targets. + + for ( const { track, target, nodeName } of trackBindings ) { + + if ( target.name !== nodeName ) { + + track.name = target.name + track.name.slice( nodeName.length ); + + } + + } + + editor.signals.sceneGraphChanged.dispatch(); + + } + return container; } diff --git a/editor/js/Strings.js b/editor/js/Strings.js index c012af3d4aa7d5..3246f8cb513fe5 100644 --- a/editor/js/Strings.js +++ b/editor/js/Strings.js @@ -8,6 +8,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': 'خطایی در باز کردن پروژه پیش آمده', 'prompt/file/export/noMeshSelected': 'هیچ Mesh ای انتخاب نکردید', 'prompt/file/export/noObjectSelected': 'هیچ آبجکتی انتخاب نکردید!', + 'prompt/file/export/duplicateNames': 'Some objects share the same name. They will be renamed to ensure unique names. Are you sure?', 'prompt/script/remove': 'آیا اطمینان دارید؟', 'prompt/history/clear': 'هیستوری قبل و بعد (undo / redo) پاک خواهند شد آیا مطمئنید؟', 'prompt/history/preserve': 'The history will be preserved across sessions.\nThis can have an impact on performance when working with textures.', @@ -456,6 +457,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': 'Failed to open project!', 'prompt/file/export/noMeshSelected': 'No Mesh selected!', 'prompt/file/export/noObjectSelected': 'No Object selected!', + 'prompt/file/export/duplicateNames': 'Some objects share the same name. They will be renamed to ensure unique names. Are you sure?', 'prompt/script/remove': 'Are you sure?', 'prompt/history/clear': 'The Undo/Redo History will be cleared. Are you sure?', 'prompt/history/preserve': 'The history will be preserved across sessions.\nThis can have an impact on performance when working with textures.', @@ -905,6 +907,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': 'Échec de l\'ouverture du projet !', 'prompt/file/export/noMeshSelected': 'Aucun maillage sélectionné !', 'prompt/file/export/noObjectSelected': 'Aucun objet sélectionné !', + 'prompt/file/export/duplicateNames': 'Certains objets portent le même nom. Ils seront renommés afin de garantir des noms uniques. Êtes-vous sûr ?', 'prompt/script/remove': 'Es-tu sûr?', 'prompt/history/clear': 'L\'historique d\'annulation/rétablissement sera effacé Êtes-vous sûr ?', 'prompt/history/preserve': 'L\'histoire sera conservée entre les sessions.\nCela peut avoir un impact sur les performances lors de la manipulation des textures.', @@ -1354,6 +1357,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': '无法打开项目!', 'prompt/file/export/noMeshSelected': '未选择网格!', 'prompt/file/export/noObjectSelected': '未选择对象!', + 'prompt/file/export/duplicateNames': '部分对象具有相同的名称。它们将被重命名以确保名称唯一。确定吗?', 'prompt/script/remove': '你确定吗?', 'prompt/history/clear': '撤销/重做历史记录将被清除。您确定吗?', 'prompt/history/preserve': '历史将在会话之间保留。\n这可能会影响在处理纹理时的性能。', @@ -1803,6 +1807,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': 'プロジェクトを開くことができませんでした!', 'prompt/file/export/noMeshSelected': 'メッシュが選択されていません!', 'prompt/file/export/noObjectSelected': 'オブジェクトが選択されていません!', + 'prompt/file/export/duplicateNames': '一部のオブジェクトの名前が重複しています。名前を一意にするために変更します。よろしいですか?', 'prompt/script/remove': '本気ですか?', 'prompt/history/clear': '元に戻す/やり直しの履歴が消去されます。 本気ですか?', 'prompt/history/preserve': '履歴はセッションをまたいで保存されます。\nこれは、テクスチャを操作する際のパフォーマンスに影響を与える可能性があります。', @@ -2251,6 +2256,7 @@ function Strings( config ) { 'prompt/file/failedToOpenProject': '프로젝트를 여는 데 실패했습니다!', 'prompt/file/export/noMeshSelected': '메시가 선택되지 않았습니다!', 'prompt/file/export/noObjectSelected': '객체가 선택되지 않았습니다!', + 'prompt/file/export/duplicateNames': '일부 객체의 이름이 중복됩니다. 이름을 고유하게 만들기 위해 변경됩니다. 계속하시겠습니까?', 'prompt/script/remove': '삭제하시겠습니까?', 'prompt/history/clear': '되돌리기/다시하기 기록이 지워집니다. 진행하시겠습니까?', 'prompt/history/preserve': '기록은 세션을 통해 저장됩니다. 이는 텍스처를 조작할 때 성능에 영향을 미칠 수 있습니다.', diff --git a/examples/screenshots/webgpu_postprocessing_motion_blur.jpg b/examples/screenshots/webgpu_postprocessing_motion_blur.jpg index 0d748b68e188e3..2e2b02578ba7f9 100644 Binary files a/examples/screenshots/webgpu_postprocessing_motion_blur.jpg and b/examples/screenshots/webgpu_postprocessing_motion_blur.jpg differ diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 13f990fd320111..5963e91506ab43 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -1,16 +1,16 @@ import { Material } from '../Material.js'; import { hashArray, hashString } from '../../nodes/core/NodeUtils.js'; -import { output, diffuseColor, emissive, varyingProperty } from '../../nodes/core/PropertyNode.js'; +import { output, diffuseColor, emissive } from '../../nodes/core/PropertyNode.js'; import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, materialLightMap, materialAO } from '../../nodes/accessors/MaterialNode.js'; import { modelViewProjection } from '../../nodes/accessors/ModelViewProjectionNode.js'; import { normalLocal } from '../../nodes/accessors/Normal.js'; -import { instancedMesh } from '../../nodes/accessors/InstancedMeshNode.js'; -import { batch } from '../../nodes/accessors/BatchNode.js'; +import { instancedMesh, instanceColor } from '../../nodes/accessors/Instance.js'; +import { batch, batchColor } from '../../nodes/accessors/Batch.js'; import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js'; import { positionLocal, positionView } from '../../nodes/accessors/Position.js'; -import { skinning } from '../../nodes/accessors/SkinningNode.js'; -import { morphReference } from '../../nodes/accessors/MorphNode.js'; +import { skinning } from '../../nodes/accessors/Skinning.js'; +import { morphReference } from '../../nodes/accessors/Morph.js'; import { fwidth, mix, smoothstep } from '../../nodes/math/MathNode.js'; import { float, vec3, vec4, bool } from '../../nodes/tsl/TSLBase.js'; import AONode from '../../nodes/lighting/AONode.js'; @@ -766,13 +766,13 @@ class NodeMaterial extends Material { if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) { - morphReference( object ).toStack(); + morphReference( object ); } if ( object.isSkinnedMesh === true ) { - skinning( object ).toStack(); + skinning( object ); } @@ -788,13 +788,13 @@ class NodeMaterial extends Material { if ( object.isBatchedMesh ) { - batch( object ).toStack(); + batch( object ); } if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) { - instancedMesh( object ).toStack(); + instancedMesh( object ); } @@ -844,16 +844,12 @@ class NodeMaterial extends Material { if ( object.instanceColor ) { - const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' ); - colorNode = instanceColor.mul( colorNode ); } if ( object.isBatchedMesh && object._colorsTexture ) { - const batchColor = varyingProperty( 'vec3', 'vBatchColor' ); - colorNode = batchColor.mul( colorNode ); } diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index e29383bfe31500..82f7a49e80c69d 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -43,24 +43,19 @@ import * as NodeUtils from './core/NodeUtils.js'; export { NodeUtils }; // accessors -export { default as BatchNode } from './accessors/BatchNode.js'; export { default as BufferAttributeNode } from './accessors/BufferAttributeNode.js'; export { default as BufferNode } from './accessors/BufferNode.js'; export { default as BuiltinNode } from './accessors/BuiltinNode.js'; export { default as ClippingNode } from './accessors/ClippingNode.js'; export { default as CubeTextureNode } from './accessors/CubeTextureNode.js'; -export { default as InstanceNode } from './accessors/InstanceNode.js'; -export { default as InstancedMeshNode } from './accessors/InstancedMeshNode.js'; export { default as MaterialNode } from './accessors/MaterialNode.js'; export { default as MaterialReferenceNode } from './accessors/MaterialReferenceNode.js'; export { default as ModelNode } from './accessors/ModelNode.js'; -export { default as MorphNode } from './accessors/MorphNode.js'; export { default as Object3DNode } from './accessors/Object3DNode.js'; export { default as PointUVNode } from './accessors/PointUVNode.js'; export { default as ReferenceBaseNode } from './accessors/ReferenceBaseNode.js'; export { default as ReferenceNode } from './accessors/ReferenceNode.js'; export { default as RendererReferenceNode } from './accessors/RendererReferenceNode.js'; -export { default as SkinningNode } from './accessors/SkinningNode.js'; export { default as StorageBufferNode } from './accessors/StorageBufferNode.js'; export { default as StorageTexture3DNode } from './accessors/StorageTexture3DNode.js'; export { default as StorageTextureNode } from './accessors/StorageTextureNode.js'; diff --git a/src/nodes/TSL.js b/src/nodes/TSL.js index 9ba06eea730ee8..fc3997d18bcfc3 100644 --- a/src/nodes/TSL.js +++ b/src/nodes/TSL.js @@ -63,14 +63,13 @@ export * from './accessors/BuiltinNode.js'; export * from './accessors/Camera.js'; export * from './accessors/VertexColorNode.js'; export * from './accessors/CubeTextureNode.js'; -export * from './accessors/InstanceNode.js'; -export * from './accessors/InstancedMeshNode.js'; -export * from './accessors/BatchNode.js'; +export * from './accessors/Instance.js'; +export * from './accessors/Batch.js'; export * from './accessors/MaterialNode.js'; export * from './accessors/MaterialProperties.js'; export * from './accessors/MaterialReferenceNode.js'; export * from './accessors/RendererReferenceNode.js'; -export * from './accessors/MorphNode.js'; +export * from './accessors/Morph.js'; export * from './accessors/TextureBicubic.js'; export * from './accessors/ModelNode.js'; export * from './accessors/ModelViewProjectionNode.js'; @@ -80,7 +79,7 @@ export * from './accessors/PointUVNode.js'; export * from './accessors/Position.js'; export * from './accessors/ReferenceNode.js'; export * from './accessors/ReflectVector.js'; -export * from './accessors/SkinningNode.js'; +export * from './accessors/Skinning.js'; export * from './accessors/SceneProperties.js'; export * from './accessors/StorageBufferNode.js'; export * from './accessors/StorageTexture3DNode.js'; diff --git a/src/nodes/accessors/Batch.js b/src/nodes/accessors/Batch.js new file mode 100644 index 00000000000000..20a9d48a3c94fe --- /dev/null +++ b/src/nodes/accessors/Batch.js @@ -0,0 +1,108 @@ + +import { normalLocal } from './Normal.js'; +import { positionLocal } from './Position.js'; +import { vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js'; +import { textureLoad } from './TextureNode.js'; +import { textureSize } from './TextureSizeNode.js'; +import { tangentLocal } from './Tangent.js'; +import { instanceIndex, drawIndex } from '../core/IndexNode.js'; +import { varyingProperty } from '../core/PropertyNode.js'; + +/** + * TSL function that retrieves the batching color for a given instance ID from a colors texture. + * + * @param {Node} colorsTexture - The colors texture. + * @param {Node} id - The instance or batch ID. + * @returns {Node} The retrieved color. + */ +const getBatchingColor = /*@__PURE__*/ Fn( ( [ colorsTexture, id ] ) => { + + const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst(); + const j = int( id ); + const x = j.mod( size ).toConst(); + const y = j.div( size ).toConst(); + return textureLoad( colorsTexture, ivec2( x, y ) ).rgb; + +} ); + +/** + * TSL function that retrieves the indirect index for a given batch ID. + * + * @param {BatchedMesh} batchMesh - The batched mesh. + * @param {Node} id - The draw or instance ID. + * @returns {Node} The indirect index. + */ +const getIndirectIndex = /*@__PURE__*/ Fn( ( [ indirectTexture, id ] ) => { + + const size = int( textureSize( textureLoad( indirectTexture ), 0 ).x ).toConst(); + const x = int( id ).mod( size ).toConst(); + const y = int( id ).div( size ).toConst(); + return textureLoad( indirectTexture, ivec2( x, y ) ).x; + +} ); + +/** + * TSL object representing a varying property for the batching color vector. + * + * @type {VaryingNode} + */ +export const batchColor = /*@__PURE__*/ varyingProperty( 'vec3', 'vBatchColor' ); + +/** + * TSL function representing the vertex shader batching setup. + * Applies the batch transformation matrix to positionLocal, normalLocal, and tangentLocal. + * Also assigns the batch color if a color texture is present. + * + * @tsl + * @function + * @param {BatchedMesh} batchMesh - The batched mesh. + */ +export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + + const batchingIdNode = builder.getDrawIndex() === null ? instanceIndex : drawIndex; + + const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) ); + + const matricesTexture = batchMesh._matricesTexture; + + const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst(); + const j = float( indirectId ).mul( 4 ).toInt().toConst(); + + const x = j.mod( size ).toConst(); + const y = j.div( size ).toConst(); + const batchingMatrix = mat4( + textureLoad( matricesTexture, ivec2( x, y ) ), + textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) ) + ); + + const colorsTexture = batchMesh._colorsTexture; + + if ( colorsTexture !== null ) { + + const color = getBatchingColor( colorsTexture, indirectId ); + + batchColor.assign( color ); + + } + + const bm = mat3( batchingMatrix ); + + positionLocal.assign( batchingMatrix.mul( positionLocal ) ); + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); + + const batchingNormal = bm.mul( transformedNormal ).xyz; + + normalLocal.assign( batchingNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.mulAssign( bm ); + + } + +}, 'void' ); + + diff --git a/src/nodes/accessors/BatchNode.js b/src/nodes/accessors/BatchNode.js deleted file mode 100644 index 9138e54d3b7089..00000000000000 --- a/src/nodes/accessors/BatchNode.js +++ /dev/null @@ -1,163 +0,0 @@ -import Node from '../core/Node.js'; -import { normalLocal } from './Normal.js'; -import { positionLocal } from './Position.js'; -import { nodeProxy, vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js'; -import { textureLoad } from './TextureNode.js'; -import { textureSize } from './TextureSizeNode.js'; -import { tangentLocal } from './Tangent.js'; -import { instanceIndex, drawIndex } from '../core/IndexNode.js'; -import { varyingProperty } from '../core/PropertyNode.js'; - -/** - * This node implements the vertex shader logic which is required - * when rendering 3D objects via batching. `BatchNode` must be used - * with instances of {@link BatchedMesh}. - * - * @augments Node - */ -class BatchNode extends Node { - - static get type() { - - return 'BatchNode'; - - } - - /** - * Constructs a new batch node. - * - * @param {BatchedMesh} batchMesh - A reference to batched mesh. - */ - constructor( batchMesh ) { - - super( 'void' ); - - /** - * A reference to batched mesh. - * - * @type {BatchedMesh} - */ - this.batchMesh = batchMesh; - - /** - * The batching index node. - * - * @type {?IndexNode} - * @default null - */ - this.batchingIdNode = null; - - } - - /** - * Setups the internal buffers and nodes and assigns the transformed vertex data - * to predefined node variables for accumulation. That follows the same patterns - * like with morph and skinning nodes. - * - * @param {NodeBuilder} builder - The current node builder. - */ - setup( builder ) { - - if ( this.batchingIdNode === null ) { - - if ( builder.getDrawIndex() === null ) { - - this.batchingIdNode = instanceIndex; - - } else { - - this.batchingIdNode = drawIndex; - - } - - } - - const getIndirectIndex = Fn( ( [ id ] ) => { - - const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ).x ).toConst(); - const x = int( id ).mod( size ).toConst(); - const y = int( id ).div( size ).toConst(); - return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x; - - } ).setLayout( { - name: 'getIndirectIndex', - type: 'uint', - inputs: [ - { name: 'id', type: 'int' } - ] - } ); - - const indirectId = getIndirectIndex( int( this.batchingIdNode ) ); - - const matricesTexture = this.batchMesh._matricesTexture; - - const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst(); - const j = float( indirectId ).mul( 4 ).toInt().toConst(); - - const x = j.mod( size ).toConst(); - const y = j.div( size ).toConst(); - const batchingMatrix = mat4( - textureLoad( matricesTexture, ivec2( x, y ) ), - textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ), - textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ), - textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) ) - ); - - - const colorsTexture = this.batchMesh._colorsTexture; - - if ( colorsTexture !== null ) { - - const getBatchingColor = Fn( ( [ id ] ) => { - - const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst(); - const j = id; - const x = j.mod( size ).toConst(); - const y = j.div( size ).toConst(); - return textureLoad( colorsTexture, ivec2( x, y ) ).rgb; - - } ).setLayout( { - name: 'getBatchingColor', - type: 'vec3', - inputs: [ - { name: 'id', type: 'int' } - ] - } ); - - const color = getBatchingColor( indirectId ); - - varyingProperty( 'vec3', 'vBatchColor' ).assign( color ); - - } - - const bm = mat3( batchingMatrix ); - - positionLocal.assign( batchingMatrix.mul( positionLocal ) ); - - const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); - - const batchingNormal = bm.mul( transformedNormal ).xyz; - - normalLocal.assign( batchingNormal ); - - if ( builder.hasGeometryAttribute( 'tangent' ) ) { - - tangentLocal.mulAssign( bm ); - - } - - } - -} - -export default BatchNode; - -/** - * TSL function for creating a batch node. - * - * @tsl - * @function - * @param {BatchedMesh} batchMesh - A reference to batched mesh. - * @returns {BatchNode} - */ -export const batch = /*@__PURE__*/ nodeProxy( BatchNode ).setParameterLength( 1 ); diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js new file mode 100644 index 00000000000000..b9dbe74da72e07 --- /dev/null +++ b/src/nodes/accessors/Instance.js @@ -0,0 +1,271 @@ + +import { vec3, mat4, Fn } from '../tsl/TSLBase.js'; +import { OnFrameUpdate, OnObjectUpdate } from '../utils/EventNode.js'; +import { normalLocal, transformNormal } from './Normal.js'; +import { positionLocal, positionPrevious } from './Position.js'; +import { varyingProperty } from '../core/PropertyNode.js'; +import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js'; +import { buffer } from './BufferNode.js'; +import { storage } from './StorageBufferNode.js'; +import { instanceIndex } from '../core/IndexNode.js'; + +import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js'; +import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js'; +import { DynamicDrawUsage } from '../../constants.js'; + +const _matrixBuffers = /*@__PURE__*/ new WeakMap(); +const _colorBuffers = /*@__PURE__*/ new WeakMap(); +const _previousInstanceMatrices = /*@__PURE__*/ new WeakMap(); + +/** + * Creates the appropriate node for instanced matrix transformations. + * Depending on buffer limits and storage capability, returns either a storage, buffer, or instanced interleaved attribute node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The matrix buffer attribute. + * @param {number} count - The instance count. + * @returns {Node} The matrix node. + */ +function createInstanceMatrixNode( builder, instanceMatrix, count ) { + + let instanceMatrixNode; + + const isStorageMatrix = instanceMatrix.isStorageInstancedBufferAttribute === true; + + if ( isStorageMatrix ) { + + instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); + + } else { + + const uniformBufferSize = count * 16 * 4; + + if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { + + instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); + + } else { + + let interleaved = _matrixBuffers.get( instanceMatrix ); + + if ( ! interleaved ) { + + interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 ); + _matrixBuffers.set( instanceMatrix, interleaved ); + + } + + const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + const instanceBuffers = [ + bufferFn( interleaved, 'vec4', 16, 0 ), + bufferFn( interleaved, 'vec4', 16, 4 ), + bufferFn( interleaved, 'vec4', 16, 8 ), + bufferFn( interleaved, 'vec4', 16, 12 ) + ]; + + instanceMatrixNode = mat4( ...instanceBuffers ); + + } + + } + + return instanceMatrixNode; + +} + +/** + * Retrieves or initializes the previous frame instance matrix node for motion vectors. + * Uses a WeakMap to cache previous frame instance matrices and their TSL nodes. + * + * @param {InstancedMesh} instancedMesh - The instanced mesh object. + * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The current matrix buffer attribute. + * @param {NodeBuilder} builder - The current node builder. + * @param {number} count - The instance count. + * @returns {Node} The previous frame instance matrix node. + */ +function getPreviousInstance( instancedMesh, instanceMatrix, builder, count ) { + + let data = _previousInstanceMatrices.get( instancedMesh ); + + if ( data === undefined ) { + + const previousInstanceMatrix = instanceMatrix.clone(); + + data = { + previousInstanceMatrix, + node: createInstanceMatrixNode( builder, previousInstanceMatrix, count ) + }; + + _previousInstanceMatrices.set( instancedMesh, data ); + + } + + return data.node; + +} + +/** + * TSL object representing a varying property for the instanced color vector. + * + * @type {VaryingNode} + */ +export const instanceColor = /*@__PURE__*/ varyingProperty( 'vec3', 'vInstanceColor' ); + +/** + * TSL function representing the standard instancing vertex shader setup. + * Transforms positionLocal and normalLocal, and assigns varying color in-place. + * + * @tsl + * @function + * @param {number} count - The instance count. + * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} matrices - The instanced transformation matrices. + * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} [colors=null] - The optional instanced colors. + */ +export const instance = /*@__PURE__*/ Fn( ( [ count, matrices, colors = null ], builder ) => { + + // get numeric value (non-node) + count = count.value; + + const isStorageMatrix = matrices.isStorageInstancedBufferAttribute === true; + const isStorageColor = colors && colors.isStorageInstancedBufferAttribute === true; + + const instanceMatrixNode = createInstanceMatrixNode( builder, matrices, count ); + + // interleaved buffer tracking for matrix + let interleavedMatrix = null; + + if ( ! isStorageMatrix ) { + + const uniformBufferSize = count * 16 * 4; + + if ( uniformBufferSize > builder.getUniformBufferLimit() ) { + + interleavedMatrix = _matrixBuffers.get( matrices ); + + } + + } + + let instanceColorNode = null; + let interleavedColor = null; + + if ( colors ) { + + if ( isStorageColor ) { + + instanceColorNode = storage( colors, 'vec3', Math.max( colors.count, 1 ) ).element( instanceIndex ); + + } else { + + let bufferAttribute = _colorBuffers.get( colors ); + + if ( ! bufferAttribute ) { + + bufferAttribute = new InstancedBufferAttribute( colors.array, 3 ); + _colorBuffers.set( colors, bufferAttribute ); + + } + + interleavedColor = bufferAttribute; + + const bufferFn = colors.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) ); + + } + + } + + // Synchronization of dynamic buffer updates per frame + if ( interleavedMatrix !== null || interleavedColor !== null ) { + + OnFrameUpdate( () => { + + if ( interleavedMatrix !== null ) { + + interleavedMatrix.clearUpdateRanges(); + interleavedMatrix.updateRanges.push( ...matrices.updateRanges ); + + if ( matrices.version !== interleavedMatrix.version ) { + + interleavedMatrix.version = matrices.version; + + } + + } + + if ( colors && interleavedColor !== null ) { + + interleavedColor.clearUpdateRanges(); + interleavedColor.updateRanges.push( ...colors.updateRanges ); + + if ( colors.version !== interleavedColor.version ) { + + interleavedColor.version = colors.version; + + } + + } + + } ); + + } + + // POSITION + + const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz; + positionLocal.assign( instancePosition ); + + if ( builder.needsPreviousData() ) { + + const instancedMesh = builder.object; + + OnObjectUpdate( ( { object } ) => { + + const previousInstanceData = _previousInstanceMatrices.get( object ); + + previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); + + } ); + + const previousInstanceMatrixNode = getPreviousInstance( instancedMesh, matrices, builder, count ); + positionPrevious.assign( previousInstanceMatrixNode.mul( positionPrevious ).xyz ); + + } + + // NORMAL + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const instanceNormal = transformNormal( normalLocal, instanceMatrixNode ); + normalLocal.assign( instanceNormal ); + + } + + // COLOR + + if ( instanceColorNode !== null ) { + + instanceColor.assign( instanceColorNode ); + + } + +}, 'void' ); + +/** + * TSL wrapper for applying instanced mesh rendering setup. + * + * @tsl + * @function + * @param {InstancedMesh} instancedMesh - The instanced mesh. + */ +export const instancedMesh = /*@__PURE__*/ Fn( ( [ instancedMesh ] ) => { + + const { count, instanceMatrix, instanceColor } = instancedMesh; + + instance( count, instanceMatrix, instanceColor ); + +}, 'void' ); + + diff --git a/src/nodes/accessors/InstanceNode.js b/src/nodes/accessors/InstanceNode.js deleted file mode 100644 index 716bc01da4cd26..00000000000000 --- a/src/nodes/accessors/InstanceNode.js +++ /dev/null @@ -1,367 +0,0 @@ -import Node from '../core/Node.js'; -import { varyingProperty } from '../core/PropertyNode.js'; -import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js'; -import { normalLocal, transformNormal } from './Normal.js'; -import { positionLocal, positionPrevious } from './Position.js'; -import { nodeProxy, vec3, mat4 } from '../tsl/TSLBase.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { buffer } from '../accessors/BufferNode.js'; -import { storage } from './StorageBufferNode.js'; -import { instanceIndex } from '../core/IndexNode.js'; - -import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js'; -import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js'; -import { DynamicDrawUsage } from '../../constants.js'; - -/** - * This node implements the vertex shader logic which is required - * when rendering 3D objects via instancing. The code makes sure - * vertex positions, normals and colors can be modified via instanced - * data. - * - * @augments Node - */ -class InstanceNode extends Node { - - static get type() { - - return 'InstanceNode'; - - } - - /** - * Constructs a new instance node. - * - * @param {number} count - The number of instances. - * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. - * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. - */ - constructor( count, instanceMatrix, instanceColor = null ) { - - super( 'void' ); - - /** - * The number of instances. - * - * @type {number} - */ - this.count = count; - - /** - * Instanced buffer attribute representing the transformation of instances. - * - * @type {InstancedBufferAttribute} - */ - this.instanceMatrix = instanceMatrix; - - /** - * Instanced buffer attribute representing the color of instances. - * - * @type {InstancedBufferAttribute} - */ - this.instanceColor = instanceColor; - - /** - * The node that represents the instance matrix data. - * - * @type {?Node} - */ - this.instanceMatrixNode = null; - - /** - * The node that represents the instance color data. - * - * @type {?Node} - * @default null - */ - this.instanceColorNode = null; - - /** - * The update type is set to `frame` for updating - * velocity-related data. - * - * @type {string} - * @default 'frame' - */ - this.updateType = NodeUpdateType.FRAME; - - /** - * The update type is set to `frame` since an update - * of instanced buffer data must be checked per frame. - * - * @type {string} - * @default 'frame' - */ - this.updateBeforeType = NodeUpdateType.FRAME; - - /** - * A reference to a buffer that is used by `instanceMatrixNode`. - * - * @type {?InstancedInterleavedBuffer} - */ - this.buffer = null; - - /** - * A reference to a buffer that is used by `instanceColorNode`. - * - * @type {?InstancedBufferAttribute} - */ - this.bufferColor = null; - - /** - * The previous instance matrices. Required for computing motion vectors. - * - * @type {?Node} - * @default null - */ - this.previousInstanceMatrixNode = null; - - } - - /** - * Tracks whether the matrix data is provided via a storage buffer. - * - * @type {boolean} - */ - get isStorageMatrix() { - - const { instanceMatrix } = this; - - return instanceMatrix && instanceMatrix.isStorageInstancedBufferAttribute === true; - - } - - /** - * Tracks whether the color data is provided via a storage buffer. - * - * @type {boolean} - */ - get isStorageColor() { - - const { instanceColor } = this; - - return instanceColor && instanceColor.isStorageInstancedBufferAttribute === true; - - } - - /** - * Setups the internal buffers and nodes and assigns the transformed vertex data - * to predefined node variables for accumulation. That follows the same patterns - * like with morph and skinning nodes. - * - * @param {NodeBuilder} builder - The current node builder. - */ - setup( builder ) { - - let { instanceMatrixNode, instanceColorNode } = this; - - // instance matrix - - if ( instanceMatrixNode === null ) { - - instanceMatrixNode = this._createInstanceMatrixNode( true, builder ); - - this.instanceMatrixNode = instanceMatrixNode; - - } - - // instance color - - const { instanceColor, isStorageColor } = this; - - if ( instanceColor && instanceColorNode === null ) { - - if ( isStorageColor ) { - - instanceColorNode = storage( instanceColor, 'vec3', Math.max( instanceColor.count, 1 ) ).element( instanceIndex ); - - } else { - - const bufferAttribute = new InstancedBufferAttribute( instanceColor.array, 3 ); - - const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; - - this.bufferColor = bufferAttribute; - - instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) ); - - } - - this.instanceColorNode = instanceColorNode; - - } - - // POSITION - - const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz; - positionLocal.assign( instancePosition ); - - if ( builder.needsPreviousData() ) { - - positionPrevious.assign( this.getPreviousInstancedPosition( builder ) ); - - } - - // NORMAL - - if ( builder.hasGeometryAttribute( 'normal' ) ) { - - const instanceNormal = transformNormal( normalLocal, instanceMatrixNode ); - - // ASSIGNS - - normalLocal.assign( instanceNormal ); - - } - - // COLOR - - if ( this.instanceColorNode !== null ) { - - varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode ); - - } - - } - - /** - * Checks if the internal buffers require an update. - * - * @param {NodeFrame} frame - The current node frame. - */ - updateBefore( /*frame*/ ) { - - if ( this.buffer !== null && this.isStorageMatrix !== true ) { - - this.buffer.clearUpdateRanges(); - this.buffer.updateRanges.push( ... this.instanceMatrix.updateRanges ); - - // update version if necessary - - if ( this.instanceMatrix.version !== this.buffer.version ) { - - this.buffer.version = this.instanceMatrix.version; - - } - - } - - if ( this.instanceColor && this.bufferColor !== null && this.isStorageColor !== true ) { - - this.bufferColor.clearUpdateRanges(); - this.bufferColor.updateRanges.push( ... this.instanceColor.updateRanges ); - - if ( this.instanceColor.version !== this.bufferColor.version ) { - - this.bufferColor.version = this.instanceColor.version; - - } - - } - - } - - /** - * Updates velocity-related data if necessary. - * - * @param {NodeFrame} frame - The current node frame. - */ - update( frame ) { - - if ( this.previousInstanceMatrixNode !== null ) { - - frame.object.previousInstanceMatrix.array.set( this.instanceMatrix.array ); - - } - - } - - /** - * Computes the transformed/instanced vertex position of the previous frame. - * - * @param {NodeBuilder} builder - The current node builder. - * @return {Node} The instanced position from the previous frame. - */ - getPreviousInstancedPosition( builder ) { - - const instancedMesh = builder.object; - - if ( this.previousInstanceMatrixNode === null ) { - - instancedMesh.previousInstanceMatrix = this.instanceMatrix.clone(); - - this.previousInstanceMatrixNode = this._createInstanceMatrixNode( false, builder ); - - } - - return this.previousInstanceMatrixNode.mul( positionPrevious ).xyz; - - } - - /** - * Creates a node representing the instance matrix data. - * - * @private - * @param {boolean} assignBuffer - Whether the created interleaved buffer should be assigned to the `buffer` member or not. - * @param {NodeBuilder} builder - A reference to the current node builder. - * @return {Node} The instance matrix node. - */ - _createInstanceMatrixNode( assignBuffer, builder ) { - - let instanceMatrixNode; - - const { instanceMatrix } = this; - const { count } = instanceMatrix; - - if ( this.isStorageMatrix ) { - - instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); - - } else { - - const uniformBufferSize = count * 16 * 4; // count * 16 components * 4 bytes (float) - - if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { - - instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); - - } else { - - const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 ); - - if ( assignBuffer === true ) this.buffer = interleaved; - - const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; - - const instanceBuffers = [ - bufferFn( interleaved, 'vec4', 16, 0 ), - bufferFn( interleaved, 'vec4', 16, 4 ), - bufferFn( interleaved, 'vec4', 16, 8 ), - bufferFn( interleaved, 'vec4', 16, 12 ) - ]; - - instanceMatrixNode = mat4( ...instanceBuffers ); - - } - - } - - return instanceMatrixNode; - - } - -} - -export default InstanceNode; - -/** - * TSL function for creating an instance node. - * - * @tsl - * @function - * @param {number} count - The number of instances. - * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. - * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. - * @returns {InstanceNode} - */ -export const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 ); diff --git a/src/nodes/accessors/InstancedMeshNode.js b/src/nodes/accessors/InstancedMeshNode.js deleted file mode 100644 index d5542ea4059938..00000000000000 --- a/src/nodes/accessors/InstancedMeshNode.js +++ /dev/null @@ -1,50 +0,0 @@ -import InstanceNode from './InstanceNode.js'; -import { nodeProxy } from '../tsl/TSLBase.js'; - -/** - * This is a special version of `InstanceNode` which requires the usage of {@link InstancedMesh}. - * It allows an easier setup of the instance node. - * - * @augments InstanceNode - */ -class InstancedMeshNode extends InstanceNode { - - static get type() { - - return 'InstancedMeshNode'; - - } - - /** - * Constructs a new instanced mesh node. - * - * @param {InstancedMesh} instancedMesh - The instanced mesh. - */ - constructor( instancedMesh ) { - - const { count, instanceMatrix, instanceColor } = instancedMesh; - - super( count, instanceMatrix, instanceColor ); - - /** - * A reference to the instanced mesh. - * - * @type {InstancedMesh} - */ - this.instancedMesh = instancedMesh; - - } - -} - -export default InstancedMeshNode; - -/** - * TSL function for creating an instanced mesh node. - * - * @tsl - * @function - * @param {InstancedMesh} instancedMesh - The instancedMesh. - * @returns {InstancedMeshNode} - */ -export const instancedMesh = /*@__PURE__*/ nodeProxy( InstancedMeshNode ).setParameterLength( 1 ); diff --git a/src/nodes/accessors/MorphNode.js b/src/nodes/accessors/Morph.js similarity index 56% rename from src/nodes/accessors/MorphNode.js rename to src/nodes/accessors/Morph.js index c05620b3ef66d8..361dd79b2db7b5 100644 --- a/src/nodes/accessors/MorphNode.js +++ b/src/nodes/accessors/Morph.js @@ -1,13 +1,12 @@ -import Node from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { float, nodeProxy, Fn, ivec2, int, If } from '../tsl/TSLBase.js'; -import { uniform } from '../core/UniformNode.js'; + +import { float, Fn, ivec2, int, If, uniform } from '../tsl/TSLBase.js'; import { reference } from './ReferenceNode.js'; +import { Loop } from '../utils/LoopNode.js'; +import { OnObjectUpdate } from '../utils/EventNode.js'; +import { textureLoad } from './TextureNode.js'; import { positionLocal } from './Position.js'; import { normalLocal } from './Normal.js'; -import { textureLoad } from './TextureNode.js'; import { instanceIndex, vertexIndex } from '../core/IndexNode.js'; -import { Loop } from '../utils/LoopNode.js'; import { DataArrayTexture } from '../../textures/DataArrayTexture.js'; import { Vector2 } from '../../math/Vector2.js'; @@ -16,7 +15,20 @@ import { FloatType } from '../../constants.js'; const _morphTextures = /*@__PURE__*/ new WeakMap(); const _morphVec4 = /*@__PURE__*/ new Vector4(); +const _morphBaseInfluences = /*@__PURE__*/ new WeakMap(); +/** + * TSL function that retrieves and scales the morphed attribute (position or normal) texel value. + * + * @param {Object} params - The parameter object. + * @param {Node} params.bufferMap - The morph target data array texture. + * @param {Node} params.influence - The target's animation influence weight. + * @param {number} params.stride - The vertex data stride (e.g. 1 or 2). + * @param {Node} params.width - The texture width limit. + * @param {Node} params.depth - The target layer index (morph target index). + * @param {Node} params.offset - The texture offset (e.g. 0 for position, 1 for normal). + * @returns {Node} The scaled morph target translation value. + */ const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => { const texelIndex = int( vertexIndex ).mul( stride ).add( offset ); @@ -30,6 +42,12 @@ const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, dept } ); +/** + * Resolves or creates a compiled DataArrayTexture containing encoded vertex morph targets data for WebGL2/WebGPU. + * + * @param {BufferGeometry} geometry - The geometry to parse. + * @returns {Object} The resolved morph targets texture data mapping entry. + */ function getEntry( geometry ) { const hasMorphPosition = geometry.morphAttributes.position !== undefined; @@ -157,154 +175,108 @@ function getEntry( geometry ) { } /** - * This node implements the vertex transformation shader logic which is required - * for morph target animation. + * TSL object representing a reference to the mesh's morphTargetInfluences array. * - * @augments Node + * @type {ReferenceNode} */ -class MorphNode extends Node { - - static get type() { - - return 'MorphNode'; +export const morphTargetInfluences = /*@__PURE__*/ reference( 'morphTargetInfluences', 'float' ); - } - - /** - * Constructs a new morph node. - * - * @param {Mesh} mesh - The mesh holding the morph targets. - */ - constructor( mesh ) { - - super( 'void' ); - - /** - * The mesh holding the morph targets. - * - * @type {Mesh} - */ - this.mesh = mesh; - - /** - * A uniform node which represents the morph base influence value. - * - * @type {UniformNode} - */ - this.morphBaseInfluence = uniform( 1 ); - - /** - * The update type overwritten since morph nodes are updated per object. - * - * @type {string} - */ - this.updateType = NodeUpdateType.OBJECT; +/** + * TSL function representing the vertex shader morph targets blend setup. + * Dynamically computes morph targets weights and updates positionLocal and normalLocal in-place. + * + * @tsl + * @function + * @param {Mesh} mesh - The mesh. + */ +export const morphReference = /*@__PURE__*/ Fn( ( [ mesh ] ) => { - } + const { geometry } = mesh; - /** - * Setups the morph node by assigning the transformed vertex data to predefined node variables. - * - * @param {NodeBuilder} builder - The current node builder. - */ - setup( builder ) { + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined; - const { geometry } = builder; + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined; + if ( morphTargetsCount === 0 ) return; - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + let morphBaseInfluence = _morphBaseInfluences.get( mesh ); - // nodes + if ( ! morphBaseInfluence ) { - const { texture: bufferMap, stride, size } = getEntry( geometry ); + morphBaseInfluence = uniform( 1 ); + _morphBaseInfluences.set( mesh, morphBaseInfluence ); - if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence ); - if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence ); + OnObjectUpdate( ( { object } ) => { - const width = int( size.width ); + if ( object.geometry.morphTargetsRelative ) { - Loop( morphTargetsCount, ( { i } ) => { + morphBaseInfluence.value = 1; - const influence = float( 0 ).toVar(); + } else { - if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) { + morphBaseInfluence.value = 1 - object.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 ); - influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r ); + } - } else { + } ); - influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() ); + } - } + const { texture: bufferMap, stride, size } = getEntry( geometry ); - If( influence.notEqual( 0 ), () => { + if ( hasMorphPosition === true ) positionLocal.mulAssign( morphBaseInfluence ); + if ( hasMorphNormals === true ) normalLocal.mulAssign( morphBaseInfluence ); - if ( hasMorphPosition === true ) { + const width = int( size.width ); - positionLocal.addAssign( getMorph( { - bufferMap, - influence, - stride, - width, - depth: i, - offset: int( 0 ) - } ) ); + Loop( morphTargetsCount, ( { i } ) => { - } + const influence = float( 0 ).toVar(); - if ( hasMorphNormals === true ) { + if ( mesh.count > 1 && ( mesh.morphTexture !== null && mesh.morphTexture !== undefined ) ) { - normalLocal.addAssign( getMorph( { - bufferMap, - influence, - stride, - width, - depth: i, - offset: int( 1 ) - } ) ); + influence.assign( textureLoad( mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r ); - } + } else { - } ); + influence.assign( morphTargetInfluences.element( i ).toVar() ); - } ); + } - } + If( influence.notEqual( 0 ), () => { - /** - * Updates the state of the morphed mesh by updating the base influence. - * - * @param {NodeFrame} frame - The current node frame. - */ - update( /*frame*/ ) { + if ( hasMorphPosition === true ) { - const morphBaseInfluence = this.morphBaseInfluence; + positionLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 0 ) + } ) ); - if ( this.mesh.geometry.morphTargetsRelative ) { + } - morphBaseInfluence.value = 1; + if ( hasMorphNormals === true ) { - } else { + normalLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 1 ) + } ) ); - morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 ); + } - } + } ); - } + } ); -} +}, 'void' ); -export default MorphNode; -/** - * TSL function for creating a morph node. - * - * @tsl - * @function - * @param {Mesh} mesh - The mesh holding the morph targets. - * @returns {MorphNode} - */ -export const morphReference = /*@__PURE__*/ nodeProxy( MorphNode ).setParameterLength( 1 ); diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js new file mode 100644 index 00000000000000..2356aed8656dce --- /dev/null +++ b/src/nodes/accessors/Skinning.js @@ -0,0 +1,263 @@ + +import { Fn, add, uniform } from '../tsl/TSLBase.js'; +import { attribute } from '../core/AttributeNode.js'; +import { OnObjectUpdate } from '../utils/EventNode.js'; +import { normalLocal } from './Normal.js'; +import { positionLocal, positionPrevious } from './Position.js'; +import { tangentLocal } from './Tangent.js'; +import { reference, referenceBuffer } from './ReferenceNode.js'; +import { buffer } from './BufferNode.js'; +import { storage } from './StorageBufferNode.js'; +import { instanceIndex } from '../core/IndexNode.js'; + +import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js'; + +const _skeletonsUpdated = /*@__PURE__*/ new WeakMap(); +const _previousBoneMatricesData = /*@__PURE__*/ new WeakMap(); + +/** + * Computes the skinned position by applying bone matrices based on weights. + * + * @param {Node} boneMatrices - The bone matrices buffer or storage node. + * @param {Node} position - The vertex position to transform. + * @param {Node} bindMatrix - The bind matrix node. + * @param {Node} bindMatrixInverse - The inverse bind matrix node. + * @param {Node} skinIndex - The skin index attribute. + * @param {Node} skinWeight - The skin weight attribute. + * @returns {Node} The skinned position. + */ +function getSkinnedPosition( boneMatrices, position, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) { + + const boneMatX = boneMatrices.element( skinIndex.x ); + const boneMatY = boneMatrices.element( skinIndex.y ); + const boneMatZ = boneMatrices.element( skinIndex.z ); + const boneMatW = boneMatrices.element( skinIndex.w ); + + // POSITION + + const skinVertex = bindMatrix.mul( position ); + + const skinned = add( + boneMatX.mul( skinWeight.x ).mul( skinVertex ), + boneMatY.mul( skinWeight.y ).mul( skinVertex ), + boneMatZ.mul( skinWeight.z ).mul( skinVertex ), + boneMatW.mul( skinWeight.w ).mul( skinVertex ) + ); + + return bindMatrixInverse.mul( skinned ).xyz; + +} + +/** + * Computes the skinned normal and tangent vectors by applying bone matrices based on weights. + * + * @param {Node} boneMatrices - The bone matrices buffer or storage node. + * @param {Node} normal - The normal vector in local space. + * @param {Node} tangent - The tangent vector in local space. + * @param {Node} bindMatrix - The bind matrix node. + * @param {Node} bindMatrixInverse - The inverse bind matrix node. + * @param {Node} skinIndex - The skin index attribute. + * @param {Node} skinWeight - The skin weight attribute. + * @returns {{skinNormal: Node, skinTangent: Node}} The skinned normal and tangent. + */ +function getSkinnedNormalAndTangent( boneMatrices, normal, tangent, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) { + + const boneMatX = boneMatrices.element( skinIndex.x ); + const boneMatY = boneMatrices.element( skinIndex.y ); + const boneMatZ = boneMatrices.element( skinIndex.z ); + const boneMatW = boneMatrices.element( skinIndex.w ); + + // NORMAL and TANGENT + + let skinMatrix = add( + skinWeight.x.mul( boneMatX ), + skinWeight.y.mul( boneMatY ), + skinWeight.z.mul( boneMatZ ), + skinWeight.w.mul( boneMatW ) + ); + + skinMatrix = bindMatrixInverse.mul( skinMatrix ).mul( bindMatrix ); + + const skinNormal = skinMatrix.transformDirection( normal ).xyz; + const skinTangent = skinMatrix.transformDirection( tangent ).xyz; + + return { skinNormal, skinTangent }; + +} + +/** + * Retrieves or initializes the previous frame skinned position node for motion vectors. + * Uses a WeakMap to cache previous frame bone matrix arrays and their TSL buffer nodes. + * + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @param {Node} bindMatrixNode - The bind matrix node. + * @param {Node} bindMatrixInverseNode - The inverse bind matrix node. + * @param {Node} skinIndexNode - The skin index attribute. + * @param {Node} skinWeightNode - The skin weight attribute. + * @returns {Node} The skinned position from the previous frame. + */ +function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ) { + + const skeleton = skinnedMesh.skeleton; + + let data = _previousBoneMatricesData.get( skeleton ); + + if ( data === undefined ) { + + skeleton.update(); + + const previousBoneMatrices = new Float32Array( skeleton.boneMatrices ); + + data = { + previousBoneMatrices, + node: buffer( previousBoneMatrices, 'mat4', skeleton.bones.length ) + }; + + _previousBoneMatricesData.set( skeleton, data ); + + } + + return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + +} + +/** + * TSL function representing the standard skeletal animation vertex shader setup. + * Transforms positionLocal, normalLocal, and tangentLocal in-place. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + */ +export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { + + const skinIndexNode = attribute( 'skinIndex', 'uvec4' ); + const skinWeightNode = attribute( 'skinWeight', 'vec4' ); + const bindMatrixNode = reference( 'bindMatrix', 'mat4' ); + const bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' ); + const boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); + + OnObjectUpdate( ( { object, frameId } ) => { + + const skeleton = object.skeleton; + + if ( _skeletonsUpdated.get( skeleton ) !== frameId ) { + + _skeletonsUpdated.set( skeleton, frameId ); + + const skeletonData = _previousBoneMatricesData.get( skeleton ); + + if ( skeletonData !== undefined ) { + + skeletonData.previousBoneMatrices.set( skeleton.boneMatrices ); + + } + + skeleton.update(); + + } + + } ); + + if ( builder.needsPreviousData() ) { + + const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + + positionPrevious.assign( previousSkinnedPosition ); + + } + + const skinPosition = getSkinnedPosition( boneMatricesNode, positionLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + positionLocal.assign( skinPosition ); + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + + normalLocal.assign( skinNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.assign( skinTangent ); + + } + + } + +}, 'void' ); + +/** + * TSL function that computes skeletal animation for custom compute passes. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @param {Node} [toPosition=null] - The target position node to assign. + * @returns {Node} The computed skinned position node. + */ +export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], builder ) => { + + const positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + const skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + const skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + const bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' ); + const bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ); + const boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length ); + + const skeleton = skinnedMesh.skeleton; + + OnObjectUpdate( ( { frameId } ) => { + + if ( _skeletonsUpdated.get( skeleton ) !== frameId ) { + + _skeletonsUpdated.set( skeleton, frameId ); + + const state = _previousBoneMatricesData.get( skeleton ); + + if ( state !== undefined ) { + + state.previousBoneMatrices.set( skeleton.boneMatrices ); + + } + + skeleton.update(); + + } + + } ); + + if ( builder.needsPreviousData() ) { + + const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + + positionPrevious.assign( previousSkinnedPosition ); + + } + + const skinPosition = getSkinnedPosition( boneMatricesNode, positionNode, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + + if ( toPosition !== null ) { + + toPosition.assign( skinPosition ); + + } + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + + normalLocal.assign( skinNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.assign( skinTangent ); + + } + + } + + return skinPosition; + +} ); + + diff --git a/src/nodes/accessors/SkinningNode.js b/src/nodes/accessors/SkinningNode.js deleted file mode 100644 index 589a2270608d22..00000000000000 --- a/src/nodes/accessors/SkinningNode.js +++ /dev/null @@ -1,328 +0,0 @@ -import Node from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { nodeObject } from '../tsl/TSLBase.js'; -import { attribute } from '../core/AttributeNode.js'; -import { reference, referenceBuffer } from './ReferenceNode.js'; -import { add } from '../math/OperatorNode.js'; -import { normalLocal } from './Normal.js'; -import { positionLocal, positionPrevious } from './Position.js'; -import { tangentLocal } from './Tangent.js'; -import { uniform } from '../core/UniformNode.js'; -import { buffer } from './BufferNode.js'; -import { storage } from './StorageBufferNode.js'; -import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js'; -import { instanceIndex } from '../core/IndexNode.js'; - -const _frameId = new WeakMap(); - -/** - * This node implements the vertex transformation shader logic which is required - * for skinning/skeletal animation. - * - * @augments Node - */ -class SkinningNode extends Node { - - static get type() { - - return 'SkinningNode'; - - } - - /** - * Constructs a new skinning node. - * - * @param {SkinnedMesh} skinnedMesh - The skinned mesh. - */ - constructor( skinnedMesh ) { - - super( 'void' ); - - /** - * The skinned mesh. - * - * @type {SkinnedMesh} - */ - this.skinnedMesh = skinnedMesh; - - /** - * The update type overwritten since skinning nodes are updated per object. - * - * @type {string} - */ - this.updateType = NodeUpdateType.OBJECT; - - // - - /** - * The skin index attribute. - * - * @type {AttributeNode} - */ - this.skinIndexNode = attribute( 'skinIndex', 'uvec4' ); - - /** - * The skin weight attribute. - * - * @type {AttributeNode} - */ - this.skinWeightNode = attribute( 'skinWeight', 'vec4' ); - - /** - * The bind matrix node. - * - * @type {Node} - */ - this.bindMatrixNode = reference( 'bindMatrix', 'mat4' ); - - /** - * The bind matrix inverse node. - * - * @type {Node} - */ - this.bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' ); - - /** - * The bind matrices as a uniform buffer node. - * - * @type {Node} - */ - this.boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); - - /** - * The current vertex position in local space. - * - * @type {Node} - */ - this.positionNode = positionLocal; - - /** - * The result of vertex position in local space. - * - * @type {Node} - */ - this.toPositionNode = positionLocal; - - /** - * The previous bind matrices as a uniform buffer node. - * Required for computing motion vectors. - * - * @type {?Node} - * @default null - */ - this.previousBoneMatricesNode = null; - - } - - /** - * Transforms the given vertex position via skinning. - * - * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices - * @param {Node} [position=this.positionNode] - The vertex position in local space. - * @return {Node} The transformed vertex position. - */ - getSkinnedPosition( boneMatrices = this.boneMatricesNode, position = this.positionNode ) { - - const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; - - const boneMatX = boneMatrices.element( skinIndexNode.x ); - const boneMatY = boneMatrices.element( skinIndexNode.y ); - const boneMatZ = boneMatrices.element( skinIndexNode.z ); - const boneMatW = boneMatrices.element( skinIndexNode.w ); - - // POSITION - - const skinVertex = bindMatrixNode.mul( position ); - - const skinned = add( - boneMatX.mul( skinWeightNode.x ).mul( skinVertex ), - boneMatY.mul( skinWeightNode.y ).mul( skinVertex ), - boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ), - boneMatW.mul( skinWeightNode.w ).mul( skinVertex ) - ); - - return bindMatrixInverseNode.mul( skinned ).xyz; - - } - - /** - * Transforms the given vertex normal and tangent via skinning. - * - * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices - * @param {Node} [normal=normalLocal] - The vertex normal in local space. - * @param {Node} [tangent=tangentLocal] - The vertex tangent in local space. - * @return {{skinNormal: Node, skinTangent:Node}} The transformed vertex normal and tangent. - */ - getSkinnedNormalAndTangent( boneMatrices = this.boneMatricesNode, normal = normalLocal, tangent = tangentLocal ) { - - const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; - - const boneMatX = boneMatrices.element( skinIndexNode.x ); - const boneMatY = boneMatrices.element( skinIndexNode.y ); - const boneMatZ = boneMatrices.element( skinIndexNode.z ); - const boneMatW = boneMatrices.element( skinIndexNode.w ); - - // NORMAL and TANGENT - - let skinMatrix = add( - skinWeightNode.x.mul( boneMatX ), - skinWeightNode.y.mul( boneMatY ), - skinWeightNode.z.mul( boneMatZ ), - skinWeightNode.w.mul( boneMatW ) - ); - - skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode ); - - const skinNormal = skinMatrix.transformDirection( normal ).xyz; - const skinTangent = skinMatrix.transformDirection( tangent ).xyz; - - return { skinNormal, skinTangent }; - - } - - /** - * Computes the transformed/skinned vertex position of the previous frame. - * - * @param {NodeBuilder} builder - The current node builder. - * @return {Node} The skinned position from the previous frame. - */ - getPreviousSkinnedPosition( builder ) { - - const skinnedMesh = builder.object; - - if ( this.previousBoneMatricesNode === null ) { - - skinnedMesh.skeleton.previousBoneMatrices = new Float32Array( skinnedMesh.skeleton.boneMatrices ); - - this.previousBoneMatricesNode = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); - - } - - return this.getSkinnedPosition( this.previousBoneMatricesNode, positionPrevious ); - - } - - /** - * Setups the skinning node by assigning the transformed vertex data to predefined node variables. - * - * @param {NodeBuilder} builder - The current node builder. - * @return {Node} The transformed vertex position. - */ - setup( builder ) { - - if ( builder.needsPreviousData() ) { - - positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) ); - - } - - const skinPosition = this.getSkinnedPosition(); - - if ( this.toPositionNode ) this.toPositionNode.assign( skinPosition ); - - // - - if ( builder.hasGeometryAttribute( 'normal' ) ) { - - const { skinNormal, skinTangent } = this.getSkinnedNormalAndTangent(); - - normalLocal.assign( skinNormal ); - - if ( builder.hasGeometryAttribute( 'tangent' ) ) { - - tangentLocal.assign( skinTangent ); - - } - - } - - return skinPosition; - - } - - /** - * Generates the code snippet of the skinning node. - * - * @param {NodeBuilder} builder - The current node builder. - * @param {string} output - The current output. - * @return {string} The generated code snippet. - */ - generate( builder, output ) { - - if ( output !== 'void' ) { - - return super.generate( builder, output ); - - } - - } - - /** - * Updates the state of the skinned mesh by updating the skeleton once per frame. - * - * @param {NodeFrame} frame - The current node frame. - */ - update( frame ) { - - const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton; - - if ( _frameId.get( skeleton ) === frame.frameId ) return; - - _frameId.set( skeleton, frame.frameId ); - - if ( this.previousBoneMatricesNode !== null ) { - - if ( skeleton.previousBoneMatrices === null ) { - - // cloned skeletons miss "previousBoneMatrices" in their first updated - - skeleton.previousBoneMatrices = new Float32Array( skeleton.boneMatrices ); - - } - - skeleton.previousBoneMatrices.set( skeleton.boneMatrices ); - - - } - - skeleton.update(); - - } - -} - -export default SkinningNode; - -/** - * TSL function for creating a skinning node. - * - * @tsl - * @function - * @param {SkinnedMesh} skinnedMesh - The skinned mesh. - * @returns {SkinningNode} - */ -export const skinning = ( skinnedMesh ) => new SkinningNode( skinnedMesh ); - -/** - * TSL function for computing skinning. - * - * @tsl - * @function - * @param {SkinnedMesh} skinnedMesh - The skinned mesh. - * @param {Node} [toPosition=null] - The target position. - * @returns {SkinningNode} - */ -export const computeSkinning = ( skinnedMesh, toPosition = null ) => { - - const node = new SkinningNode( skinnedMesh ); - node.positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); - node.skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); - node.skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); - node.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' ); - node.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ); - node.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length ); - node.toPositionNode = toPosition; - - return nodeObject( node ); - -}; diff --git a/src/objects/InstancedMesh.js b/src/objects/InstancedMesh.js index d519633ddfff5c..7c29cc68873fe3 100644 --- a/src/objects/InstancedMesh.js +++ b/src/objects/InstancedMesh.js @@ -56,15 +56,6 @@ class InstancedMesh extends Mesh { */ this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - /** - * Represents the local transformation of all instances of the previous frame. - * Required for computing velocity. Maintained in {@link InstanceNode}. - * - * @type {?InstancedBufferAttribute} - * @default null - */ - this.previousInstanceMatrix = null; - /** * Represents the color of all instances. You have to set its * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data @@ -194,8 +185,6 @@ class InstancedMesh extends Mesh { this.instanceMatrix.copy( source.instanceMatrix ); - if ( source.previousInstanceMatrix !== null ) this.previousInstanceMatrix = source.previousInstanceMatrix.clone(); - if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); diff --git a/src/objects/Skeleton.js b/src/objects/Skeleton.js index 5d1361086ae3c8..31384a9bcd40eb 100644 --- a/src/objects/Skeleton.js +++ b/src/objects/Skeleton.js @@ -70,15 +70,6 @@ class Skeleton { */ this.boneMatrices = null; - /** - * An array buffer holding the bone data of the previous frame. - * Required for computing velocity. Maintained in {@link SkinningNode}. - * - * @type {?Float32Array} - * @default null - */ - this.previousBoneMatrices = null; - /** * A texture holding the bone data for use * in the vertex shader. diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js index 0d732bdeb46d6c..1503ff56e96c92 100644 --- a/src/renderers/common/RenderObject.js +++ b/src/renderers/common/RenderObject.js @@ -684,7 +684,7 @@ class RenderObject { // structural equality isn't sufficient for morph targets since the // data are maintained in textures. only if the targets are all equal - // the texture and thus the instance of `MorphNode` can be shared. + // the texture and thus the `morphReference` can be shared. for ( const name of Object.keys( geometry.morphAttributes ).sort() ) {