Spaces:
Running
Running
| import { FloatType, RGBAFormat } from '../../constants.js'; | |
| import { DataTexture2DArray } from '../../textures/DataTexture2DArray.js'; | |
| import { Vector3 } from '../../math/Vector3.js'; | |
| import { Vector2 } from '../../math/Vector2.js'; | |
| function numericalSort(a, b) { | |
| return a[0] - b[0]; | |
| } | |
| function absNumericalSort(a, b) { | |
| return Math.abs(b[1]) - Math.abs(a[1]); | |
| } | |
| function denormalize(morph, attribute) { | |
| let denominator = 1; | |
| const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array; | |
| if (array instanceof Int8Array) denominator = 127; | |
| else if (array instanceof Int16Array) denominator = 32767; | |
| else if (array instanceof Int32Array) denominator = 2147483647; | |
| else console.error('THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array); | |
| morph.divideScalar(denominator); | |
| } | |
| function WebGLMorphtargets(gl, capabilities, textures) { | |
| const influencesList = {}; | |
| const morphInfluences = new Float32Array(8); | |
| const morphTextures = new WeakMap(); | |
| const morph = new Vector3(); | |
| const workInfluences = []; | |
| for (let i = 0; i < 8; i++) { | |
| workInfluences[i] = [i, 0]; | |
| } | |
| function update(object, geometry, material, program) { | |
| const objectInfluences = object.morphTargetInfluences; | |
| if (capabilities.isWebGL2 === true) { | |
| // instead of using attributes, the WebGL 2 code path encodes morph targets | |
| // into an array of data textures. Each layer represents a single morph target. | |
| const numberOfMorphTargets = geometry.morphAttributes.position.length; | |
| let entry = morphTextures.get(geometry); | |
| if (entry === undefined || entry.count !== numberOfMorphTargets) { | |
| if (entry !== undefined) entry.texture.dispose(); | |
| const hasMorphNormals = geometry.morphAttributes.normal !== undefined; | |
| const morphTargets = geometry.morphAttributes.position; | |
| const morphNormals = geometry.morphAttributes.normal || []; | |
| const numberOfVertices = geometry.attributes.position.count; | |
| const numberOfVertexData = hasMorphNormals === true ? 2 : 1; // (v,n) vs. (v) | |
| let width = numberOfVertices * numberOfVertexData; | |
| let height = 1; | |
| if (width > capabilities.maxTextureSize) { | |
| height = Math.ceil(width / capabilities.maxTextureSize); | |
| width = capabilities.maxTextureSize; | |
| } | |
| const buffer = new Float32Array(width * height * 4 * numberOfMorphTargets); | |
| const texture = new DataTexture2DArray(buffer, width, height, numberOfMorphTargets); | |
| texture.format = RGBAFormat; // using RGBA since RGB might be emulated (and is thus slower) | |
| texture.type = FloatType; | |
| texture.needsUpdate = true; | |
| // fill buffer | |
| const vertexDataStride = numberOfVertexData * 4; | |
| for (let i = 0; i < numberOfMorphTargets; i++) { | |
| const morphTarget = morphTargets[i]; | |
| const morphNormal = morphNormals[i]; | |
| const offset = width * height * 4 * i; | |
| for (let j = 0; j < morphTarget.count; j++) { | |
| morph.fromBufferAttribute(morphTarget, j); | |
| if (morphTarget.normalized === true) denormalize(morph, morphTarget); | |
| const stride = j * vertexDataStride; | |
| buffer[offset + stride + 0] = morph.x; | |
| buffer[offset + stride + 1] = morph.y; | |
| buffer[offset + stride + 2] = morph.z; | |
| buffer[offset + stride + 3] = 0; | |
| if (hasMorphNormals === true) { | |
| morph.fromBufferAttribute(morphNormal, j); | |
| if (morphNormal.normalized === true) denormalize(morph, morphNormal); | |
| buffer[offset + stride + 4] = morph.x; | |
| buffer[offset + stride + 5] = morph.y; | |
| buffer[offset + stride + 6] = morph.z; | |
| buffer[offset + stride + 7] = 0; | |
| } | |
| } | |
| } | |
| entry = { | |
| count: numberOfMorphTargets, | |
| texture: texture, | |
| size: new Vector2(width, height), | |
| }; | |
| morphTextures.set(geometry, entry); | |
| function disposeTexture() { | |
| texture.dispose(); | |
| morphTextures.delete(geometry); | |
| geometry.removeEventListener('dispose', disposeTexture); | |
| } | |
| geometry.addEventListener('dispose', disposeTexture); | |
| } | |
| // | |
| let morphInfluencesSum = 0; | |
| for (let i = 0; i < objectInfluences.length; i++) { | |
| morphInfluencesSum += objectInfluences[i]; | |
| } | |
| const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; | |
| program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence); | |
| program.getUniforms().setValue(gl, 'morphTargetInfluences', objectInfluences); | |
| program.getUniforms().setValue(gl, 'morphTargetsTexture', entry.texture, textures); | |
| program.getUniforms().setValue(gl, 'morphTargetsTextureSize', entry.size); | |
| } else { | |
| // When object doesn't have morph target influences defined, we treat it as a 0-length array | |
| // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences | |
| const length = objectInfluences === undefined ? 0 : objectInfluences.length; | |
| let influences = influencesList[geometry.id]; | |
| if (influences === undefined || influences.length !== length) { | |
| // initialise list | |
| influences = []; | |
| for (let i = 0; i < length; i++) { | |
| influences[i] = [i, 0]; | |
| } | |
| influencesList[geometry.id] = influences; | |
| } | |
| // Collect influences | |
| for (let i = 0; i < length; i++) { | |
| const influence = influences[i]; | |
| influence[0] = i; | |
| influence[1] = objectInfluences[i]; | |
| } | |
| influences.sort(absNumericalSort); | |
| for (let i = 0; i < 8; i++) { | |
| if (i < length && influences[i][1]) { | |
| workInfluences[i][0] = influences[i][0]; | |
| workInfluences[i][1] = influences[i][1]; | |
| } else { | |
| workInfluences[i][0] = Number.MAX_SAFE_INTEGER; | |
| workInfluences[i][1] = 0; | |
| } | |
| } | |
| workInfluences.sort(numericalSort); | |
| const morphTargets = geometry.morphAttributes.position; | |
| const morphNormals = geometry.morphAttributes.normal; | |
| let morphInfluencesSum = 0; | |
| for (let i = 0; i < 8; i++) { | |
| const influence = workInfluences[i]; | |
| const index = influence[0]; | |
| const value = influence[1]; | |
| if (index !== Number.MAX_SAFE_INTEGER && value) { | |
| if (morphTargets && geometry.getAttribute('morphTarget' + i) !== morphTargets[index]) { | |
| geometry.setAttribute('morphTarget' + i, morphTargets[index]); | |
| } | |
| if (morphNormals && geometry.getAttribute('morphNormal' + i) !== morphNormals[index]) { | |
| geometry.setAttribute('morphNormal' + i, morphNormals[index]); | |
| } | |
| morphInfluences[i] = value; | |
| morphInfluencesSum += value; | |
| } else { | |
| if (morphTargets && geometry.hasAttribute('morphTarget' + i) === true) { | |
| geometry.deleteAttribute('morphTarget' + i); | |
| } | |
| if (morphNormals && geometry.hasAttribute('morphNormal' + i) === true) { | |
| geometry.deleteAttribute('morphNormal' + i); | |
| } | |
| morphInfluences[i] = 0; | |
| } | |
| } | |
| // GLSL shader uses formula baseinfluence * base + sum(target * influence) | |
| // This allows us to switch between absolute morphs and relative morphs without changing shader code | |
| // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) | |
| const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; | |
| program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence); | |
| program.getUniforms().setValue(gl, 'morphTargetInfluences', morphInfluences); | |
| } | |
| } | |
| return { | |
| update: update, | |
| }; | |
| } | |
| export { WebGLMorphtargets }; | |