@origingame/engine 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/og.mjs +1 -1
- package/dist/web/origin-engine.d.ts +255 -5
- package/dist/web/origin-engine.js +1584 -125
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +80 -3
- package/dist/web-publish/origin-engine-lanes.js +1288 -82
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +199 -6
- package/dist/web-publish/origin-engine.js +1285 -79
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +65 -4
- package/dist/web-publish-2d/origin-engine-lanes.js +1285 -79
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +198 -5
- package/dist/web-publish-2d/origin-engine.js +1282 -76
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +64 -3
- package/knowledge/README.md +2 -1
- package/knowledge/guides/three-to-og.md +393 -0
- package/knowledge/knowledge.json +2 -1
- package/knowledge/looks/catalogue.json +40 -40
- package/knowledge/package.json +3 -2
- package/knowledge/scaffold/distribution.json +14 -0
- package/knowledge/scaffold/origin-engine.json +2 -2
- package/knowledge/scaffold/template/README.md +2 -1
- package/knowledge/scaffold/template/assets/audio/sting.ogt +10 -0
- package/knowledge/scaffold/template/assets/timelines/intro.ogt +101 -0
- package/knowledge/scaffold/template/assets/timelines/narration.srt +11 -0
- package/knowledge/scaffold/template/host.d.ts +46 -0
- package/knowledge/scaffold/template/package.json +1 -1
- package/knowledge/scaffold/template/project.ogt +1 -1
- package/knowledge/scaffold/template/src/main.ts +1 -1
- package/knowledge/scaffold/template/tests/frames.ts +134 -0
- package/knowledge/scaffold/template/tests/intro.og.ts +65 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +30 -36
- package/knowledge/scaffold/template/tsconfig.json +1 -1
- package/knowledge/scripts/mount.ts +2 -1
- package/knowledge/scripts/validate.ts +159 -31
- package/knowledge/skills/og-audio/SKILL.md +2 -2
- package/knowledge/skills/og-build/SKILL.md +29 -7
- package/knowledge/skills/og-core/SKILL.md +31 -5
- package/knowledge/skills/og-feel/SKILL.md +2 -2
- package/knowledge/skills/og-game/SKILL.md +1331 -16
- package/knowledge/skills/og-i18n/SKILL.md +7 -7
- package/knowledge/skills/og-model/SKILL.md +374 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +2 -1
- package/knowledge/skills/og-taste/SKILL.md +82 -73
- package/knowledge/skills/og-ui/SKILL.md +80 -58
- package/knowledge/wiki/ai.md +20 -20
- package/knowledge/wiki/anim.md +392 -41
- package/knowledge/wiki/atmosphere.md +36 -13
- package/knowledge/wiki/audio.md +18 -18
- package/knowledge/wiki/camera.md +19 -13
- package/knowledge/wiki/commands.md +19 -13
- package/knowledge/wiki/debug.md +414 -0
- package/knowledge/wiki/game.md +8 -8
- package/knowledge/wiki/i18n.md +7 -7
- package/knowledge/wiki/index.md +317 -8
- package/knowledge/wiki/input.md +47 -47
- package/knowledge/wiki/load.md +238 -70
- package/knowledge/wiki/math.md +2607 -0
- package/knowledge/wiki/nav.md +34 -34
- package/knowledge/wiki/net.md +340 -0
- package/knowledge/wiki/path.md +402 -0
- package/knowledge/wiki/physics.md +95 -95
- package/knowledge/wiki/platform.md +463 -29
- package/knowledge/wiki/probe.md +681 -67
- package/knowledge/wiki/reflect.md +29 -17
- package/knowledge/wiki/render.md +4993 -1998
- package/knowledge/wiki/rng.md +14 -14
- package/knowledge/wiki/shapes.md +727 -0
- package/knowledge/wiki/stream.md +15 -15
- package/knowledge/wiki/system.md +8 -8
- package/knowledge/wiki/systems.md +2 -2
- package/knowledge/wiki/terrain.md +24 -20
- package/knowledge/wiki/tilemap.md +13 -13
- package/knowledge/wiki/time.md +4 -4
- package/knowledge/wiki/timeline.md +396 -0
- package/knowledge/wiki/transform.md +8 -8
- package/knowledge/wiki/tween.md +17 -17
- package/knowledge/wiki/ui.md +560 -654
- package/knowledge/wiki/video.md +157 -0
- package/knowledge/wiki/world.md +36 -36
- package/package.json +4 -3
- package/schema/api.schema.json +28151 -19640
- package/shell/page.ts +26 -2
- package/shell/platform.ts +51 -0
- package/shell/player.ts +15 -2
- package/shell/transport.ts +6 -0
- package/src/depth.ts +71 -0
- package/src/e6.ts +32 -1
- package/src/env.d.ts +5 -1
- package/src/generated/abi.ts +226 -4
- package/src/geom.ts +260 -0
- package/src/index.ts +32 -4
- package/src/light.ts +18 -0
- package/src/math.ts +622 -0
- package/src/node/index.ts +17 -0
- package/src/node/playtest-run.ts +115 -18
- package/src/node/polish.ts +13 -3
- package/src/node/render.ts +291 -0
- package/src/node/run.ts +86 -5
- package/src/p2.ts +19 -1
- package/src/platform-local.ts +2 -0
- package/src/playtest.ts +65 -4
- package/src/runtime.ts +100 -9
- package/src/targets.ts +28 -0
- package/src/trace.ts +169 -0
- package/src/ui-kit/huds.ts +34 -34
- package/src/ui-kit/menus.ts +12 -12
- package/src/ui-kit/settings.ts +11 -2
- package/src/ui-kit/store.ts +18 -18
- package/src/ui-kit/story.ts +6 -4
- package/src/ui-kit/util.ts +8 -8
- package/src/web/shell.ts +59 -1
- package/src/web/worker.ts +105 -4
- package/types/bindings.json +9368 -6619
- package/types/index.d.ts +16377 -11520
package/src/math.ts
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
// The math layer (IDL `math`: vec3, vec2, quat, mat4, box3, sphere, plane,
|
|
2
|
+
// frustum, ray, color): three.js's methods as pure functions over the plain
|
|
3
|
+
// structs the IDL declares. No classes: a Vector3 is `{ x, y, z }`, so the
|
|
4
|
+
// values game code passes are the ones columns and reflection already hold,
|
|
5
|
+
// and every function returns a new value without changing its arguments.
|
|
6
|
+
//
|
|
7
|
+
// Determinism: a system may store a result in world state, so every number
|
|
8
|
+
// here comes from + - * / and sqrt, which JavaScript engines round
|
|
9
|
+
// identically. Trigonometry and powers are written out below (sincos is the
|
|
10
|
+
// camera systems' one); Math.sin, Math.atan2 and Math.pow never appear.
|
|
11
|
+
|
|
12
|
+
import type * as Api from "@origingame/engine";
|
|
13
|
+
import { sincos } from "./systems/cameras.ts";
|
|
14
|
+
|
|
15
|
+
type V2 = Api.Vector2;
|
|
16
|
+
type V3 = Api.Vector3;
|
|
17
|
+
type Q = Api.Quaternion;
|
|
18
|
+
type M4 = Api.Matrix4;
|
|
19
|
+
type Box = Api.Box3;
|
|
20
|
+
type Sph = Api.Sphere;
|
|
21
|
+
type Pl = Api.Plane;
|
|
22
|
+
type Fr = Api.Frustum;
|
|
23
|
+
type R = Api.Ray;
|
|
24
|
+
type C = Api.Color;
|
|
25
|
+
|
|
26
|
+
const PI = 3.141592653589793;
|
|
27
|
+
|
|
28
|
+
// ── scalar helpers (deterministic) ─────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/** atan(x) for |x| <= tan(pi/8): its series to x^45 (below 1e-17 there). */
|
|
31
|
+
function atanSmall(x: number): number {
|
|
32
|
+
const x2 = x * x;
|
|
33
|
+
let s = 0;
|
|
34
|
+
for (let n = 22; n >= 0; n--) s = s * -x2 + 1 / (2 * n + 1);
|
|
35
|
+
return x * s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** atan(x) on [0, 1]: reduced by atan(x) = pi/4 + atan((x - 1) / (x + 1)). */
|
|
39
|
+
function atanUnit(x: number): number {
|
|
40
|
+
return x > 0.41421356237309503 ? PI / 4 + atanSmall((x - 1) / (x + 1)) : atanSmall(x);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** atan2(y, x) with basic arithmetic only. */
|
|
44
|
+
export function atan2(y: number, x: number): number {
|
|
45
|
+
if (x === 0 && y === 0) return 0;
|
|
46
|
+
const ax = Math.abs(x);
|
|
47
|
+
const ay = Math.abs(y);
|
|
48
|
+
const a = ay <= ax ? atanUnit(ay / ax) : PI / 2 - atanUnit(ax / ay);
|
|
49
|
+
const r = x < 0 ? PI - a : a;
|
|
50
|
+
return y < 0 ? -r : r;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The n-th root of x >= 0 by Newton's method from above (x^(1/n)). */
|
|
54
|
+
function root(x: number, n: number): number {
|
|
55
|
+
if (x <= 0) return 0;
|
|
56
|
+
let y = x > 1 ? x : 1;
|
|
57
|
+
for (let i = 0; i < 64; i++) {
|
|
58
|
+
let p = 1;
|
|
59
|
+
for (let k = 0; k < n - 1; k++) p *= y;
|
|
60
|
+
const next = ((n - 1) * y + x / p) / n;
|
|
61
|
+
if (next >= y) break;
|
|
62
|
+
y = next;
|
|
63
|
+
}
|
|
64
|
+
return y;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** sRGB to linear (three.js SRGBToLinear): c^2.4 as c^2 * (c^2)^(1/5). */
|
|
68
|
+
function srgbToLinear(c: number): number {
|
|
69
|
+
if (c < 0.04045) return c * 0.0773993808;
|
|
70
|
+
const b = c * 0.9478672986 + 0.0521327014;
|
|
71
|
+
const b2 = b * b;
|
|
72
|
+
return b2 * root(b2, 5);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Linear to sRGB (three.js LinearToSRGB): c^(1/2.4) as the 12th root of c^5. */
|
|
76
|
+
function linearToSrgb(c: number): number {
|
|
77
|
+
if (c < 0.0031308) return c * 12.92;
|
|
78
|
+
const c2 = c * c;
|
|
79
|
+
return 1.055 * root(c2 * c2 * c, 12) - 0.055;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);
|
|
83
|
+
|
|
84
|
+
// ── vec3 ───────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
const v3 = (x: number, y: number, z: number): V3 => ({ x, y, z });
|
|
87
|
+
|
|
88
|
+
export const vec3: typeof Api.vec3 = Object.freeze({
|
|
89
|
+
add: (a: V3, b: V3) => v3(a.x + b.x, a.y + b.y, a.z + b.z),
|
|
90
|
+
sub: (a: V3, b: V3) => v3(a.x - b.x, a.y - b.y, a.z - b.z),
|
|
91
|
+
scale: (v: V3, s: number) => v3(v.x * s, v.y * s, v.z * s),
|
|
92
|
+
dot: (a: V3, b: V3) => a.x * b.x + a.y * b.y + a.z * b.z,
|
|
93
|
+
cross: (a: V3, b: V3) => v3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x),
|
|
94
|
+
length: (v: V3) => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z),
|
|
95
|
+
normalize: (v: V3) => {
|
|
96
|
+
const l = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
|
97
|
+
// three.js divides by (length || 1): the zero vector stays zero.
|
|
98
|
+
const d = l || 1;
|
|
99
|
+
return v3(v.x / d, v.y / d, v.z / d);
|
|
100
|
+
},
|
|
101
|
+
lerp: (a: V3, b: V3, t: number) => v3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t),
|
|
102
|
+
distance: (a: V3, b: V3) => {
|
|
103
|
+
const dx = a.x - b.x;
|
|
104
|
+
const dy = a.y - b.y;
|
|
105
|
+
const dz = a.z - b.z;
|
|
106
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
107
|
+
},
|
|
108
|
+
applyQuaternion: (v: V3, q: Q) => rotateV(q, v),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/** v turned by q, as three.js Vector3.applyQuaternion (t = 2 q x v; v + w t + q x t). */
|
|
112
|
+
function rotateV(q: Q, v: V3): V3 {
|
|
113
|
+
const tx = 2 * (q.y * v.z - q.z * v.y);
|
|
114
|
+
const ty = 2 * (q.z * v.x - q.x * v.z);
|
|
115
|
+
const tz = 2 * (q.x * v.y - q.y * v.x);
|
|
116
|
+
return v3(
|
|
117
|
+
v.x + q.w * tx + q.y * tz - q.z * ty,
|
|
118
|
+
v.y + q.w * ty + q.z * tx - q.x * tz,
|
|
119
|
+
v.z + q.w * tz + q.x * ty - q.y * tx,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── vec2 ───────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
const v2 = (x: number, y: number): V2 => ({ x, y });
|
|
126
|
+
|
|
127
|
+
export const vec2: typeof Api.vec2 = Object.freeze({
|
|
128
|
+
add: (a: V2, b: V2) => v2(a.x + b.x, a.y + b.y),
|
|
129
|
+
sub: (a: V2, b: V2) => v2(a.x - b.x, a.y - b.y),
|
|
130
|
+
scale: (v: V2, s: number) => v2(v.x * s, v.y * s),
|
|
131
|
+
dot: (a: V2, b: V2) => a.x * b.x + a.y * b.y,
|
|
132
|
+
cross: (a: V2, b: V2) => a.x * b.y - a.y * b.x,
|
|
133
|
+
length: (v: V2) => Math.sqrt(v.x * v.x + v.y * v.y),
|
|
134
|
+
normalize: (v: V2) => {
|
|
135
|
+
const d = Math.sqrt(v.x * v.x + v.y * v.y) || 1;
|
|
136
|
+
return v2(v.x / d, v.y / d);
|
|
137
|
+
},
|
|
138
|
+
lerp: (a: V2, b: V2, t: number) => v2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t),
|
|
139
|
+
distance: (a: V2, b: V2) => {
|
|
140
|
+
const dx = a.x - b.x;
|
|
141
|
+
const dy = a.y - b.y;
|
|
142
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
143
|
+
},
|
|
144
|
+
rotateAround: (v: V2, center: V2, angle: number) => {
|
|
145
|
+
const [s, c] = sincos(angle);
|
|
146
|
+
const x = v.x - center.x;
|
|
147
|
+
const y = v.y - center.y;
|
|
148
|
+
return v2(x * c - y * s + center.x, x * s + y * c + center.y);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── quat ───────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
const q4 = (x: number, y: number, z: number, w: number): Q => ({ x, y, z, w });
|
|
155
|
+
|
|
156
|
+
function multiplyQ(a: Q, b: Q): Q {
|
|
157
|
+
return q4(
|
|
158
|
+
a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,
|
|
159
|
+
a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,
|
|
160
|
+
a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,
|
|
161
|
+
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const quat: typeof Api.quat = Object.freeze({
|
|
166
|
+
multiply: multiplyQ,
|
|
167
|
+
slerp: (a: Q, b: Q, t: number) => {
|
|
168
|
+
// three.js Quaternion.slerp, step for step.
|
|
169
|
+
if (t === 0) return q4(a.x, a.y, a.z, a.w);
|
|
170
|
+
if (t === 1) return q4(b.x, b.y, b.z, b.w);
|
|
171
|
+
let { x, y, z, w } = b;
|
|
172
|
+
let cosHalf = a.w * w + a.x * x + a.y * y + a.z * z;
|
|
173
|
+
if (cosHalf < 0) {
|
|
174
|
+
x = -x;
|
|
175
|
+
y = -y;
|
|
176
|
+
z = -z;
|
|
177
|
+
w = -w;
|
|
178
|
+
cosHalf = -cosHalf;
|
|
179
|
+
}
|
|
180
|
+
if (cosHalf >= 1) return q4(a.x, a.y, a.z, a.w);
|
|
181
|
+
const sqrSin = 1 - cosHalf * cosHalf;
|
|
182
|
+
if (sqrSin <= Number.EPSILON) {
|
|
183
|
+
const s = 1 - t;
|
|
184
|
+
const r = q4(s * a.x + t * x, s * a.y + t * y, s * a.z + t * z, s * a.w + t * w);
|
|
185
|
+
const l = Math.sqrt(r.x * r.x + r.y * r.y + r.z * r.z + r.w * r.w) || 1;
|
|
186
|
+
return q4(r.x / l, r.y / l, r.z / l, r.w / l);
|
|
187
|
+
}
|
|
188
|
+
const sinHalf = Math.sqrt(sqrSin);
|
|
189
|
+
const half = atan2(sinHalf, cosHalf);
|
|
190
|
+
const ra = sincos((1 - t) * half)[0] / sinHalf;
|
|
191
|
+
const rb = sincos(t * half)[0] / sinHalf;
|
|
192
|
+
return q4(a.x * ra + x * rb, a.y * ra + y * rb, a.z * ra + z * rb, a.w * ra + w * rb);
|
|
193
|
+
},
|
|
194
|
+
fromEuler: (angles: V3, order: string = "XYZ") => {
|
|
195
|
+
// three.js Quaternion.setFromEuler.
|
|
196
|
+
const [s1, c1] = sincos(angles.x / 2);
|
|
197
|
+
const [s2, c2] = sincos(angles.y / 2);
|
|
198
|
+
const [s3, c3] = sincos(angles.z / 2);
|
|
199
|
+
switch (order) {
|
|
200
|
+
case "XYZ":
|
|
201
|
+
return q4(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3);
|
|
202
|
+
case "YXZ":
|
|
203
|
+
return q4(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3);
|
|
204
|
+
case "ZXY":
|
|
205
|
+
return q4(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3);
|
|
206
|
+
case "ZYX":
|
|
207
|
+
return q4(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3);
|
|
208
|
+
case "YZX":
|
|
209
|
+
return q4(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3);
|
|
210
|
+
case "XZY":
|
|
211
|
+
return q4(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3);
|
|
212
|
+
default:
|
|
213
|
+
throw new Error(`quat.fromEuler: order "${order}" is not XYZ, YXZ, ZXY, ZYX, YZX or XZY`);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
invert: (q: Q) => q4(-q.x, -q.y, -q.z, q.w),
|
|
217
|
+
rotate: (q: Q, v: V3) => rotateV(q, v),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ── mat4 ───────────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
type E = Api.Matrix4["elements"];
|
|
223
|
+
const m4 = (e: number[]): M4 => ({ elements: e as unknown as E });
|
|
224
|
+
|
|
225
|
+
function multiplyM(a: M4, b: M4): M4 {
|
|
226
|
+
const ae = a.elements;
|
|
227
|
+
const be = b.elements;
|
|
228
|
+
const out = new Array<number>(16);
|
|
229
|
+
for (let c = 0; c < 4; c++) {
|
|
230
|
+
for (let r = 0; r < 4; r++) {
|
|
231
|
+
out[c * 4 + r] =
|
|
232
|
+
ae[r]! * be[c * 4]! + ae[4 + r]! * be[c * 4 + 1]! + ae[8 + r]! * be[c * 4 + 2]! + ae[12 + r]! * be[c * 4 + 3]!;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return m4(out);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function invertM(m: M4): M4 {
|
|
239
|
+
// three.js Matrix4.invert (cofactors), a singular matrix to zeros.
|
|
240
|
+
const te = m.elements;
|
|
241
|
+
const [n11, n21, n31, n41, n12, n22, n32, n42, n13, n23, n33, n43, n14, n24, n34, n44] = te as unknown as number[] as [
|
|
242
|
+
number, number, number, number, number, number, number, number,
|
|
243
|
+
number, number, number, number, number, number, number, number,
|
|
244
|
+
];
|
|
245
|
+
const t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44;
|
|
246
|
+
const t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44;
|
|
247
|
+
const t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44;
|
|
248
|
+
const t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
|
|
249
|
+
const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
|
|
250
|
+
if (det === 0) return m4(new Array<number>(16).fill(0));
|
|
251
|
+
const d = 1 / det;
|
|
252
|
+
return m4([
|
|
253
|
+
t11 * d,
|
|
254
|
+
(n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * d,
|
|
255
|
+
(n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * d,
|
|
256
|
+
(n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * d,
|
|
257
|
+
t12 * d,
|
|
258
|
+
(n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * d,
|
|
259
|
+
(n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * d,
|
|
260
|
+
(n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * d,
|
|
261
|
+
t13 * d,
|
|
262
|
+
(n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * d,
|
|
263
|
+
(n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * d,
|
|
264
|
+
(n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * d,
|
|
265
|
+
t14 * d,
|
|
266
|
+
(n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * d,
|
|
267
|
+
(n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * d,
|
|
268
|
+
(n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * d,
|
|
269
|
+
]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function transformPointM(m: M4, p: V3): V3 {
|
|
273
|
+
const e = m.elements;
|
|
274
|
+
const w = 1 / (e[3]! * p.x + e[7]! * p.y + e[11]! * p.z + e[15]!);
|
|
275
|
+
return v3(
|
|
276
|
+
(e[0]! * p.x + e[4]! * p.y + e[8]! * p.z + e[12]!) * w,
|
|
277
|
+
(e[1]! * p.x + e[5]! * p.y + e[9]! * p.z + e[13]!) * w,
|
|
278
|
+
(e[2]! * p.x + e[6]! * p.y + e[10]! * p.z + e[14]!) * w,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function composeM(p: V3, q: Q, s: V3): M4 {
|
|
283
|
+
// three.js Matrix4.compose.
|
|
284
|
+
const { x, y, z, w } = q;
|
|
285
|
+
const x2 = x + x, y2 = y + y, z2 = z + z;
|
|
286
|
+
const xx = x * x2, xy = x * y2, xz = x * z2;
|
|
287
|
+
const yy = y * y2, yz = y * z2, zz = z * z2;
|
|
288
|
+
const wx = w * x2, wy = w * y2, wz = w * z2;
|
|
289
|
+
return m4([
|
|
290
|
+
(1 - (yy + zz)) * s.x, (xy + wz) * s.x, (xz - wy) * s.x, 0,
|
|
291
|
+
(xy - wz) * s.y, (1 - (xx + zz)) * s.y, (yz + wx) * s.y, 0,
|
|
292
|
+
(xz + wy) * s.z, (yz - wx) * s.z, (1 - (xx + yy)) * s.z, 0,
|
|
293
|
+
p.x, p.y, p.z, 1,
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export const mat4: typeof Api.mat4 = Object.freeze({
|
|
298
|
+
multiply: multiplyM,
|
|
299
|
+
invert: invertM,
|
|
300
|
+
transformPoint: transformPointM,
|
|
301
|
+
transformDirection: (m: M4, d: V3) => {
|
|
302
|
+
const e = m.elements;
|
|
303
|
+
return vec3.normalize(
|
|
304
|
+
v3(e[0]! * d.x + e[4]! * d.y + e[8]! * d.z, e[1]! * d.x + e[5]! * d.y + e[9]! * d.z, e[2]! * d.x + e[6]! * d.y + e[10]! * d.z),
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
compose: composeM,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ── box3 ───────────────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
const box = (min: V3, max: V3): Box => ({ min, max });
|
|
313
|
+
const emptyBox = (): Box => box(v3(Infinity, Infinity, Infinity), v3(-Infinity, -Infinity, -Infinity));
|
|
314
|
+
const isEmptyBox = (b: Box) => b.max.x < b.min.x || b.max.y < b.min.y || b.max.z < b.min.z;
|
|
315
|
+
|
|
316
|
+
function expand(b: Box, p: V3): Box {
|
|
317
|
+
return box(
|
|
318
|
+
v3(Math.min(b.min.x, p.x), Math.min(b.min.y, p.y), Math.min(b.min.z, p.z)),
|
|
319
|
+
v3(Math.max(b.max.x, p.x), Math.max(b.max.y, p.y), Math.max(b.max.z, p.z)),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function boxFromPoints(points: readonly V3[]): Box {
|
|
324
|
+
let b = emptyBox();
|
|
325
|
+
for (const p of points) b = expand(b, p);
|
|
326
|
+
return b;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const clampToBox = (b: Box, p: V3) =>
|
|
330
|
+
v3(clamp(p.x, b.min.x, b.max.x), clamp(p.y, b.min.y, b.max.y), clamp(p.z, b.min.z, b.max.z));
|
|
331
|
+
|
|
332
|
+
function boxCenter(b: Box): V3 {
|
|
333
|
+
return isEmptyBox(b) ? v3(0, 0, 0) : v3((b.min.x + b.max.x) / 2, (b.min.y + b.max.y) / 2, (b.min.z + b.max.z) / 2);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function boxIntersectsSphere(b: Box, s: Sph): boolean {
|
|
337
|
+
const c = clampToBox(b, s.center);
|
|
338
|
+
return vec3.dot(vec3.sub(c, s.center), vec3.sub(c, s.center)) <= s.radius * s.radius;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export const box3: typeof Api.box3 = Object.freeze({
|
|
342
|
+
empty: emptyBox,
|
|
343
|
+
fromPoints: boxFromPoints,
|
|
344
|
+
isEmpty: isEmptyBox,
|
|
345
|
+
containsPoint: (b: Box, p: V3) =>
|
|
346
|
+
!(p.x < b.min.x || p.x > b.max.x || p.y < b.min.y || p.y > b.max.y || p.z < b.min.z || p.z > b.max.z),
|
|
347
|
+
containsBox: (b: Box, o: Box) =>
|
|
348
|
+
b.min.x <= o.min.x && o.max.x <= b.max.x && b.min.y <= o.min.y && o.max.y <= b.max.y && b.min.z <= o.min.z && o.max.z <= b.max.z,
|
|
349
|
+
intersectsBox: (a: Box, b: Box) =>
|
|
350
|
+
!(b.max.x < a.min.x || b.min.x > a.max.x || b.max.y < a.min.y || b.min.y > a.max.y || b.max.z < a.min.z || b.min.z > a.max.z),
|
|
351
|
+
intersectsSphere: boxIntersectsSphere,
|
|
352
|
+
expandByPoint: expand,
|
|
353
|
+
union: (a: Box, b: Box) =>
|
|
354
|
+
box(
|
|
355
|
+
v3(Math.min(a.min.x, b.min.x), Math.min(a.min.y, b.min.y), Math.min(a.min.z, b.min.z)),
|
|
356
|
+
v3(Math.max(a.max.x, b.max.x), Math.max(a.max.y, b.max.y), Math.max(a.max.z, b.max.z)),
|
|
357
|
+
),
|
|
358
|
+
getCenter: boxCenter,
|
|
359
|
+
getSize: (b: Box) => (isEmptyBox(b) ? v3(0, 0, 0) : v3(b.max.x - b.min.x, b.max.y - b.min.y, b.max.z - b.min.z)),
|
|
360
|
+
applyMatrix4: (b: Box, m: M4) => {
|
|
361
|
+
if (isEmptyBox(b)) return b;
|
|
362
|
+
const corners: V3[] = [];
|
|
363
|
+
for (const x of [b.min.x, b.max.x]) for (const y of [b.min.y, b.max.y]) for (const z of [b.min.z, b.max.z]) corners.push(transformPointM(m, v3(x, y, z)));
|
|
364
|
+
return boxFromPoints(corners);
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ── sphere ─────────────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
export const sphere: typeof Api.sphere = Object.freeze({
|
|
371
|
+
fromPoints: (points: readonly V3[]) => {
|
|
372
|
+
const center = boxCenter(boxFromPoints(points));
|
|
373
|
+
let r2 = 0;
|
|
374
|
+
for (const p of points) {
|
|
375
|
+
const d = vec3.sub(p, center);
|
|
376
|
+
r2 = Math.max(r2, vec3.dot(d, d));
|
|
377
|
+
}
|
|
378
|
+
return { center, radius: Math.sqrt(r2) };
|
|
379
|
+
},
|
|
380
|
+
containsPoint: (s: Sph, p: V3) => {
|
|
381
|
+
const d = vec3.sub(p, s.center);
|
|
382
|
+
return vec3.dot(d, d) <= s.radius * s.radius;
|
|
383
|
+
},
|
|
384
|
+
distanceToPoint: (s: Sph, p: V3) => vec3.distance(p, s.center) - s.radius,
|
|
385
|
+
intersectsSphere: (a: Sph, b: Sph) => {
|
|
386
|
+
const r = a.radius + b.radius;
|
|
387
|
+
const d = vec3.sub(a.center, b.center);
|
|
388
|
+
return vec3.dot(d, d) <= r * r;
|
|
389
|
+
},
|
|
390
|
+
intersectsBox: (s: Sph, b: Box) => boxIntersectsSphere(b, s),
|
|
391
|
+
getBoundingBox: (s: Sph) =>
|
|
392
|
+
s.radius < 0
|
|
393
|
+
? emptyBox()
|
|
394
|
+
: box(vec3.sub(s.center, v3(s.radius, s.radius, s.radius)), vec3.add(s.center, v3(s.radius, s.radius, s.radius))),
|
|
395
|
+
applyMatrix4: (s: Sph, m: M4) => {
|
|
396
|
+
const e = m.elements;
|
|
397
|
+
// three.js Matrix4.getMaxScaleOnAxis.
|
|
398
|
+
const sx = e[0]! * e[0]! + e[1]! * e[1]! + e[2]! * e[2]!;
|
|
399
|
+
const sy = e[4]! * e[4]! + e[5]! * e[5]! + e[6]! * e[6]!;
|
|
400
|
+
const sz = e[8]! * e[8]! + e[9]! * e[9]! + e[10]! * e[10]!;
|
|
401
|
+
return { center: transformPointM(m, s.center), radius: s.radius * Math.sqrt(Math.max(sx, sy, sz)) };
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ── plane ──────────────────────────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
const distanceToPlane = (pl: Pl, p: V3) => vec3.dot(pl.normal, p) + pl.constant;
|
|
408
|
+
|
|
409
|
+
function normalizePlane(pl: Pl): Pl {
|
|
410
|
+
const inv = 1 / vec3.length(pl.normal);
|
|
411
|
+
return { normal: vec3.scale(pl.normal, inv), constant: pl.constant * inv };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function planeFromNormalAndPoint(n: V3, p: V3): Pl {
|
|
415
|
+
const normal = vec3.normalize(n);
|
|
416
|
+
return { normal, constant: -vec3.dot(p, normal) };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export const plane: typeof Api.plane = Object.freeze({
|
|
420
|
+
fromNormalAndCoplanarPoint: planeFromNormalAndPoint,
|
|
421
|
+
fromCoplanarPoints: (a: V3, b: V3, c: V3) =>
|
|
422
|
+
planeFromNormalAndPoint(vec3.cross(vec3.sub(c, b), vec3.sub(a, b)), a),
|
|
423
|
+
normalize: normalizePlane,
|
|
424
|
+
distanceToPoint: distanceToPlane,
|
|
425
|
+
projectPoint: (pl: Pl, p: V3) => vec3.sub(p, vec3.scale(pl.normal, distanceToPlane(pl, p))),
|
|
426
|
+
intersectsBox: (pl: Pl, b: Box) => {
|
|
427
|
+
// three.js Plane.intersectsBox: the box's corners on both sides.
|
|
428
|
+
let lo = Infinity;
|
|
429
|
+
let hi = -Infinity;
|
|
430
|
+
for (const x of [b.min.x, b.max.x]) for (const y of [b.min.y, b.max.y]) for (const z of [b.min.z, b.max.z]) {
|
|
431
|
+
const d = distanceToPlane(pl, v3(x, y, z));
|
|
432
|
+
lo = Math.min(lo, d);
|
|
433
|
+
hi = Math.max(hi, d);
|
|
434
|
+
}
|
|
435
|
+
return lo <= 0 && hi >= 0;
|
|
436
|
+
},
|
|
437
|
+
intersectsSphere: (pl: Pl, s: Sph) => Math.abs(distanceToPlane(pl, s.center)) <= s.radius,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// ── frustum ────────────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
function frustumFromMatrix(m: M4): Fr {
|
|
443
|
+
// three.js Frustum.setFromProjectionMatrix (WebGL clip space).
|
|
444
|
+
const e = m.elements as unknown as number[];
|
|
445
|
+
const pl = (a: number, b: number, c: number, d: number) => normalizePlane({ normal: v3(a, b, c), constant: d });
|
|
446
|
+
return {
|
|
447
|
+
planes: [
|
|
448
|
+
pl(e[3]! - e[0]!, e[7]! - e[4]!, e[11]! - e[8]!, e[15]! - e[12]!),
|
|
449
|
+
pl(e[3]! + e[0]!, e[7]! + e[4]!, e[11]! + e[8]!, e[15]! + e[12]!),
|
|
450
|
+
pl(e[3]! + e[1]!, e[7]! + e[5]!, e[11]! + e[9]!, e[15]! + e[13]!),
|
|
451
|
+
pl(e[3]! - e[1]!, e[7]! - e[5]!, e[11]! - e[9]!, e[15]! - e[13]!),
|
|
452
|
+
pl(e[3]! - e[2]!, e[7]! - e[6]!, e[11]! - e[10]!, e[15]! - e[14]!),
|
|
453
|
+
pl(e[3]! + e[2]!, e[7]! + e[6]!, e[11]! + e[10]!, e[15]! + e[14]!),
|
|
454
|
+
],
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** The projection of og.Camera (three.js PerspectiveCamera / OrthographicCamera.updateProjectionMatrix). */
|
|
459
|
+
function projection(c: Api.Camera, aspect: number): M4 {
|
|
460
|
+
const zoom = c.zoom || 1;
|
|
461
|
+
const near = c.near;
|
|
462
|
+
const far = c.far;
|
|
463
|
+
let left: number, right: number, top: number, bottom: number;
|
|
464
|
+
if (c.orthographic) {
|
|
465
|
+
top = c.height / 2 / zoom;
|
|
466
|
+
bottom = -top;
|
|
467
|
+
right = top * aspect;
|
|
468
|
+
left = -right;
|
|
469
|
+
const w = 1 / (right - left), h = 1 / (top - bottom), p = 1 / (far - near);
|
|
470
|
+
return m4([2 * w, 0, 0, 0, 0, 2 * h, 0, 0, 0, 0, -2 * p, 0, -(right + left) * w, -(top + bottom) * h, -(far + near) * p, 1]);
|
|
471
|
+
}
|
|
472
|
+
const [s, co] = sincos((c.fov * PI) / 360);
|
|
473
|
+
top = (near * s) / co / zoom;
|
|
474
|
+
const height = 2 * top;
|
|
475
|
+
const width = aspect * height;
|
|
476
|
+
left = -width / 2;
|
|
477
|
+
right = left + width;
|
|
478
|
+
bottom = top - height;
|
|
479
|
+
const x = (2 * near) / (right - left);
|
|
480
|
+
const y = (2 * near) / (top - bottom);
|
|
481
|
+
const a = (right + left) / (right - left);
|
|
482
|
+
const b = (top + bottom) / (top - bottom);
|
|
483
|
+
const cc = -(far + near) / (far - near);
|
|
484
|
+
const d = (-2 * far * near) / (far - near);
|
|
485
|
+
return m4([x, 0, 0, 0, 0, y, 0, 0, a, b, cc, -1, 0, 0, d, 0]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const frustumContains = (f: Fr, p: V3) => f.planes.every((pl) => distanceToPlane(pl, p) >= 0);
|
|
489
|
+
|
|
490
|
+
export const frustum: typeof Api.frustum = Object.freeze({
|
|
491
|
+
fromProjectionMatrix: frustumFromMatrix,
|
|
492
|
+
fromCamera: (camera: Api.Camera, aspect: number) => {
|
|
493
|
+
const world = composeM(camera.position, camera.quaternion, v3(1, 1, 1));
|
|
494
|
+
return frustumFromMatrix(multiplyM(projection(camera, aspect), invertM(world)));
|
|
495
|
+
},
|
|
496
|
+
containsPoint: frustumContains,
|
|
497
|
+
intersectsBox: (f: Fr, b: Box) =>
|
|
498
|
+
f.planes.every((pl) => {
|
|
499
|
+
// three.js: the corner furthest along the normal.
|
|
500
|
+
const p = v3(pl.normal.x > 0 ? b.max.x : b.min.x, pl.normal.y > 0 ? b.max.y : b.min.y, pl.normal.z > 0 ? b.max.z : b.min.z);
|
|
501
|
+
return distanceToPlane(pl, p) >= 0;
|
|
502
|
+
}),
|
|
503
|
+
intersectsSphere: (f: Fr, s: Sph) => f.planes.every((pl) => distanceToPlane(pl, s.center) >= -s.radius),
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// ── ray ────────────────────────────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
const at = (r: R, t: number) => vec3.add(r.origin, vec3.scale(r.direction, t));
|
|
509
|
+
|
|
510
|
+
function distanceSqToPoint(r: R, p: V3): number {
|
|
511
|
+
const t = vec3.dot(vec3.sub(p, r.origin), r.direction);
|
|
512
|
+
const q = t < 0 ? r.origin : at(r, t);
|
|
513
|
+
const d = vec3.sub(q, p);
|
|
514
|
+
return vec3.dot(d, d);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export const ray: typeof Api.ray = Object.freeze({
|
|
518
|
+
at,
|
|
519
|
+
distanceToPoint: (r: R, p: V3) => Math.sqrt(distanceSqToPoint(r, p)),
|
|
520
|
+
intersectBox: (r: R, b: Box) => {
|
|
521
|
+
// three.js Ray.intersectBox (slabs; a NaN-safe order of the tests).
|
|
522
|
+
const o = r.origin;
|
|
523
|
+
const d = r.direction;
|
|
524
|
+
const slab = (lo: number, hi: number, origin: number, dir: number): [number, number] => {
|
|
525
|
+
const inv = 1 / dir;
|
|
526
|
+
return inv >= 0 ? [(lo - origin) * inv, (hi - origin) * inv] : [(hi - origin) * inv, (lo - origin) * inv];
|
|
527
|
+
};
|
|
528
|
+
let [tmin, tmax] = slab(b.min.x, b.max.x, o.x, d.x);
|
|
529
|
+
const [tymin, tymax] = slab(b.min.y, b.max.y, o.y, d.y);
|
|
530
|
+
if (tmin > tymax || tymin > tmax) return null;
|
|
531
|
+
if (tymin > tmin || Number.isNaN(tmin)) tmin = tymin;
|
|
532
|
+
if (tymax < tmax || Number.isNaN(tmax)) tmax = tymax;
|
|
533
|
+
const [tzmin, tzmax] = slab(b.min.z, b.max.z, o.z, d.z);
|
|
534
|
+
if (tmin > tzmax || tzmin > tmax) return null;
|
|
535
|
+
if (tzmin > tmin || Number.isNaN(tmin)) tmin = tzmin;
|
|
536
|
+
if (tzmax < tmax || Number.isNaN(tmax)) tmax = tzmax;
|
|
537
|
+
if (tmax < 0) return null;
|
|
538
|
+
return at(r, tmin >= 0 ? tmin : tmax);
|
|
539
|
+
},
|
|
540
|
+
intersectSphere: (r: R, s: Sph) => {
|
|
541
|
+
// three.js Ray.intersectSphere.
|
|
542
|
+
const l = vec3.sub(s.center, r.origin);
|
|
543
|
+
const tca = vec3.dot(l, r.direction);
|
|
544
|
+
const d2 = vec3.dot(l, l) - tca * tca;
|
|
545
|
+
const r2 = s.radius * s.radius;
|
|
546
|
+
if (d2 > r2) return null;
|
|
547
|
+
const thc = Math.sqrt(r2 - d2);
|
|
548
|
+
const t0 = tca - thc;
|
|
549
|
+
const t1 = tca + thc;
|
|
550
|
+
if (t1 < 0) return null;
|
|
551
|
+
return at(r, t0 < 0 ? t1 : t0);
|
|
552
|
+
},
|
|
553
|
+
intersectPlane: (r: R, pl: Pl) => {
|
|
554
|
+
// three.js Ray.distanceToPlane, then at().
|
|
555
|
+
const den = vec3.dot(pl.normal, r.direction);
|
|
556
|
+
if (den === 0) return distanceToPlane(pl, r.origin) === 0 ? at(r, 0) : null;
|
|
557
|
+
const t = -(vec3.dot(r.origin, pl.normal) + pl.constant) / den;
|
|
558
|
+
return t >= 0 ? at(r, t) : null;
|
|
559
|
+
},
|
|
560
|
+
intersectTriangle: (r: R, a: V3, b: V3, c: V3, backfaceCulling = false) => {
|
|
561
|
+
// three.js Ray.intersectTriangle (Möller-Trumbore in its form).
|
|
562
|
+
const edge1 = vec3.sub(b, a);
|
|
563
|
+
const edge2 = vec3.sub(c, a);
|
|
564
|
+
const normal = vec3.cross(edge1, edge2);
|
|
565
|
+
let ddn = vec3.dot(r.direction, normal);
|
|
566
|
+
let sign: number;
|
|
567
|
+
if (ddn > 0) {
|
|
568
|
+
if (backfaceCulling) return null;
|
|
569
|
+
sign = 1;
|
|
570
|
+
} else if (ddn < 0) {
|
|
571
|
+
sign = -1;
|
|
572
|
+
ddn = -ddn;
|
|
573
|
+
} else return null;
|
|
574
|
+
const diff = vec3.sub(r.origin, a);
|
|
575
|
+
const ddqxe2 = sign * vec3.dot(r.direction, vec3.cross(diff, edge2));
|
|
576
|
+
if (ddqxe2 < 0) return null;
|
|
577
|
+
const dde1xq = sign * vec3.dot(r.direction, vec3.cross(edge1, diff));
|
|
578
|
+
if (dde1xq < 0) return null;
|
|
579
|
+
if (ddqxe2 + dde1xq > ddn) return null;
|
|
580
|
+
const qdn = -sign * vec3.dot(diff, normal);
|
|
581
|
+
if (qdn < 0) return null;
|
|
582
|
+
return at(r, qdn / ddn);
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// ── color ──────────────────────────────────────────────────────────────────
|
|
587
|
+
|
|
588
|
+
const rgb = (r: number, g: number, b: number): C => ({ r, g, b });
|
|
589
|
+
|
|
590
|
+
function hue2rgb(p: number, q: number, t: number): number {
|
|
591
|
+
if (t < 0) t += 1;
|
|
592
|
+
if (t > 1) t -= 1;
|
|
593
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
594
|
+
if (t < 1 / 2) return q;
|
|
595
|
+
if (t < 2 / 3) return p + (q - p) * 6 * (2 / 3 - t);
|
|
596
|
+
return p;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export const color: typeof Api.color = Object.freeze({
|
|
600
|
+
fromHex: (hex: number) => {
|
|
601
|
+
const h = Math.floor(hex);
|
|
602
|
+
return rgb(srgbToLinear(((h >> 16) & 255) / 255), srgbToLinear(((h >> 8) & 255) / 255), srgbToLinear((h & 255) / 255));
|
|
603
|
+
},
|
|
604
|
+
toHex: (c: C) => {
|
|
605
|
+
const ch = (v: number) => Math.round(clamp(linearToSrgb(v), 0, 1) * 255);
|
|
606
|
+
return ((ch(c.r) << 16) | (ch(c.g) << 8) | ch(c.b)) >>> 0;
|
|
607
|
+
},
|
|
608
|
+
lerp: (a: C, b: C, t: number) => rgb(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t, a.b + (b.b - a.b) * t),
|
|
609
|
+
fromHsl: (h: number, s: number, l: number) => {
|
|
610
|
+
// three.js Color.setHSL: euclidean modulo of the hue, clamped s and l, then sRGB to linear.
|
|
611
|
+
h = ((h % 1) + 1) % 1;
|
|
612
|
+
s = clamp(s, 0, 1);
|
|
613
|
+
l = clamp(l, 0, 1);
|
|
614
|
+
if (s === 0) {
|
|
615
|
+
const v = srgbToLinear(l);
|
|
616
|
+
return rgb(v, v, v);
|
|
617
|
+
}
|
|
618
|
+
const p = l <= 0.5 ? l * (1 + s) : l + s - l * s;
|
|
619
|
+
const q = 2 * l - p;
|
|
620
|
+
return rgb(srgbToLinear(hue2rgb(q, p, h + 1 / 3)), srgbToLinear(hue2rgb(q, p, h)), srgbToLinear(hue2rgb(q, p, h - 1 / 3)));
|
|
621
|
+
},
|
|
622
|
+
});
|
package/src/node/index.ts
CHANGED
|
@@ -50,6 +50,23 @@ export interface NativeExtras {
|
|
|
50
50
|
og_node_probe_listen(engine: number, port: number): string;
|
|
51
51
|
og_node_probe_serve(engine: number): number;
|
|
52
52
|
og_node_probe_close(engine: number): boolean;
|
|
53
|
+
/** Connect an engine's rooms to `og relay` (`ws://host:port/<room>` joins that room). A Result. */
|
|
54
|
+
og_node_room_connect(engine: number, url: string): string;
|
|
55
|
+
/** Carry room ops to the relay and its events to the engine; the events delivered. */
|
|
56
|
+
og_node_room_pump(engine: number): number;
|
|
57
|
+
og_node_room_close(engine: number): boolean;
|
|
58
|
+
/** Start a relay in this process on 127.0.0.1 (0 a free port) for as long as it lives. A Result with the port. */
|
|
59
|
+
og_node_relay_start(port: number): string;
|
|
60
|
+
/** `og render`: the frame the loop drew last, RGBA8 rows at the offscreen size (empty without one). Not in publish builds. */
|
|
61
|
+
og_node_render_frame(engine: number): Uint8Array;
|
|
62
|
+
/** `og render --jobs`: the world as a render snapshot (empty when refused). */
|
|
63
|
+
og_node_render_snapshot(engine: number): Uint8Array;
|
|
64
|
+
/** Restores a render snapshot another process took: a wire Result of its tick. */
|
|
65
|
+
og_node_render_restore(engine: number, snapshot: Uint8Array): string;
|
|
66
|
+
/** `og run --trace`: turns the engine's trace on or off. */
|
|
67
|
+
og_node_trace(engine: number, on: boolean): void;
|
|
68
|
+
/** The trace's spans since the last take: `{ now, events }`, Chrome trace events on the engine's clock (src/trace.ts); `last` waits for the GPU's frames in flight. */
|
|
69
|
+
og_node_trace_take(engine: number, last: boolean): string;
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
let extras: NativeExtras | null = null;
|