@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
429 lines (350 loc) • 9.04 kB
JavaScript
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;