Initial commit
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const V3 = (x = 0, y = 0, z = 0) => new THREE.Vector3(x, y, z);
|
||||
export const UP = V3(0, 1, 0);
|
||||
export const FWD = V3(0, 0, 1);
|
||||
|
||||
export const clamp = (x, a, b) => (x < a ? a : x > b ? b : x);
|
||||
export const lerp = (a, b, t) => a + (b - a) * t;
|
||||
export const smooth = (t) => t * t * (3 - 2 * t);
|
||||
|
||||
export function assert(cond, msg) {
|
||||
if (!cond) throw new Error('ASSERT FAILED: ' + msg);
|
||||
}
|
||||
|
||||
export function lerpAngle(a, b, t) {
|
||||
let d = b - a;
|
||||
while (d > Math.PI) d -= Math.PI * 2;
|
||||
while (d < -Math.PI) d += Math.PI * 2;
|
||||
return a + d * t;
|
||||
}
|
||||
|
||||
const _sd1 = new THREE.Vector3();
|
||||
const _sd2 = new THREE.Vector3();
|
||||
|
||||
/** Distance from point `p` to segment a-b; writes the closest point into `out`. */
|
||||
export function segDist(p, a, b, out) {
|
||||
_sd1.subVectors(b, a);
|
||||
_sd2.subVectors(p, a);
|
||||
const t = clamp(_sd2.dot(_sd1) / Math.max(1e-9, _sd1.lengthSq()), 0, 1);
|
||||
out.copy(a).addScaledVector(_sd1, t);
|
||||
return p.distanceTo(out);
|
||||
}
|
||||
|
||||
const _u = new THREE.Vector3();
|
||||
const _v = new THREE.Vector3();
|
||||
const _w = new THREE.Vector3();
|
||||
|
||||
/**
|
||||
* Closest distance between two segments, writing the closest point on each
|
||||
* into `outA` / `outB`.
|
||||
*
|
||||
* Used to work out which limb hit which limb: both ragdolls are 18 capsules,
|
||||
* and a capsule is a segment plus a radius, so the nearest pair of segments is
|
||||
* the nearest pair of body parts. Standard Ericson clamped-parameter solve —
|
||||
* the degenerate cases (either segment a point, or the two parallel) all fall
|
||||
* out of the denominator guards rather than needing separate branches.
|
||||
*/
|
||||
export function segSegDistance(p1, q1, p2, q2, outA, outB) {
|
||||
_u.subVectors(q1, p1);
|
||||
_v.subVectors(q2, p2);
|
||||
_w.subVectors(p1, p2);
|
||||
const a = _u.dot(_u);
|
||||
const b = _u.dot(_v);
|
||||
const c = _v.dot(_v);
|
||||
const d = _u.dot(_w);
|
||||
const e = _v.dot(_w);
|
||||
const D = a * c - b * b;
|
||||
let sN;
|
||||
let sD = D;
|
||||
let tN;
|
||||
let tD = D;
|
||||
|
||||
if (D < 1e-9) {
|
||||
// Parallel or degenerate: pin the first parameter and solve the second.
|
||||
sN = 0;
|
||||
sD = 1;
|
||||
tN = e;
|
||||
tD = c;
|
||||
} else {
|
||||
sN = b * e - c * d;
|
||||
tN = a * e - b * d;
|
||||
if (sN < 0) {
|
||||
sN = 0;
|
||||
tN = e;
|
||||
tD = c;
|
||||
} else if (sN > sD) {
|
||||
sN = sD;
|
||||
tN = e + b;
|
||||
tD = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (tN < 0) {
|
||||
tN = 0;
|
||||
if (-d < 0) sN = 0;
|
||||
else if (-d > a) sN = sD;
|
||||
else {
|
||||
sN = -d;
|
||||
sD = a;
|
||||
}
|
||||
} else if (tN > tD) {
|
||||
tN = tD;
|
||||
if (-d + b < 0) sN = 0;
|
||||
else if (-d + b > a) sN = sD;
|
||||
else {
|
||||
sN = -d + b;
|
||||
sD = a;
|
||||
}
|
||||
}
|
||||
|
||||
const s = Math.abs(sD) < 1e-9 ? 0 : sN / sD;
|
||||
const t = Math.abs(tD) < 1e-9 ? 0 : tN / tD;
|
||||
outA.copy(p1).addScaledVector(_u, s);
|
||||
outB.copy(p2).addScaledVector(_v, t);
|
||||
return outA.distanceTo(outB);
|
||||
}
|
||||
|
||||
const _euler = new THREE.Euler();
|
||||
/** Write XYZ euler angles into an existing quaternion without allocating. */
|
||||
export function E(out, x, y, z, order) {
|
||||
_euler.set(x, y, z, order || 'XYZ');
|
||||
return out.setFromEuler(_euler);
|
||||
}
|
||||
export { _euler };
|
||||
|
||||
/** Merge indexed BufferGeometries that share an attribute set. */
|
||||
export function mergeGeoms(list) {
|
||||
let vTotal = 0;
|
||||
let iTotal = 0;
|
||||
const attrNames = Object.keys(list[0].attributes);
|
||||
for (const g of list) {
|
||||
vTotal += g.attributes.position.count;
|
||||
iTotal += g.index.count;
|
||||
}
|
||||
const out = new THREE.BufferGeometry();
|
||||
const arrays = {};
|
||||
for (const name of attrNames) {
|
||||
const itemSize = list[0].attributes[name].itemSize;
|
||||
const Ctor = list[0].attributes[name].array.constructor;
|
||||
arrays[name] = new Ctor(vTotal * itemSize);
|
||||
}
|
||||
const index = new (vTotal > 65535 ? Uint32Array : Uint16Array)(iTotal);
|
||||
let vOff = 0;
|
||||
let iOff = 0;
|
||||
for (const g of list) {
|
||||
const n = g.attributes.position.count;
|
||||
for (const name of attrNames) {
|
||||
arrays[name].set(g.attributes[name].array, vOff * g.attributes[name].itemSize);
|
||||
}
|
||||
const gi = g.index.array;
|
||||
for (let i = 0; i < gi.length; i++) index[iOff + i] = gi[i] + vOff;
|
||||
vOff += n;
|
||||
iOff += gi.length;
|
||||
}
|
||||
for (const name of attrNames) {
|
||||
out.setAttribute(name, new THREE.BufferAttribute(arrays[name], list[0].attributes[name].itemSize));
|
||||
}
|
||||
out.setIndex(new THREE.BufferAttribute(index, 1));
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Normalize an arbitrary geometry to position/normal/uv + index so it can merge. */
|
||||
export function stripAttrs(g) {
|
||||
const out = new THREE.BufferGeometry();
|
||||
out.setAttribute('position', g.attributes.position);
|
||||
out.setAttribute('normal', g.attributes.normal);
|
||||
const n = g.attributes.position.count;
|
||||
out.setAttribute('uv', g.attributes.uv || new THREE.Float32BufferAttribute(new Float32Array(n * 2), 2));
|
||||
if (g.index) out.setIndex(g.index);
|
||||
else {
|
||||
const idx = [];
|
||||
for (let i = 0; i < n; i++) idx.push(i);
|
||||
out.setIndex(idx);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function disposeObject(root) {
|
||||
root.traverse((o) => {
|
||||
if (o.geometry) o.geometry.dispose();
|
||||
if (o.material) {
|
||||
const mats = Array.isArray(o.material) ? o.material : [o.material];
|
||||
for (const m of mats) {
|
||||
for (const k of Object.keys(m)) if (m[k] && m[k].isTexture) m[k].dispose();
|
||||
m.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Seeded PRNG. One integer seed drives every generated detail of a fighter.
|
||||
//
|
||||
// The showcase this grew out of used a module-level generator, which is fine
|
||||
// for one character on screen. A match has at least two, and they have to be
|
||||
// independently reproducible from their own seeds, so the generator is an
|
||||
// object that gets threaded through the builders instead.
|
||||
|
||||
export function makeRng(seed) {
|
||||
let a = seed | 0;
|
||||
const f = () => {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
return {
|
||||
seed,
|
||||
f,
|
||||
range: (lo, hi) => lo + (hi - lo) * f(),
|
||||
int: (lo, hi) => Math.floor(lo + (hi + 0.9999 - lo) * f()),
|
||||
pick: (arr) => arr[Math.floor(f() * arr.length) % arr.length],
|
||||
// Independent sub-stream, so adding a generator in one place doesn't shift
|
||||
// every value drawn after it.
|
||||
fork: (salt) => makeRng((Math.imul(seed ^ salt, 0x9e3779b1) ^ (seed >>> 3)) | 0),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user