UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

429 lines (350 loc) • 9.04 kB
import { assert } from "../../../../core/assert.js"; import { BvhClient } from "../../../../core/bvh2/bvh3/BvhClient.js"; import { AABB3 } from "../../../../core/geom/3d/aabb/AABB3.js"; import { aabb3_matrix4_project } from "../../../../core/geom/3d/aabb/aabb3_matrix4_project.js"; import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js"; import { Transform } from "../../../ecs/transform/Transform.js"; import { applyTransformToThreeObject } from "./applyTransformToThreeObject.js"; import { getSkeletonBoneByType } from "./SkeletonUtils.js"; /** * * @enum {number} */ export const MeshFlags = { CastShadow: 1, ReceiveShadow: 2, Loaded: 4, InView: 8 }; const DEFAULT_FLAGS = 0; /** * * @type {number} */ const EQUALITY_FLAGS = MeshFlags.CastShadow | MeshFlags.ReceiveShadow ; /** * Traversal stack * @type {Object3D[]} */ const scratch_stack = []; /** * @readonly * @type {Transform} */ const scratch_transform = new Transform(); class Mesh { constructor() { /** * * @type {string|null} */ this.url = null; /** * * @type {Object3D|null} */ this.mesh = null; /** * @transient * @type {Asset|null} */ this.asset = null; /** * * @type {number} */ this.opacity = 1; /** * Initialized to size 1x1x1 * @type {AABB3} */ this.boundingBox = new AABB3(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5); /** * * @type {BvhClient} */ this.__bvh_leaf = new BvhClient(); /** * @private * @type {number} */ this.flags = DEFAULT_FLAGS; } /** * * @param {Transform} transform */ internalApplyTransform(transform) { assert.equal(transform.isTransform, true, 'transform.isTransform! == true'); if (!this.hasMesh()) { // not loaded, can't apply anything return; } /** * @type {Transform} */ let _t; const asset = this.asset; if (asset.has_root_transform) { _t = scratch_transform; _t.multiplyTransforms(transform, asset.root_transform); // console.log("root transform"); } else { _t = transform; } /** * * @type {Object3D} */ const m = this.mesh; applyTransformToThreeObject(m, _t); //set bvh aabb3_matrix4_project(this.__bvh_leaf.bounds, this.boundingBox, _t.matrix); this.__bvh_leaf.write_bounds(); } /** * * @param {AABB3} destination */ getBoundingBox(destination) { destination.readFromArray(this.__bvh_leaf.bounds); } /** * * @param {AABB3} destination */ getOriginalBoundingBox(destination) { destination.copy(this.boundingBox); } /** * * @return {boolean} */ get castShadow() { return this.getFlag(MeshFlags.CastShadow); } /** * * @param {boolean} v */ set castShadow(v) { this.writeFlag(MeshFlags.CastShadow, v); } /** * * @return {boolean} */ get receiveShadow() { return this.getFlag(MeshFlags.ReceiveShadow); } /** * * @param {boolean} v */ set receiveShadow(v) { this.writeFlag(MeshFlags.ReceiveShadow, v); } get isLoaded() { return this.getFlag(MeshFlags.Loaded); } /** * * @param {boolean} v */ set isLoaded(v) { this.writeFlag(MeshFlags.Loaded, v); } /** * * @param {number|MeshFlags} flag * @returns {void} */ setFlag(flag) { this.flags |= flag; } /** * * @param {number|MeshFlags} flag * @returns {void} */ clearFlag(flag) { this.flags &= ~flag; } /** * * @param {number|MeshFlags} flag * @param {boolean} value */ writeFlag(flag, value) { if (value) { this.setFlag(flag); } else { this.clearFlag(flag); } } /** * * @param {number|MeshFlags} flag * @returns {boolean} */ getFlag(flag) { return (this.flags & flag) === flag; } /** * * @returns {boolean} */ hasMesh() { return this.mesh !== undefined && this.mesh !== null; } fromJSON({ url = null, castShadow = true, receiveShadow = true, opacity = 1 }) { this.url = url; this.castShadow = castShadow; this.receiveShadow = receiveShadow; this.opacity = opacity; } toJSON() { return { url: this.url, castShadow: this.castShadow, receiveShadow: this.receiveShadow, opacity: this.opacity }; } hash() { return computeStringHash(this.url) ^ this.flags; } /** * * @param {Mesh} other * @returns {boolean} */ equals(other) { return this.url === other.url && (this.flags & EQUALITY_FLAGS) === (other.flags & EQUALITY_FLAGS) && this.opacity === other.opacity ; } /** * * @param {Mesh} other */ copy(other) { this.url = other.url; this.castShadow = other.castShadow; this.receiveShadow = other.receiveShadow; this.opacity = other.opacity; } /** * * @returns {Mesh} */ clone() { const clone = new Mesh(); clone.copy(this); return clone; } /** * * @param json * @returns {Mesh} */ static fromJSON(json) { const result = new Mesh(); result.fromJSON(json); return result; } /** * * @param {string} url * @returns {Mesh} */ static fromURL(url) { const r = new Mesh(); r.url = url; r.castShadow = true; r.receiveShadow = true; return r; } /** * * @param {string} name * @return {Object3D|undefined} */ getDescendantObjectByName(name) { assert.isString(name, "name"); const m = this.mesh; if (m === null) { return undefined; } let stack_top = 0; scratch_stack[stack_top++] = m; while (stack_top > 0) { stack_top--; const top = scratch_stack[stack_top]; if (top.name === name) { return top; } const children = top.children; const n = children.length; for (let i = 0; i < n; i++) { scratch_stack[stack_top++] = children[i]; } } return undefined; } /** * * @param {Vector3} result * @param {HumanoidBoneType|string} boneType */ getBonePositionByType(result, boneType) { if (!this.getFlag(MeshFlags.Loaded)) { // mesh is not loaded yet, return origin instead result.set(0, 0, 0); } else { const skeletonBone = getSkeletonBoneByType(this, boneType); if (skeletonBone === null) { // console.warn("Couldn't find bone '" + boneType + "', using mesh origin instead"); const mesh = this.mesh; if (mesh !== null) { result.copy(mesh.position); } else { //no mesh loaded result.set(0, 0, 0); } } else { const matrixWorld = skeletonBone.matrixWorld; result.setFromMatrixPosition(matrixWorld.elements); } } } dispose() { if (this.getFlag(MeshFlags.Loaded)) { this.mesh.traverse(disposeObject3DResources); } } } /** * * @param {Object3D} o */ function disposeObject3DResources(o) { if (o.isSkinnedMesh) { const skeleton = o.skeleton; if (skeleton !== undefined && skeleton.boneTexture !== undefined && skeleton.boneTexture !== null) { // three.js renderer creates textures internally for each skinned mesh, these need to be manually cleaned up skeleton.boneTexture.dispose(); } } } Mesh.typeName = "Mesh"; export default Mesh;