import { RGBAFormat, FloatType } from '../constants.js'; import { Bone } from './Bone.js'; import { Matrix4 } from '../math/Matrix4.js'; import { DataTexture } from '../textures/DataTexture.js'; import * as MathUtils from '../math/MathUtils.js'; const _offsetMatrix = /*@__PURE__*/ new Matrix4(); const _identityMatrix = /*@__PURE__*/ new Matrix4(); class Skeleton { constructor(bones = [], boneInverses = []) { this.uuid = MathUtils.generateUUID(); this.bones = bones.slice(0); this.boneInverses = boneInverses; this.boneMatrices = null; this.boneTexture = null; this.boneTextureSize = 0; this.frame = -1; this.init(); } init() { const bones = this.bones; const boneInverses = this.boneInverses; this.boneMatrices = new Float32Array(bones.length * 16); // calculate inverse bone matrices if necessary if (boneInverses.length === 0) { this.calculateInverses(); } else { // handle special case if (bones.length !== boneInverses.length) { console.warn('THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.'); this.boneInverses = []; for (let i = 0, il = this.bones.length; i < il; i++) { this.boneInverses.push(new Matrix4()); } } } } calculateInverses() { this.boneInverses.length = 0; for (let i = 0, il = this.bones.length; i < il; i++) { const inverse = new Matrix4(); if (this.bones[i]) { inverse.copy(this.bones[i].matrixWorld).invert(); } this.boneInverses.push(inverse); } } pose() { // recover the bind-time world matrices for (let i = 0, il = this.bones.length; i < il; i++) { const bone = this.bones[i]; if (bone) { bone.matrixWorld.copy(this.boneInverses[i]).invert(); } } // compute the local matrices, positions, rotations and scales for (let i = 0, il = this.bones.length; i < il; i++) { const bone = this.bones[i]; if (bone) { if (bone.parent && bone.parent.isBone) { bone.matrix.copy(bone.parent.matrixWorld).invert(); bone.matrix.multiply(bone.matrixWorld); } else { bone.matrix.copy(bone.matrixWorld); } bone.matrix.decompose(bone.position, bone.quaternion, bone.scale); } } } update() { const bones = this.bones; const boneInverses = this.boneInverses; const boneMatrices = this.boneMatrices; const boneTexture = this.boneTexture; // flatten bone matrices to array for (let i = 0, il = bones.length; i < il; i++) { // compute the offset between the current and the original transform const matrix = bones[i] ? bones[i].matrixWorld : _identityMatrix; _offsetMatrix.multiplyMatrices(matrix, boneInverses[i]); _offsetMatrix.toArray(boneMatrices, i * 16); } if (boneTexture !== null) { boneTexture.needsUpdate = true; } } clone() { return new Skeleton(this.bones, this.boneInverses); } computeBoneTexture() { // layout (1 matrix = 4 pixels) // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) let size = Math.sqrt(this.bones.length * 4); // 4 pixels needed for 1 matrix size = MathUtils.ceilPowerOfTwo(size); size = Math.max(size, 4); const boneMatrices = new Float32Array(size * size * 4); // 4 floats per RGBA pixel boneMatrices.set(this.boneMatrices); // copy current values const boneTexture = new DataTexture(boneMatrices, size, size, RGBAFormat, FloatType); boneTexture.needsUpdate = true; this.boneMatrices = boneMatrices; this.boneTexture = boneTexture; this.boneTextureSize = size; return this; } getBoneByName(name) { for (let i = 0, il = this.bones.length; i < il; i++) { const bone = this.bones[i]; if (bone.name === name) { return bone; } } return undefined; } dispose() { if (this.boneTexture !== null) { this.boneTexture.dispose(); this.boneTexture = null; } } fromJSON(json, bones) { this.uuid = json.uuid; for (let i = 0, l = json.bones.length; i < l; i++) { const uuid = json.bones[i]; let bone = bones[uuid]; if (bone === undefined) { console.warn('THREE.Skeleton: No bone found with UUID:', uuid); bone = new Bone(); } this.bones.push(bone); this.boneInverses.push(new Matrix4().fromArray(json.boneInverses[i])); } this.init(); return this; } toJSON() { const data = { metadata: { version: 4.5, type: 'Skeleton', generator: 'Skeleton.toJSON', }, bones: [], boneInverses: [], }; data.uuid = this.uuid; const bones = this.bones; const boneInverses = this.boneInverses; for (let i = 0, l = bones.length; i < l; i++) { const bone = bones[i]; data.bones.push(bone.uuid); const boneInverse = boneInverses[i]; data.boneInverses.push(boneInverse.toArray()); } return data; } } export { Skeleton };