UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

239 lines (185 loc) • 6.74 kB
import { Transform } from "../../../../engine/ecs/transform/Transform.js"; import { assert } from "../../../assert.js"; import { isArrayEqualStrict } from "../../../collection/array/isArrayEqualStrict.js"; import { computeHashFloatArray } from "../../../math/hash/computeHashFloatArray.js"; import { sign } from "../../../math/sign.js"; import { v3_length } from "../../vec3/v3_length.js"; import { v3_matrix4_multiply } from "../../vec3/v3_matrix4_multiply.js"; import { aabb3_matrix4_project } from "../aabb/aabb3_matrix4_project.js"; import { m4_extract_scale } from "../mat4/m4_extract_scale.js"; import { m4_invert } from "../mat4/m4_invert.js"; import { AbstractShape3D } from "./AbstractShape3D.js"; /** * * @type {number[]|Float32Array|vec3} */ const scratch_v3_0 = new Float32Array(3); export class TransformedShape3D extends AbstractShape3D { constructor() { super(); /** * * @type {number[]|Float32Array|mat4} * @private */ this.__matrix = new Float32Array(16); /** * * @type {Float32Array|mat4} * @private */ this.__inverse_matrix = new Float32Array(16); /** * * @type {AbstractShape3D|null} * @private */ this.__subject = null; } /** * * @returns {AbstractShape3D|null} */ get subject() { return this.__subject; } /** * * @param {number[]|Float32Array|mat4} m4 * @param {AbstractShape3D} subject * @returns {TransformedShape3D} */ static from_m4(subject, m4) { const r = new TransformedShape3D(); r.__subject = subject; r.transform = m4; return r; } /** * * @param {AbstractShape3D} subject * @param {number[]} translation * @param {number[]} scale */ static from_translation_scale(subject, translation, scale) { assert.isArrayLike(translation, 'translation'); assert.greaterThanOrEqual(translation.length, 3, 'translation length must be >= 3'); assert.isArrayLike(scale, 'scale'); assert.greaterThanOrEqual(scale.length, 3, 'scale length must be >= 3'); const t = new Transform(); t.scale.fromArray(scale); t.position.fromArray(translation); return TransformedShape3D.from_m4(subject, t.matrix); } /** * * @param {AbstractShape3D} subject * @param {number[]} translation */ static from_translation(subject, translation) { return TransformedShape3D.from_translation_scale(subject, translation, [1, 1, 1]); } /** * * @returns {TransformedShape3D} * @param {AbstractShape3D} subject * @param {number[]|Float32Array|vec3} scale */ static from_scale(subject, scale) { const t = new Transform(); t.scale.fromArray(scale); return TransformedShape3D.from_m4(subject, t.matrix); } /** * * @param {number[]|Float32Array} m */ set transform(m) { this.__matrix.set(m); m4_invert(this.__inverse_matrix, this.__matrix); } get transform() { return this.__matrix; } get volume() { m4_extract_scale(scratch_v3_0, 0, this.__matrix); const scale_modifier = scratch_v3_0[0] * scratch_v3_0[1] * scratch_v3_0[2]; return this.__subject.volume * scale_modifier; } compute_bounding_box(result) { const tmp = []; this.__subject.compute_bounding_box(tmp); aabb3_matrix4_project(result, tmp, this.__matrix); } signed_distance_gradient_at_point(result, point) { // transform point to subject's local space v3_matrix4_multiply(scratch_v3_0, 0, point, 0, this.__inverse_matrix); const subject = this.__subject; // sample gradient at the point const distance = subject.signed_distance_gradient_at_point(scratch_v3_0, scratch_v3_0); const m = this.__matrix; const m11 = m[0]; const m12 = m[1]; const m13 = m[2]; const m21 = m[4]; const m22 = m[5]; const m23 = m[6]; const m31 = m[8]; const m32 = m[9]; const m33 = m[10]; const scale_x = v3_length(m11, m12, m13); const scale_y = v3_length(m21, m22, m23); const scale_z = v3_length(m31, m32, m33); const scaled_gx = scratch_v3_0[0] * scale_x; const scaled_gy = scratch_v3_0[1] * scale_y; const scaled_gz = scratch_v3_0[2] * scale_z; // normalize gradient const gd = distance / v3_length(scratch_v3_0[0], scratch_v3_0[1], scratch_v3_0[2]); const scaled_dx = scaled_gx * gd; const scaled_dy = scaled_gy * gd; const scaled_dz = scaled_gz * gd; // determine sign const result_sign = sign(distance); const scaled_distance = result_sign * Math.sqrt(scaled_dx * scaled_dx + scaled_dy * scaled_dy + scaled_dz * scaled_dz); result[0] = scaled_gx; result[1] = scaled_gy; result[2] = scaled_gz; return scaled_distance; } signed_distance_at_point(point) { return this.signed_distance_gradient_at_point(scratch_v3_0, point); } contains_point(point) { // transform point to subject's local space v3_matrix4_multiply(scratch_v3_0, 0, point, 0, this.__inverse_matrix); return this.__subject.contains_point(scratch_v3_0); } sample_random_point_in_volume(result, result_offset, random) { this.__subject.sample_random_point_in_volume(scratch_v3_0, 0, random); v3_matrix4_multiply(result, result_offset, scratch_v3_0, 0, this.__matrix); } support(result, result_offset, direction_x, direction_y, direction_z) { this.__subject.support(scratch_v3_0, 0, direction_x, direction_y, direction_z); v3_matrix4_multiply(result, result_offset, scratch_v3_0, 0, this.__matrix); } /** * * @param {TransformedShape3D} other * @returns {boolean} */ equals(other) { return super.equals(other) && this.__subject.equals(other.subject) && isArrayEqualStrict(this.__matrix, other.__matrix) ; } hash() { // to avoid recursion, only use matrix for hash return computeHashFloatArray(this.__matrix); } } /** * @readonly * @type {boolean} */ TransformedShape3D.isTransformedShape3D = true;