UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

289 lines (227 loc) • 8.54 kB
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();