@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
289 lines (227 loc) • 8.54 kB
JavaScript
import { assert } from "../../../../../core/assert.js";
import { BinaryUint32BVH } from "../../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
import { Cache } from "../../../../../core/cache/Cache.js";
import { read_three_planes_to_array } from "../../../../../core/geom/3d/frustum/read_three_planes_to_array.js";
import { bvh32_from_indexed_geometry } from "../../bvh/buffered/bvh32_from_indexed_geometry.js";
import { bvh32_from_unindexed_geometry } from "../../bvh/buffered/bvh32_from_unindexed_geometry.js";
import { buffer_attribute_deinterleave } from "../buffer_attribute_deinterleave.js";
import { buffer_attribute_denormalize } from "../buffer_attribute_denormalize.js";
import { bvh32_geometry_overlap_clipping_volume } from "./bvh32_geometry_overlap_clipping_volume.js";
import { bvh32_geometry_raycast } from "./bvh32_geometry_raycast.js";
/**
* 4-tuples
* @type {number[]}
*/
const scratch_planes = []
export class GeometrySpatialQueryAccelerator {
constructor(
cache_size = 100 * 1024 * 1024
) {
assert.isNonNegativeInteger(cache_size,'cache_size');
assert.greaterThanOrEqual(cache_size, 0, 'cache_size must be greater than or equal to 0');
/**
* @private
* @type {Cache<THREE.BufferGeometry, BinaryUint32BVH>}
*/
this.cache = new Cache({
maxWeight: cache_size,
keyHashFunction(geometry) {
return geometry.id;
},
keyEqualityFunction(a, b) {
return a.id === b.id && a.uuid === b.uuid;
},
/**
*
* @param {BinaryUint32BVH} tree
*/
valueWeigher(tree) {
return tree.estimateByteSize();
}
});
}
/**
*
* @param {number} value in bytes
*/
set cache_size(value) {
this.cache.maxWeight = value;
}
/**
*
* @return {number} in bytes
*/
get cache_size() {
return this.cache.maxWeight;
}
/**
*
* @param {THREE.BufferGeometry} geometry
* @param {Plane[]} planes
* @returns {boolean} true means some overlap exists, false otherwise
*/
queryContainmentViaClippingPlanes(geometry, planes) {
assert.notNull(geometry, 'geometry');
assert.defined(geometry, 'geometry');
const bvh = this.acquire_bvh(geometry);
read_three_planes_to_array(planes, scratch_planes);
const geometryIndices = geometry.getIndex()?.array;
const position_attribute = geometry.getAttribute('position');
let position_data;
let stride = 3;
let position_data_offset = 0;
if (position_attribute.isInterleavedBufferAttribute) {
position_data = position_attribute.data.array;
stride = position_attribute.data.stride;
position_data_offset = position_attribute.offset;
} else {
position_data = position_attribute.array;
stride = 3;
position_data_offset = 0;
}
return bvh32_geometry_overlap_clipping_volume(
bvh,
position_data, position_data_offset, stride, position_attribute.normalized,
geometryIndices,
scratch_planes, 0, planes.length
);
}
/**
*
* @param {SurfacePoint3} destination
* @param {THREE.BufferGeometry} geometry
* @param {number[]|ArrayLike<number>|Float32Array} ray 6 component vector, [origin_x, origin_y, origin_z, direction_x, direction_y, direction_z]
* @return {boolean}
*/
queryRaycastNearest_array(destination, geometry, ray) {
return this.queryRaycastNearest_primitive(
destination,
geometry,
ray[0], ray[1], ray[2],
ray[3], ray[4], ray[5]
);
}
/**
*
* @param {SurfacePoint3} destination
* @param {THREE.BufferGeometry} geometry
* @param {number} ray_origin_x
* @param {number} ray_origin_y
* @param {number} ray_origin_z
* @param {number} ray_direction_x
* @param {number} ray_direction_y
* @param {number} ray_direction_z
* @return {boolean}
*/
queryRaycastNearest_primitive(
destination,
geometry,
ray_origin_x, ray_origin_y, ray_origin_z,
ray_direction_x, ray_direction_y, ray_direction_z
) {
assert.notNull(geometry, 'geometry');
assert.defined(geometry, 'geometry');
let bvh;
try {
bvh = this.acquire_bvh(geometry);
} catch (e) {
console.error(e);
return false;
}
const geometryIndices = geometry.getIndex()?.array;
const position_attribute = geometry.getAttribute('position');
let position_data;
let stride = 3;
let position_data_offset = 0;
if (position_attribute.isInterleavedBufferAttribute) {
position_data = position_attribute.data.array;
stride = position_attribute.data.stride;
position_data_offset = position_attribute.offset;
} else {
position_data = position_attribute.array;
stride = 3;
position_data_offset = 0;
}
return bvh32_geometry_raycast(
destination,
bvh,
position_data,
position_data_offset,
stride,
position_attribute.normalized,
geometryIndices,
ray_origin_x, ray_origin_y, ray_origin_z,
ray_direction_x, ray_direction_y, ray_direction_z,
Infinity
);
}
/**
* Get the nearest ray intersection
* @param {SurfacePoint3} destination
* @param {THREE.BufferGeometry} geometry
* @param {Vector3} ray_origin
* @param {Vector3} ray_direction
* @returns {boolean} true if hit detected, false otherwise
*/
queryRaycastNearest(destination, geometry, ray_origin, ray_direction) {
return this.queryRaycastNearest_primitive(
destination, geometry,
ray_origin.x,
ray_origin.y,
ray_origin.z,
ray_direction.x,
ray_direction.y,
ray_direction.z
);
}
/**
* Destroys cache for a given geometry, useful for when geometry changes and cache needs to be invalidated
* @param {THREE.BufferGeometry} geometry
* @returns {boolean}
*/
cache_remove_geometry(geometry) {
return this.cache.remove(geometry);
}
/**
* NOTE: do not modify the BVH
* @param {THREE.BufferGeometry} geometry
* @returns {BinaryUint32BVH}
*/
acquire_bvh(geometry) {
assert.notNull(geometry, 'geometry');
assert.defined(geometry, 'geometry');
const existing = this.cache.get(geometry);
if (existing !== null) {
return existing;
}
const bvh = this.__build_bvh(geometry);
this.cache.put(geometry, bvh);
return bvh;
}
/**
* @private
* @param {THREE.BufferGeometry} geometry
* @returns {BinaryUint32BVH}
*/
__build_bvh(geometry) {
const bvh = new BinaryUint32BVH();
const position_attribute = geometry.getAttribute('position');
const index_attribute = geometry.getIndex();
const de_interleaved_position_attribute = buffer_attribute_deinterleave(position_attribute);
const de_normalized_position_attribute = buffer_attribute_denormalize(de_interleaved_position_attribute);
const vertices = de_normalized_position_attribute.array;
if (index_attribute === undefined || index_attribute === null) {
bvh32_from_unindexed_geometry(bvh, vertices);
} else {
const de_interleaved_index_attribute = buffer_attribute_deinterleave(index_attribute);
const indices = de_interleaved_index_attribute.array;
bvh32_from_indexed_geometry(bvh, vertices, indices);
}
return bvh;
}
}
/**
* @readonly
* @type {GeometrySpatialQueryAccelerator}
*/
GeometrySpatialQueryAccelerator.INSTANCE = new GeometrySpatialQueryAccelerator();