@genart-dev/projection 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,670 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ add: () => add,
24
+ aerialCamera: () => aerialCamera,
25
+ createCamera: () => createCamera,
26
+ cross: () => cross,
27
+ depthSort: () => depthSort,
28
+ depthSortIndices: () => depthSortIndices,
29
+ deriveVanishingPoints: () => deriveVanishingPoints,
30
+ distance: () => distance,
31
+ distanceSq: () => distanceSq,
32
+ distanceTo: () => distanceTo,
33
+ dollyCamera: () => dollyCamera,
34
+ dot: () => dot,
35
+ frustumContainsBox: () => frustumContainsBox,
36
+ frustumContainsPoint: () => frustumContainsPoint,
37
+ horizonScreenY: () => horizonScreenY,
38
+ identity: () => identity,
39
+ invert: () => invert,
40
+ isBackFace: () => isBackFace,
41
+ isometricCamera: () => isometricCamera,
42
+ landscapeCamera: () => landscapeCamera,
43
+ length: () => length,
44
+ lengthSq: () => lengthSq,
45
+ lerpCamera: () => lerpCamera,
46
+ lerpVec3: () => lerp,
47
+ lookAt: () => lookAt,
48
+ multiply: () => multiply,
49
+ negate: () => negate,
50
+ normalize: () => normalize,
51
+ normalizedDepth: () => normalizedDepth,
52
+ orbitCamera: () => orbitCamera,
53
+ orthographicMatrix: () => orthographicMatrix,
54
+ panCamera: () => panCamera,
55
+ perspectiveMatrix: () => perspectiveMatrix,
56
+ project: () => project,
57
+ projectMany: () => projectMany,
58
+ projectWithMatrix: () => projectWithMatrix,
59
+ scaleAtDepth: () => scaleAtDepth,
60
+ scaleVec3: () => scale,
61
+ streetCamera: () => streetCamera,
62
+ sub: () => sub,
63
+ transformDirection: () => transformDirection,
64
+ transformPoint: () => transformPoint,
65
+ transformPointRaw: () => transformPointRaw,
66
+ unproject: () => unproject,
67
+ updateCamera: () => updateCamera,
68
+ vec3: () => vec3,
69
+ viewProjectionMatrix: () => viewProjectionMatrix,
70
+ wormEyeCamera: () => wormEyeCamera
71
+ });
72
+ module.exports = __toCommonJS(index_exports);
73
+
74
+ // src/vec3.ts
75
+ function vec3(x, y, z) {
76
+ return { x, y, z };
77
+ }
78
+ function add(a, b) {
79
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
80
+ }
81
+ function sub(a, b) {
82
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
83
+ }
84
+ function scale(v, s) {
85
+ return { x: v.x * s, y: v.y * s, z: v.z * s };
86
+ }
87
+ function dot(a, b) {
88
+ return a.x * b.x + a.y * b.y + a.z * b.z;
89
+ }
90
+ function cross(a, b) {
91
+ return {
92
+ x: a.y * b.z - a.z * b.y,
93
+ y: a.z * b.x - a.x * b.z,
94
+ z: a.x * b.y - a.y * b.x
95
+ };
96
+ }
97
+ function length(v) {
98
+ return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
99
+ }
100
+ function lengthSq(v) {
101
+ return v.x * v.x + v.y * v.y + v.z * v.z;
102
+ }
103
+ function normalize(v) {
104
+ const len = length(v);
105
+ if (len === 0) return { x: 0, y: 0, z: 0 };
106
+ return { x: v.x / len, y: v.y / len, z: v.z / len };
107
+ }
108
+ function negate(v) {
109
+ return { x: -v.x, y: -v.y, z: -v.z };
110
+ }
111
+ function lerp(a, b, t) {
112
+ return {
113
+ x: a.x + (b.x - a.x) * t,
114
+ y: a.y + (b.y - a.y) * t,
115
+ z: a.z + (b.z - a.z) * t
116
+ };
117
+ }
118
+ function distance(a, b) {
119
+ return length(sub(a, b));
120
+ }
121
+ function distanceSq(a, b) {
122
+ return lengthSq(sub(a, b));
123
+ }
124
+
125
+ // src/mat4.ts
126
+ function identity() {
127
+ const m = new Float64Array(16);
128
+ m[0] = 1;
129
+ m[5] = 1;
130
+ m[10] = 1;
131
+ m[15] = 1;
132
+ return m;
133
+ }
134
+ function lookAt(eye, target, up) {
135
+ const f = normalize(sub(target, eye));
136
+ const s = normalize(cross(up, f));
137
+ const u = cross(f, s);
138
+ const m = new Float64Array(16);
139
+ m[0] = s.x;
140
+ m[1] = u.x;
141
+ m[2] = -f.x;
142
+ m[3] = 0;
143
+ m[4] = s.y;
144
+ m[5] = u.y;
145
+ m[6] = -f.y;
146
+ m[7] = 0;
147
+ m[8] = s.z;
148
+ m[9] = u.z;
149
+ m[10] = -f.z;
150
+ m[11] = 0;
151
+ m[12] = -dot(s, eye);
152
+ m[13] = -dot(u, eye);
153
+ m[14] = dot(f, eye);
154
+ m[15] = 1;
155
+ return m;
156
+ }
157
+ function perspectiveMatrix(fovDegrees, aspect, near, far) {
158
+ const fovRad = fovDegrees * Math.PI / 180;
159
+ const f = 1 / Math.tan(fovRad / 2);
160
+ const rangeInv = 1 / (near - far);
161
+ const m = new Float64Array(16);
162
+ m[0] = f / aspect;
163
+ m[5] = f;
164
+ m[10] = (far + near) * rangeInv;
165
+ m[11] = -1;
166
+ m[14] = 2 * far * near * rangeInv;
167
+ return m;
168
+ }
169
+ function orthographicMatrix(left, right, bottom, top, near, far) {
170
+ const m = new Float64Array(16);
171
+ m[0] = 2 / (right - left);
172
+ m[5] = 2 / (top - bottom);
173
+ m[10] = -2 / (far - near);
174
+ m[12] = -(right + left) / (right - left);
175
+ m[13] = -(top + bottom) / (top - bottom);
176
+ m[14] = -(far + near) / (far - near);
177
+ m[15] = 1;
178
+ return m;
179
+ }
180
+ function multiply(a, b) {
181
+ const r = new Float64Array(16);
182
+ for (let col = 0; col < 4; col++) {
183
+ const c0 = b[col * 4];
184
+ const c1 = b[col * 4 + 1];
185
+ const c2 = b[col * 4 + 2];
186
+ const c3 = b[col * 4 + 3];
187
+ for (let row = 0; row < 4; row++) {
188
+ r[col * 4 + row] = a[row] * c0 + a[4 + row] * c1 + a[8 + row] * c2 + a[12 + row] * c3;
189
+ }
190
+ }
191
+ return r;
192
+ }
193
+ function invert(m) {
194
+ const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
195
+ const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
196
+ const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
197
+ const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15];
198
+ const b00 = a00 * a11 - a01 * a10;
199
+ const b01 = a00 * a12 - a02 * a10;
200
+ const b02 = a00 * a13 - a03 * a10;
201
+ const b03 = a01 * a12 - a02 * a11;
202
+ const b04 = a01 * a13 - a03 * a11;
203
+ const b05 = a02 * a13 - a03 * a12;
204
+ const b06 = a20 * a31 - a21 * a30;
205
+ const b07 = a20 * a32 - a22 * a30;
206
+ const b08 = a20 * a33 - a23 * a30;
207
+ const b09 = a21 * a32 - a22 * a31;
208
+ const b10 = a21 * a33 - a23 * a31;
209
+ const b11 = a22 * a33 - a23 * a32;
210
+ const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
211
+ if (Math.abs(det) < 1e-12) return null;
212
+ const invDet = 1 / det;
213
+ const r = new Float64Array(16);
214
+ r[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
215
+ r[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
216
+ r[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;
217
+ r[3] = (a22 * b04 - a21 * b05 - a23 * b03) * invDet;
218
+ r[4] = (a12 * b08 - a10 * b11 - a13 * b07) * invDet;
219
+ r[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;
220
+ r[6] = (a32 * b02 - a30 * b05 - a33 * b01) * invDet;
221
+ r[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;
222
+ r[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;
223
+ r[9] = (a01 * b08 - a00 * b10 - a03 * b06) * invDet;
224
+ r[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;
225
+ r[11] = (a21 * b02 - a20 * b04 - a23 * b00) * invDet;
226
+ r[12] = (a11 * b07 - a10 * b09 - a12 * b06) * invDet;
227
+ r[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;
228
+ r[14] = (a31 * b01 - a30 * b03 - a32 * b00) * invDet;
229
+ r[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;
230
+ return r;
231
+ }
232
+ function transformPoint(m, v) {
233
+ const w = m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15];
234
+ return {
235
+ x: (m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12]) / w,
236
+ y: (m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13]) / w,
237
+ z: (m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14]) / w
238
+ };
239
+ }
240
+ function transformDirection(m, v) {
241
+ return {
242
+ x: m[0] * v.x + m[4] * v.y + m[8] * v.z,
243
+ y: m[1] * v.x + m[5] * v.y + m[9] * v.z,
244
+ z: m[2] * v.x + m[6] * v.y + m[10] * v.z
245
+ };
246
+ }
247
+ function transformPointRaw(m, v) {
248
+ return {
249
+ x: m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12],
250
+ y: m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13],
251
+ z: m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14],
252
+ w: m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15]
253
+ };
254
+ }
255
+
256
+ // src/camera.ts
257
+ var DEFAULT_UP = { x: 0, y: 1, z: 0 };
258
+ function createCamera(options) {
259
+ return {
260
+ position: options?.position ?? { x: 0, y: 1.7, z: 0 },
261
+ target: options?.target ?? { x: 0, y: 0.5, z: 100 },
262
+ up: options?.up ?? DEFAULT_UP,
263
+ fov: options?.fov ?? 60,
264
+ near: options?.near ?? 0.1,
265
+ far: options?.far ?? 1e4,
266
+ projection: options?.projection ?? "perspective",
267
+ orthoScale: options?.orthoScale
268
+ };
269
+ }
270
+ function updateCamera(camera, updates) {
271
+ return { ...camera, ...updates };
272
+ }
273
+ function viewProjectionMatrix(camera, viewport) {
274
+ const view = lookAt(camera.position, camera.target, camera.up);
275
+ const aspect = viewport.width / viewport.height;
276
+ let proj;
277
+ if (camera.projection === "orthographic") {
278
+ const scale2 = camera.orthoScale ?? 10;
279
+ const halfH = scale2 / 2;
280
+ const halfW = halfH * aspect;
281
+ proj = orthographicMatrix(-halfW, halfW, -halfH, halfH, camera.near, camera.far);
282
+ } else {
283
+ proj = perspectiveMatrix(camera.fov, aspect, camera.near, camera.far);
284
+ }
285
+ return multiply(proj, view);
286
+ }
287
+
288
+ // src/project.ts
289
+ function project(point, camera, viewport) {
290
+ const vp = viewProjectionMatrix(camera, viewport);
291
+ return projectWithMatrix(point, vp, camera, viewport);
292
+ }
293
+ function projectWithMatrix(point, vpMatrix, camera, viewport) {
294
+ const clip = transformPointRaw(vpMatrix, point);
295
+ if (clip.w <= 0) {
296
+ return {
297
+ x: 0,
298
+ y: 0,
299
+ depth: -1,
300
+ scale: 0,
301
+ visible: false,
302
+ distance: distance(point, camera.position)
303
+ };
304
+ }
305
+ const ndcX = clip.x / clip.w;
306
+ const ndcY = clip.y / clip.w;
307
+ const ndcZ = clip.z / clip.w;
308
+ const screenX = viewport.x + (ndcX + 1) / 2 * viewport.width;
309
+ const screenY = viewport.y + (1 - ndcY) / 2 * viewport.height;
310
+ const depth = (ndcZ + 1) / 2;
311
+ const visible = ndcX >= -1 && ndcX <= 1 && ndcY >= -1 && ndcY <= 1 && ndcZ >= -1 && ndcZ <= 1;
312
+ const fovRad = camera.fov * Math.PI / 180;
313
+ const projScale = camera.projection === "perspective" ? viewport.height / 2 * (1 / Math.tan(fovRad / 2)) / clip.w : viewport.height / (camera.orthoScale ?? 10);
314
+ return {
315
+ x: screenX,
316
+ y: screenY,
317
+ depth,
318
+ scale: projScale,
319
+ visible,
320
+ distance: distance(point, camera.position)
321
+ };
322
+ }
323
+ function projectMany(points, camera, viewport) {
324
+ const vp = viewProjectionMatrix(camera, viewport);
325
+ return points.map((p) => projectWithMatrix(p, vp, camera, viewport));
326
+ }
327
+ function unproject(screenX, screenY, depth, camera, viewport) {
328
+ const vp = viewProjectionMatrix(camera, viewport);
329
+ const inv = invert(vp);
330
+ if (!inv) return null;
331
+ const ndcX = (screenX - viewport.x) / viewport.width * 2 - 1;
332
+ const ndcY = 1 - (screenY - viewport.y) / viewport.height * 2;
333
+ const ndcZ = depth * 2 - 1;
334
+ const clip = transformPointRaw(inv, { x: ndcX, y: ndcY, z: ndcZ });
335
+ if (Math.abs(clip.w) < 1e-12) return null;
336
+ return {
337
+ x: clip.x / clip.w,
338
+ y: clip.y / clip.w,
339
+ z: clip.z / clip.w
340
+ };
341
+ }
342
+
343
+ // src/spatial.ts
344
+ function frustumContainsPoint(point, camera, viewport) {
345
+ const vp = viewProjectionMatrix(camera, viewport);
346
+ const clip = transformPointRaw(vp, point);
347
+ if (clip.w <= 0) return false;
348
+ const ndcX = clip.x / clip.w;
349
+ const ndcY = clip.y / clip.w;
350
+ const ndcZ = clip.z / clip.w;
351
+ return ndcX >= -1 && ndcX <= 1 && ndcY >= -1 && ndcY <= 1 && ndcZ >= -1 && ndcZ <= 1;
352
+ }
353
+ function frustumContainsBox(box, camera, viewport) {
354
+ const vp = viewProjectionMatrix(camera, viewport);
355
+ const corners = [
356
+ { x: box.min.x, y: box.min.y, z: box.min.z },
357
+ { x: box.max.x, y: box.min.y, z: box.min.z },
358
+ { x: box.min.x, y: box.max.y, z: box.min.z },
359
+ { x: box.max.x, y: box.max.y, z: box.min.z },
360
+ { x: box.min.x, y: box.min.y, z: box.max.z },
361
+ { x: box.max.x, y: box.min.y, z: box.max.z },
362
+ { x: box.min.x, y: box.max.y, z: box.max.z },
363
+ { x: box.max.x, y: box.max.y, z: box.max.z }
364
+ ];
365
+ let allInside = true;
366
+ let anyInside = false;
367
+ for (const corner of corners) {
368
+ const clip = transformPointRaw(vp, corner);
369
+ if (clip.w <= 0) {
370
+ allInside = false;
371
+ continue;
372
+ }
373
+ const ndcX = clip.x / clip.w;
374
+ const ndcY = clip.y / clip.w;
375
+ const ndcZ = clip.z / clip.w;
376
+ const inside = ndcX >= -1 && ndcX <= 1 && ndcY >= -1 && ndcY <= 1 && ndcZ >= -1 && ndcZ <= 1;
377
+ if (inside) {
378
+ anyInside = true;
379
+ } else {
380
+ allInside = false;
381
+ }
382
+ }
383
+ if (allInside) return "inside";
384
+ if (anyInside) return "intersect";
385
+ let minNdcX = Infinity, maxNdcX = -Infinity;
386
+ let minNdcY = Infinity, maxNdcY = -Infinity;
387
+ let behindCount = 0;
388
+ for (const corner of corners) {
389
+ const clip = transformPointRaw(vp, corner);
390
+ if (clip.w <= 0) {
391
+ behindCount++;
392
+ continue;
393
+ }
394
+ const ndcX = clip.x / clip.w;
395
+ const ndcY = clip.y / clip.w;
396
+ minNdcX = Math.min(minNdcX, ndcX);
397
+ maxNdcX = Math.max(maxNdcX, ndcX);
398
+ minNdcY = Math.min(minNdcY, ndcY);
399
+ maxNdcY = Math.max(maxNdcY, ndcY);
400
+ }
401
+ if (behindCount > 0 && behindCount < 8) return "intersect";
402
+ if (behindCount === 8) return "outside";
403
+ if (maxNdcX < -1 || minNdcX > 1 || maxNdcY < -1 || minNdcY > 1) return "outside";
404
+ return "intersect";
405
+ }
406
+ function isBackFace(faceNormal, faceCenter, camera) {
407
+ const toCamera = sub(camera.position, faceCenter);
408
+ return dot(faceNormal, toCamera) <= 0;
409
+ }
410
+ function scaleAtDepth(worldPoint, camera, viewport) {
411
+ const vp = viewProjectionMatrix(camera, viewport);
412
+ const clip = transformPointRaw(vp, worldPoint);
413
+ if (clip.w <= 0) return 0;
414
+ if (camera.projection === "perspective") {
415
+ const fovRad = camera.fov * Math.PI / 180;
416
+ return viewport.height / 2 * (1 / Math.tan(fovRad / 2)) / clip.w;
417
+ }
418
+ return viewport.height / (camera.orthoScale ?? 10);
419
+ }
420
+ function normalizedDepth(point, camera, viewport) {
421
+ const vp = viewProjectionMatrix(camera, viewport);
422
+ const clip = transformPointRaw(vp, point);
423
+ if (clip.w <= 0) return -1;
424
+ const ndcZ = clip.z / clip.w;
425
+ return (ndcZ + 1) / 2;
426
+ }
427
+ function distanceTo(point, camera) {
428
+ return distance(point, camera.position);
429
+ }
430
+ function horizonScreenY(camera, viewport) {
431
+ const lookDir = normalize(sub(camera.target, camera.position));
432
+ const horizDir = normalize({ x: lookDir.x, y: 0, z: lookDir.z });
433
+ if (horizDir.x === 0 && horizDir.z === 0) {
434
+ return viewport.y + viewport.height / 2;
435
+ }
436
+ const farPoint = {
437
+ x: camera.position.x + horizDir.x * camera.far * 0.99,
438
+ y: camera.position.y,
439
+ z: camera.position.z + horizDir.z * camera.far * 0.99
440
+ };
441
+ const vp = viewProjectionMatrix(camera, viewport);
442
+ const clip = transformPointRaw(vp, farPoint);
443
+ if (clip.w <= 0) return viewport.y + viewport.height / 2;
444
+ const ndcY = clip.y / clip.w;
445
+ return viewport.y + (1 - ndcY) / 2 * viewport.height;
446
+ }
447
+ function deriveVanishingPoints(camera, viewport) {
448
+ const vp = viewProjectionMatrix(camera, viewport);
449
+ const far = camera.far * 0.99;
450
+ const projectDir = (dir) => {
451
+ const point = {
452
+ x: camera.position.x + dir.x * far,
453
+ y: camera.position.y + dir.y * far,
454
+ z: camera.position.z + dir.z * far
455
+ };
456
+ const clip = transformPointRaw(vp, point);
457
+ if (clip.w <= 0) return void 0;
458
+ const ndcX = clip.x / clip.w;
459
+ const ndcY = clip.y / clip.w;
460
+ return {
461
+ x: viewport.x + (ndcX + 1) / 2 * viewport.width,
462
+ y: viewport.y + (1 - ndcY) / 2 * viewport.height
463
+ };
464
+ };
465
+ return {
466
+ left: projectDir({ x: 1, y: 0, z: 0 }),
467
+ right: projectDir({ x: -1, y: 0, z: 0 }),
468
+ vertical: projectDir({ x: 0, y: 1, z: 0 })
469
+ };
470
+ }
471
+
472
+ // src/sorting.ts
473
+ function depthSort(items, positionFn, camera) {
474
+ const camPos = camera.position;
475
+ return [...items].sort((a, b) => {
476
+ const da = distanceSq(positionFn(a), camPos);
477
+ const db = distanceSq(positionFn(b), camPos);
478
+ return db - da;
479
+ });
480
+ }
481
+ function depthSortIndices(positions, camera) {
482
+ const camPos = camera.position;
483
+ const indices = positions.map((_, i) => i);
484
+ indices.sort((a, b) => {
485
+ const pa = positions[a];
486
+ const pb = positions[b];
487
+ return distanceSq(pb, camPos) - distanceSq(pa, camPos);
488
+ });
489
+ return indices;
490
+ }
491
+
492
+ // src/presets.ts
493
+ function landscapeCamera(options) {
494
+ const eyeHeight = options?.eyeHeight ?? 1.7;
495
+ const lookDistance = options?.lookDistance ?? 100;
496
+ const lookElevation = options?.lookElevation ?? 0.3;
497
+ return createCamera({
498
+ position: { x: 0, y: eyeHeight, z: 0 },
499
+ target: { x: 0, y: lookElevation, z: lookDistance },
500
+ fov: options?.fov ?? 60
501
+ });
502
+ }
503
+ function aerialCamera(options) {
504
+ const altitude = options?.altitude ?? 200;
505
+ const angleDeg = options?.lookAngle ?? 45;
506
+ const angleRad = angleDeg * Math.PI / 180;
507
+ const lookDistance = altitude / Math.tan(angleRad);
508
+ return createCamera({
509
+ position: { x: 0, y: altitude, z: 0 },
510
+ target: { x: 0, y: 0, z: lookDistance },
511
+ fov: options?.fov ?? 50
512
+ });
513
+ }
514
+ function streetCamera(options) {
515
+ const eyeHeight = options?.eyeHeight ?? 1.7;
516
+ const angleDeg = options?.lookDirection ?? 0;
517
+ const angleRad = angleDeg * Math.PI / 180;
518
+ const lookDist = 50;
519
+ return createCamera({
520
+ position: { x: 0, y: eyeHeight, z: 0 },
521
+ target: {
522
+ x: Math.sin(angleRad) * lookDist,
523
+ y: eyeHeight * 0.8,
524
+ z: Math.cos(angleRad) * lookDist
525
+ },
526
+ fov: options?.fov ?? 55
527
+ });
528
+ }
529
+ function wormEyeCamera(options) {
530
+ const groundLevel = options?.groundLevel ?? 0.3;
531
+ const tiltDeg = options?.tiltAngle ?? 60;
532
+ const tiltRad = tiltDeg * Math.PI / 180;
533
+ const lookDist = 30;
534
+ return createCamera({
535
+ position: { x: 0, y: groundLevel, z: 0 },
536
+ target: {
537
+ x: 0,
538
+ y: groundLevel + Math.sin(tiltRad) * lookDist,
539
+ z: Math.cos(tiltRad) * lookDist
540
+ },
541
+ fov: options?.fov ?? 75
542
+ });
543
+ }
544
+ function isometricCamera(options) {
545
+ const angleDeg = options?.angle ?? 30;
546
+ const angleRad = angleDeg * Math.PI / 180;
547
+ const dist = options?.distance ?? 200;
548
+ return createCamera({
549
+ position: {
550
+ x: -Math.cos(angleRad) * dist,
551
+ y: Math.sin(angleRad) * dist + dist * 0.5,
552
+ z: -Math.cos(angleRad) * dist
553
+ },
554
+ target: { x: 0, y: 0, z: 0 },
555
+ projection: "orthographic",
556
+ orthoScale: options?.orthoScale ?? 100
557
+ });
558
+ }
559
+
560
+ // src/interpolation.ts
561
+ function lerpCamera(a, b, t) {
562
+ return {
563
+ position: lerp(a.position, b.position, t),
564
+ target: lerp(a.target, b.target, t),
565
+ up: normalize(lerp(a.up, b.up, t)),
566
+ fov: a.fov + (b.fov - a.fov) * t,
567
+ near: a.near + (b.near - a.near) * t,
568
+ far: a.far + (b.far - a.far) * t,
569
+ projection: t < 0.5 ? a.projection : b.projection,
570
+ orthoScale: a.orthoScale != null && b.orthoScale != null ? a.orthoScale + (b.orthoScale - a.orthoScale) * t : t < 0.5 ? a.orthoScale : b.orthoScale
571
+ };
572
+ }
573
+ function orbitCamera(camera, angleX, angleY, center) {
574
+ const pivot = center ?? camera.target;
575
+ const offset = sub(camera.position, pivot);
576
+ const cosX = Math.cos(angleX);
577
+ const sinX = Math.sin(angleX);
578
+ let rotated = {
579
+ x: offset.x * cosX + offset.z * sinX,
580
+ y: offset.y,
581
+ z: -offset.x * sinX + offset.z * cosX
582
+ };
583
+ const forward = normalize(sub(pivot, add(pivot, rotated)));
584
+ const right = normalize(cross(camera.up, forward));
585
+ const cosY = Math.cos(angleY);
586
+ const sinY = Math.sin(angleY);
587
+ const dotRR = right.x * rotated.x + right.y * rotated.y + right.z * rotated.z;
588
+ const crossRR = cross(right, rotated);
589
+ rotated = {
590
+ x: rotated.x * cosY + crossRR.x * sinY + right.x * dotRR * (1 - cosY),
591
+ y: rotated.y * cosY + crossRR.y * sinY + right.y * dotRR * (1 - cosY),
592
+ z: rotated.z * cosY + crossRR.z * sinY + right.z * dotRR * (1 - cosY)
593
+ };
594
+ return {
595
+ ...camera,
596
+ position: add(pivot, rotated)
597
+ };
598
+ }
599
+ function dollyCamera(camera, distance2) {
600
+ const dir = normalize(sub(camera.target, camera.position));
601
+ const move = scale(dir, distance2);
602
+ return {
603
+ ...camera,
604
+ position: add(camera.position, move),
605
+ target: add(camera.target, move)
606
+ };
607
+ }
608
+ function panCamera(camera, dx, dy) {
609
+ const forward = normalize(sub(camera.target, camera.position));
610
+ const right = normalize(cross(camera.up, forward));
611
+ const up = cross(forward, right);
612
+ const offset = add(scale(right, dx), scale(up, dy));
613
+ return {
614
+ ...camera,
615
+ position: add(camera.position, offset),
616
+ target: add(camera.target, offset)
617
+ };
618
+ }
619
+ // Annotate the CommonJS export names for ESM import in node:
620
+ 0 && (module.exports = {
621
+ add,
622
+ aerialCamera,
623
+ createCamera,
624
+ cross,
625
+ depthSort,
626
+ depthSortIndices,
627
+ deriveVanishingPoints,
628
+ distance,
629
+ distanceSq,
630
+ distanceTo,
631
+ dollyCamera,
632
+ dot,
633
+ frustumContainsBox,
634
+ frustumContainsPoint,
635
+ horizonScreenY,
636
+ identity,
637
+ invert,
638
+ isBackFace,
639
+ isometricCamera,
640
+ landscapeCamera,
641
+ length,
642
+ lengthSq,
643
+ lerpCamera,
644
+ lerpVec3,
645
+ lookAt,
646
+ multiply,
647
+ negate,
648
+ normalize,
649
+ normalizedDepth,
650
+ orbitCamera,
651
+ orthographicMatrix,
652
+ panCamera,
653
+ perspectiveMatrix,
654
+ project,
655
+ projectMany,
656
+ projectWithMatrix,
657
+ scaleAtDepth,
658
+ scaleVec3,
659
+ streetCamera,
660
+ sub,
661
+ transformDirection,
662
+ transformPoint,
663
+ transformPointRaw,
664
+ unproject,
665
+ updateCamera,
666
+ vec3,
667
+ viewProjectionMatrix,
668
+ wormEyeCamera
669
+ });
670
+ //# sourceMappingURL=index.cjs.map