@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
312 lines (265 loc) • 7.97 kB
JavaScript
import { mat4 } from "gl-matrix";
import { BvhClient } from "../../../../core/bvh2/bvh3/BvhClient.js";
import { AABB3 } from "../../../../core/geom/3d/aabb/AABB3.js";
import { aabb3_from_threejs_geometry } from "../../../../core/geom/3d/aabb/aabb3_from_threejs_geometry.js";
import { aabb3_matrix4_project } from "../../../../core/geom/3d/aabb/aabb3_matrix4_project.js";
import { ray3_array_apply_matrix4 } from "../../../../core/geom/3d/ray/ray3_array_apply_matrix4.js";
import { GeometrySpatialQueryAccelerator } from "../../geometry/buffered/query/GeometrySpatialQueryAccelerator.js";
import { DrawMode } from "./DrawMode.js";
import { ShadedGeometryFlags } from "./ShadedGeometryFlags.js";
/**
* @readonly
* @type {Float32Array}
*/
const scratch_aabb3_array = new Float32Array(6);
const DEFAULT_FLAGS = ShadedGeometryFlags.CastShadow
| ShadedGeometryFlags.ReceiveShadow
| ShadedGeometryFlags.Visible
;
const FLAG_SET_EQUALITY = ShadedGeometryFlags.CastShadow
| ShadedGeometryFlags.ReceiveShadow
| ShadedGeometryFlags.DrawMethodLocked
| ShadedGeometryFlags.Visible
;
/**
*
* @type {Float32Array|mat4}
*/
const scratch_m4 = new Float32Array(16);
/**
* @readonly
* @type {Float32Array}
*/
const scratch_ray_0 = new Float32Array(6);
/**
* Represents a primitive 3d object, a combination of a material and geometry
* This is roughly equivalent to single draw call, note that his is not a hierarchical data structure, if you want that - you will need to combine multiple entities, each with a `ShadedGeometry` component
*/
export class ShadedGeometry {
constructor() {
/**
*
* @type {THREE.BufferGeometry|null}
*/
this.geometry = null;
/**
*
* @type {THREE.Material|null}
*/
this.material = null;
/**
* Optional extra material used for shadowmaps
* @type {THREE.Material|null}
*/
this.depth_material = null;
this.__bvh_leaf = new BvhClient();
/**
* Transient, assigned in the system
* @type {number}
* @private
*/
this.__entity = -1;
/**
* Transient, cached for convenience. Associated transform component from the same entity
* @type {Transform|null}
* @private
*/
this.__c_transform = null;
/**
*
* @type {DrawMode|number}
*/
this.mode = DrawMode.Triangles;
/**
* Transient property signifying how to render object, such as using INSTANCED draw
* @type {number}
*/
this.draw_method = 0;
/**
*
* @type {number}
*/
this.flags = DEFAULT_FLAGS;
}
/**
*
* @param {number|ShadedGeometryFlags} flag
* @returns {void}
*/
setFlag(flag) {
this.flags |= flag;
}
/**
*
* @param {number|ShadedGeometryFlags} flag
* @returns {void}
*/
clearFlag(flag) {
this.flags &= ~flag;
}
/**
*
* @param {number|ShadedGeometryFlags} flag
* @param {boolean} value
*/
writeFlag(flag, value) {
if (value) {
this.setFlag(flag);
} else {
this.clearFlag(flag);
}
}
/**
*
* @param {number|ShadedGeometryFlags} flag
* @returns {boolean}
*/
getFlag(flag) {
return (this.flags & flag) === flag;
}
/**
*
* @return {number}
*/
hash() {
return this.geometry.id
^ this.material.id
^ (this.flags & FLAG_SET_EQUALITY)
^ (this.draw_method << 16)
^ (this.mode << 12)
;
}
/**
*
* @param {ShadedGeometry} other
* @returns {boolean}
*/
equals(other) {
return this.geometry.id === other.geometry.id
&& this.material.id === other.material.id
&& this.draw_method === other.draw_method
&& this.mode === other.mode
&& (this.flags & FLAG_SET_EQUALITY) === (other.flags & FLAG_SET_EQUALITY)
&& this.depth_material === other.depth_material
;
}
/**
*
* @param {ShadedGeometry} other
*/
copy(other) {
this.geometry = other.geometry;
this.material = other.material;
this.depth_material = other.depth_material;
this.draw_method = other.draw_method;
this.mode = other.mode;
this.flags = other.flags;
}
/**
*
* @returns {ShadedGeometry}
*/
clone() {
const r = new ShadedGeometry();
r.copy(this);
return r;
}
/**
* Current cached entity that this component is attached to
* @return {number}
*/
get entity() {
return this.__entity;
}
/**
* Global transform matrix
* 4x4 matrix
* @return {null|number[]}
*/
get transform() {
return this.__c_transform.matrix;
}
/**
*
* @param {AABB3} destination
*/
getBoundingBox(destination) {
const aabb = this.__bvh_leaf.bounds;
destination.readFromArray(aabb);
}
/**
*
* @param {THREE.BufferGeometry} geometry
* @param {THREE.Material} material
* @param {DrawMode} [draw_mode]
*/
from(geometry, material, draw_mode = DrawMode.Triangles) {
this.geometry = geometry;
this.material = material;
this.mode = draw_mode;
// ensure that geometry has a bounding box
if (geometry.boundingBox === null) {
geometry.computeBoundingBox();
}
}
/**
*
* @param {THREE.BufferGeometry} geometry
* @param {THREE.Material} material
* @param {DrawMode} [draw_mode]
* @return {ShadedGeometry}
*/
static from(geometry, material, draw_mode) {
const r = new ShadedGeometry();
r.from(geometry, material, draw_mode);
return r;
}
updateTransform() {
this.update_bounds();
this.__bvh_leaf.write_bounds();
}
update_bounds() {
// update bounds
aabb3_from_threejs_geometry(scratch_aabb3_array, this.geometry);
// transform
aabb3_matrix4_project(this.__bvh_leaf.bounds, scratch_aabb3_array, this.transform);
}
/**
*
* @param {SurfacePoint3} contact if found, contact is written here
* @param {ArrayLike<number>|number[]|Float32Array} ray 6-tuple: [origin_x, origin_y, origin_z, direction_x, direction_y, direction_z]
* @param {ArrayLike<number>|number[]|Float32Array} transform_matrix4 transform applied to the geometry
* @returns {boolean}
*/
query_raycast_nearest(contact, ray, transform_matrix4) {
// get transform in local space
mat4.invert(scratch_m4, transform_matrix4);
// transform ray to local space
if (!ray3_array_apply_matrix4(scratch_ray_0, 0, ray, 0, scratch_m4)) {
// invalid transform matrix
return false;
}
const hit_found = GeometrySpatialQueryAccelerator.INSTANCE.queryRaycastNearest_array(
contact,
this.geometry,
scratch_ray_0
);
if (!hit_found) {
return false;
}
// transform hit contact into global space
contact.applyMatrix4(transform_matrix4);
return true;
}
}
/**
* @readonly
* @type {string}
*/
ShadedGeometry.typeName = 'ShadedGeometry';
/**
* TODO add serialization support
* @readonly
* @type {boolean}
*/
ShadedGeometry.serializable = false;