trid3d 0.0.0-stage → 0.1.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.
@@ -0,0 +1,302 @@
1
+ import { project, screen } from "./projection.js";
2
+
3
+ /**
4
+ * Camera object. Every field is optional and falls back to its default.
5
+ *
6
+ * The camera looks down +Z when yaw and pitch are 0.
7
+ *
8
+ * @typedef {Object} Camera
9
+ * @property {number} [x=0] - Position on X
10
+ * @property {number} [y=0] - Position on Y
11
+ * @property {number} [z=0] - Position on Z
12
+ * @property {number} [yaw=0] - Turn around Y in radians (> 0 turns right)
13
+ * @property {number} [pitch=0] - Tilt around X in radians (> 0 looks up)
14
+ * @property {number} [fov=Math.PI/2] - Vertical field of view in radians
15
+ * @property {number} [near=0.01] - Near plane distance; closer geometry is clipped
16
+ * @property {number} [far=1000] - Far distance; farther geometry isn't drawn (WebGL renderer only)
17
+ */
18
+
19
+ /** Default vertical field of view (90°). */
20
+ export const DEFAULT_FOV = Math.PI / 2;
21
+
22
+ /** Default near plane distance. */
23
+ export const DEFAULT_NEAR = 0.01;
24
+
25
+ /**
26
+ * Converts a vertex from local (mesh) space to world space
27
+ * using an instance transform.
28
+ *
29
+ * Transformation order:
30
+ * 1. Scale
31
+ * 2. Rotate (X then Y then Z)
32
+ * 3. Translate to world position
33
+ *
34
+ * @param {{x:number,y:number,z:number}} v - Local-space vertex
35
+ * @param {{
36
+ * pos:{x:number,y:number,z:number},
37
+ * rot:{x:number,y:number,z:number},
38
+ * scale:{x:number,y:number,z:number}
39
+ * }} inst - Mesh instance transform
40
+ *
41
+ * @returns {{x:number,y:number,z:number}} World-space vertex
42
+ */
43
+ export function localToWorld(v, inst) {
44
+ let p = scale(v, inst.scale.x, inst.scale.y, inst.scale.z);
45
+ p = rotateXYZ(p, inst.rot);
46
+ p = translate(p, inst.pos.x, inst.pos.y, inst.pos.z);
47
+ return p;
48
+ }
49
+
50
+
51
+ /**
52
+ * Converts a world-space point into camera space.
53
+ *
54
+ * Steps:
55
+ * 1. Move the point relative to the camera position
56
+ * 2. Undo the camera's yaw, then its pitch
57
+ *
58
+ * In camera space the camera sits at the origin looking down +Z.
59
+ *
60
+ * @param {{x:number,y:number,z:number}} pWorld - World-space point
61
+ * @param {Camera} camera - Camera object
62
+ *
63
+ * @returns {{x:number,y:number,z:number}} Camera-space point
64
+ */
65
+ export function worldToCamera(pWorld, camera) {
66
+ let p = translate(pWorld, -(camera.x ?? 0), -(camera.y ?? 0), -(camera.z ?? 0));
67
+ p = rotateY(p, camera.yaw ?? 0);
68
+ p = rotateX(p, camera.pitch ?? 0);
69
+ return p;
70
+ }
71
+
72
+ /**
73
+ * Converts a camera-space point into screen-space coordinates.
74
+ *
75
+ * Steps:
76
+ * 1. Perspective projection (3D → 2D) using the camera's fov
77
+ * and the canvas aspect ratio
78
+ * 2. Convert normalized coords to screen pixels
79
+ *
80
+ * NOTE: z must be > 0 (in front of the camera). Use `clipNear`
81
+ * on segments first if they may cross behind the camera.
82
+ *
83
+ * @param {{x:number,y:number,z:number}} pCam - Camera-space point
84
+ * @param {Camera} camera - Camera object
85
+ * @param {number} w - Canvas width
86
+ * @param {number} h - Canvas height
87
+ *
88
+ * @returns {{x:number,y:number}} Screen-space point
89
+ */
90
+ export function cameraToScreen(pCam, camera, w, h) {
91
+ return screen(project(pCam, camera.fov ?? DEFAULT_FOV, w / h), w, h);
92
+ }
93
+
94
+ /**
95
+ * Converts a world-space point into screen-space coordinates.
96
+ *
97
+ * Steps:
98
+ * 1. World space → camera space (`worldToCamera`)
99
+ * 2. Camera space → screen pixels (`cameraToScreen`)
100
+ *
101
+ * @param {{x:number,y:number,z:number}} pWorld - World-space point
102
+ * @param {Camera} camera - Camera object
103
+ * @param {number} w - Canvas width
104
+ * @param {number} h - Canvas height
105
+ *
106
+ * @returns {{x:number,y:number}} Screen-space point
107
+ */
108
+ export function worldToScreen(pWorld, camera, w, h) {
109
+ return cameraToScreen(worldToCamera(pWorld, camera), camera, w, h);
110
+ }
111
+
112
+ /**
113
+ * Direction the camera looks at, in world space (unit vector).
114
+ *
115
+ * @param {Camera} camera - Camera object
116
+ * @returns {{x:number,y:number,z:number}} Forward vector
117
+ */
118
+ export function cameraForward(camera) {
119
+ const yaw = camera.yaw ?? 0;
120
+ const pitch = camera.pitch ?? 0;
121
+ return {
122
+ x: Math.sin(yaw) * Math.cos(pitch),
123
+ y: Math.sin(pitch),
124
+ z: Math.cos(yaw) * Math.cos(pitch),
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Direction of the screen's right side, in world space (unit vector).
130
+ *
131
+ * @param {Camera} camera - Camera object
132
+ * @returns {{x:number,y:number,z:number}} Right vector
133
+ */
134
+ export function cameraRight(camera) {
135
+ const yaw = camera.yaw ?? 0;
136
+ return {
137
+ x: Math.cos(yaw),
138
+ y: 0,
139
+ z: -Math.sin(yaw),
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Direction of the screen's top side, in world space (unit vector).
145
+ *
146
+ * @param {Camera} camera - Camera object
147
+ * @returns {{x:number,y:number,z:number}} Up vector
148
+ */
149
+ export function cameraUp(camera) {
150
+ const yaw = camera.yaw ?? 0;
151
+ const pitch = camera.pitch ?? 0;
152
+ return {
153
+ x: -Math.sin(pitch) * Math.sin(yaw),
154
+ y: Math.cos(pitch),
155
+ z: -Math.sin(pitch) * Math.cos(yaw),
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Clips a camera-space segment against the near plane (z = near).
161
+ *
162
+ * - Both ends in front: segment returned unchanged
163
+ * - Both ends behind: returns null (nothing to draw)
164
+ * - One end behind: that end is moved onto the near plane
165
+ *
166
+ * @param {{x:number,y:number,z:number}} a - Segment start (camera space)
167
+ * @param {{x:number,y:number,z:number}} b - Segment end (camera space)
168
+ * @param {number} near - Near plane distance (> 0)
169
+ *
170
+ * @returns {[{x:number,y:number,z:number}, {x:number,y:number,z:number}] | null}
171
+ */
172
+ export function clipNear(a, b, near) {
173
+ const aIn = a.z >= near;
174
+ const bIn = b.z >= near;
175
+
176
+ if (aIn && bIn) return [a, b];
177
+ if (!aIn && !bIn) return null;
178
+
179
+ const t = (near - a.z) / (b.z - a.z);
180
+ const p = {
181
+ x: a.x + (b.x - a.x) * t,
182
+ y: a.y + (b.y - a.y) * t,
183
+ z: near,
184
+ };
185
+
186
+ return aIn ? [a, p] : [p, b];
187
+ }
188
+
189
+ /**
190
+ * Scales a 3D point along each axis.
191
+ *
192
+ * @param {{x:number,y:number,z:number}} p - Input point
193
+ * @param {number} sx - Scale factor on X axis
194
+ * @param {number} sy - Scale factor on Y axis
195
+ * @param {number} sz - Scale factor on Z axis
196
+ * @returns {{x:number,y:number,z:number}} Scaled point
197
+ */
198
+ export function scale(p, sx, sy, sz) {
199
+ return {
200
+ x: p.x * sx,
201
+ y: p.y * sy,
202
+ z: p.z * sz,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Translates a 3D point in space.
208
+ *
209
+ * @param {{x:number,y:number,z:number}} p - Input point
210
+ * @param {number} dx - Translation on X axis
211
+ * @param {number} dy - Translation on Y axis
212
+ * @param {number} dz - Translation on Z axis
213
+ * @returns {{x:number,y:number,z:number}} Translated point
214
+ */
215
+ export function translate(p, dx, dy, dz) {
216
+ return {
217
+ x: p.x + dx,
218
+ y: p.y + dy,
219
+ z: p.z + dz,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Rotates a 3D point around the X axis (YZ plane).
225
+ *
226
+ * This creates an up/down “pitch” rotation.
227
+ *
228
+ * @param {{x:number,y:number,z:number}} p - Input point
229
+ * @param {number} angle - Rotation angle in radians
230
+ * @returns {{x:number,y:number,z:number}} Rotated point
231
+ */
232
+ export function rotateX(p, angle) {
233
+ const c = Math.cos(angle);
234
+ const s = Math.sin(angle);
235
+
236
+ return {
237
+ x: p.x,
238
+ y: p.y * c - p.z * s,
239
+ z: p.y * s + p.z * c,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Rotates a 3D point around the Y axis (XZ plane).
245
+ *
246
+ * This creates a left/right “yaw” rotation.
247
+ *
248
+ * @param {{x:number,y:number,z:number}} p - Input point
249
+ * @param {number} angle - Rotation angle in radians
250
+ * @returns {{x:number,y:number,z:number}} Rotated point
251
+ */
252
+ export function rotateY(p, angle) {
253
+ const c = Math.cos(angle);
254
+ const s = Math.sin(angle);
255
+
256
+ return {
257
+ x: p.x * c - p.z * s,
258
+ y: p.y,
259
+ z: p.x * s + p.z * c,
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Rotates a 3D point around the Z axis (XY plane).
265
+ *
266
+ * This creates a “roll” rotation (spins in 2D).
267
+ *
268
+ * @param {{x:number,y:number,z:number}} p - Input point
269
+ * @param {number} angle - Rotation angle in radians
270
+ * @returns {{x:number,y:number,z:number}} Rotated point
271
+ */
272
+ export function rotateZ(p, angle) {
273
+ const c = Math.cos(angle);
274
+ const s = Math.sin(angle);
275
+
276
+ return {
277
+ x: p.x * c - p.y * s,
278
+ y: p.x * s + p.y * c,
279
+ z: p.z,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Applies Euler rotations to a point.
285
+ *
286
+ * Rotation order matters. This function applies:
287
+ * 1) rotateX
288
+ * 2) rotateY
289
+ * 3) rotateZ
290
+ *
291
+ * If you change the order, the final orientation will change.
292
+ *
293
+ * @param {{x:number,y:number,z:number}} p - Input point
294
+ * @param {{x:number,y:number,z:number}} rot - Rotation angles in radians
295
+ * @returns {{x:number,y:number,z:number}} Rotated point
296
+ */
297
+ export function rotateXYZ(p, rot) {
298
+ let r = rotateX(p, rot.x);
299
+ r = rotateY(r, rot.y);
300
+ r = rotateZ(r, rot.z);
301
+ return r;
302
+ }