UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

254 lines (233 loc) 8.79 kB
import { Mesh } from './Mesh.js'; import { Box3 } from '../math/Box3.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Sphere } from '../math/Sphere.js'; import { Vector3 } from '../math/Vector3.js'; import { Vector4 } from '../math/Vector4.js'; import { Ray } from '../math/Ray.js'; import { AttachedBindMode, DetachedBindMode } from '../constants.js'; const _basePosition = /*@__PURE__*/new Vector3(); const _skinIndex = /*@__PURE__*/new Vector4(); const _skinWeight = /*@__PURE__*/new Vector4(); const _vector3 = /*@__PURE__*/new Vector3(); const _matrix4 = /*@__PURE__*/new Matrix4(); const _vertex = /*@__PURE__*/new Vector3(); const _sphere = /*@__PURE__*/new Sphere(); const _inverseMatrix = /*@__PURE__*/new Matrix4(); const _ray = /*@__PURE__*/new Ray(); /** * A mesh that has a {@link Skeleton} that can then be used to animate the * vertices of the geometry with skinning/skeleton animation. * * Next to a valid skeleton, the skinned mesh requires skin indices and weights * as buffer attributes in its geometry. These attribute define which bones affect a single * vertex to a certain extend. * * Typically skinned meshes are not created manually but loaders like {@link GLTFLoader} * or {@link FBXLoader } import respective models. * * @augments Mesh */ class SkinnedMesh extends Mesh { /** * Constructs a new skinned mesh. * * @param {BufferGeometry} [geometry] - The mesh geometry. * @param {Material|Array<Material>} [material] - The mesh material. */ constructor(geometry, material) { super(geometry, material); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isSkinnedMesh = true; this.type = 'SkinnedMesh'; /** * `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton * across multiple skinned meshes. * * @type {(AttachedBindMode|DetachedBindMode)} * @default AttachedBindMode */ this.bindMode = AttachedBindMode; /** * The base matrix that is used for the bound bone transforms. * * @type {Matrix4} */ this.bindMatrix = new Matrix4(); /** * The base matrix that is used for resetting the bound bone transforms. * * @type {Matrix4} */ this.bindMatrixInverse = new Matrix4(); /** * The bounding box of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingBox}. * * @type {?Box3} * @default null */ this.boundingBox = null; /** * The bounding sphere of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingSphere}. * * @type {?Sphere} * @default null */ this.boundingSphere = null; } /** * Computes the bounding box of the skinned mesh, and updates {@link SkinnedMesh#boundingBox}. * The bounding box is not automatically computed by the engine; this method must be called by your app. * If the skinned mesh is animated, the bounding box should be recomputed per frame in order to reflect * the current animation state. */ computeBoundingBox() { const geometry = this.geometry; if (this.boundingBox === null) { this.boundingBox = new Box3(); } this.boundingBox.makeEmpty(); const positionAttribute = geometry.getAttribute('position'); for (let i = 0; i < positionAttribute.count; i++) { this.getVertexPosition(i, _vertex); this.boundingBox.expandByPoint(_vertex); } } /** * Computes the bounding sphere of the skinned mesh, and updates {@link SkinnedMesh#boundingSphere}. * The bounding sphere is automatically computed by the engine once when it is needed, e.g., for ray casting * and view frustum culling. If the skinned mesh is animated, the bounding sphere should be recomputed * per frame in order to reflect the current animation state. */ computeBoundingSphere() { const geometry = this.geometry; if (this.boundingSphere === null) { this.boundingSphere = new Sphere(); } this.boundingSphere.makeEmpty(); const positionAttribute = geometry.getAttribute('position'); for (let i = 0; i < positionAttribute.count; i++) { this.getVertexPosition(i, _vertex); this.boundingSphere.expandByPoint(_vertex); } } copy(source, recursive) { super.copy(source, recursive); this.bindMode = source.bindMode; this.bindMatrix.copy(source.bindMatrix); this.bindMatrixInverse.copy(source.bindMatrixInverse); this.skeleton = source.skeleton; if (source.boundingBox !== null) this.boundingBox = source.boundingBox.clone(); if (source.boundingSphere !== null) this.boundingSphere = source.boundingSphere.clone(); return this; } raycast(raycaster, intersects) { const material = this.material; const matrixWorld = this.matrixWorld; if (material === undefined) return; // test with bounding sphere in world space if (this.boundingSphere === null) this.computeBoundingSphere(); _sphere.copy(this.boundingSphere); _sphere.applyMatrix4(matrixWorld); if (raycaster.ray.intersectsSphere(_sphere) === false) return; // convert ray to local space of skinned mesh _inverseMatrix.copy(matrixWorld).invert(); _ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix); // test with bounding box in local space if (this.boundingBox !== null) { if (_ray.intersectsBox(this.boundingBox) === false) return; } // test for intersections with geometry this._computeIntersections(raycaster, intersects, _ray); } getVertexPosition(index, target) { super.getVertexPosition(index, target); this.applyBoneTransform(index, target); return target; } /** * Binds the given skeleton to the skinned mesh. * * @param {Skeleton} skeleton - The skeleton to bind. * @param {Matrix4} [bindMatrix] - The bind matrix. If no bind matrix is provided, * the skinned mesh's world matrix will be used instead. */ bind(skeleton, bindMatrix) { this.skeleton = skeleton; if (bindMatrix === undefined) { this.updateMatrixWorld(true); this.skeleton.calculateInverses(); bindMatrix = this.matrixWorld; } this.bindMatrix.copy(bindMatrix); this.bindMatrixInverse.copy(bindMatrix).invert(); } /** * This method sets the skinned mesh in the rest pose). */ pose() { this.skeleton.pose(); } /** * Normalizes the skin weights which are defined as a buffer attribute * in the skinned mesh's geometry. */ normalizeSkinWeights() { const vector = new Vector4(); const skinWeight = this.geometry.attributes.skinWeight; for (let i = 0, l = skinWeight.count; i < l; i++) { vector.fromBufferAttribute(skinWeight, i); const scale = 1.0 / vector.manhattanLength(); if (scale !== Infinity) { vector.multiplyScalar(scale); } else { vector.set(1, 0, 0, 0); // do something reasonable } skinWeight.setXYZW(i, vector.x, vector.y, vector.z, vector.w); } } updateMatrixWorld(force) { super.updateMatrixWorld(force); if (this.bindMode === AttachedBindMode) { this.bindMatrixInverse.copy(this.matrixWorld).invert(); } else if (this.bindMode === DetachedBindMode) { this.bindMatrixInverse.copy(this.bindMatrix).invert(); } else { console.warn('THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode); } } /** * Applies the bone transform associated with the given index to the given * vertex position. Returns the updated vector. * * @param {number} index - The vertex index. * @param {Vector3} target - The target object that is used to store the method's result. * the skinned mesh's world matrix will be used instead. * @return {Vector3} The updated vertex position. */ applyBoneTransform(index, target) { const skeleton = this.skeleton; const geometry = this.geometry; _skinIndex.fromBufferAttribute(geometry.attributes.skinIndex, index); _skinWeight.fromBufferAttribute(geometry.attributes.skinWeight, index); _basePosition.copy(target).applyMatrix4(this.bindMatrix); target.set(0, 0, 0); for (let i = 0; i < 4; i++) { const weight = _skinWeight.getComponent(i); if (weight !== 0) { const boneIndex = _skinIndex.getComponent(i); _matrix4.multiplyMatrices(skeleton.bones[boneIndex].matrixWorld, skeleton.boneInverses[boneIndex]); target.addScaledVector(_vector3.copy(_basePosition).applyMatrix4(_matrix4), weight); } } return target.applyMatrix4(this.bindMatrixInverse); } } export { SkinnedMesh };