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