@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.
@@ -0,0 +1,301 @@
1
+ /** 2D vector. */
2
+ interface Vec2 {
3
+ readonly x: number;
4
+ readonly y: number;
5
+ }
6
+ /** 3D vector. Y-up, right-handed coordinate system. */
7
+ interface Vec3 {
8
+ readonly x: number;
9
+ readonly y: number;
10
+ readonly z: number;
11
+ }
12
+ /**
13
+ * 4x4 matrix stored as a 16-element Float64Array in column-major order.
14
+ *
15
+ * Column-major layout (OpenGL convention):
16
+ * ```
17
+ * | m[0] m[4] m[8] m[12] |
18
+ * | m[1] m[5] m[9] m[13] |
19
+ * | m[2] m[6] m[10] m[14] |
20
+ * | m[3] m[7] m[11] m[15] |
21
+ * ```
22
+ */
23
+ type Mat4 = Float64Array;
24
+ /** Camera definition. Perspective or orthographic projection. */
25
+ interface Camera {
26
+ /** Camera position in world space. */
27
+ readonly position: Vec3;
28
+ /** Look-at target in world space. */
29
+ readonly target: Vec3;
30
+ /** Up vector. Default (0, 1, 0). */
31
+ readonly up: Vec3;
32
+ /** Vertical field of view in degrees (perspective mode). */
33
+ readonly fov: number;
34
+ /** Near clip distance. */
35
+ readonly near: number;
36
+ /** Far clip distance. */
37
+ readonly far: number;
38
+ /** Projection type. */
39
+ readonly projection: "perspective" | "orthographic";
40
+ /** World units visible vertically (orthographic mode only). */
41
+ readonly orthoScale?: number;
42
+ }
43
+ /** Screen viewport rectangle. */
44
+ interface Viewport {
45
+ readonly x: number;
46
+ readonly y: number;
47
+ readonly width: number;
48
+ readonly height: number;
49
+ }
50
+ /** Result of projecting a 3D point to screen space. */
51
+ interface ProjectedPoint {
52
+ /** Screen X coordinate. */
53
+ readonly x: number;
54
+ /** Screen Y coordinate. */
55
+ readonly y: number;
56
+ /** Normalized depth: 0 (near) to 1 (far). For sorting and atmosphere. */
57
+ readonly depth: number;
58
+ /** World-to-screen scale factor at this depth. */
59
+ readonly scale: number;
60
+ /** Whether the point is within the camera frustum. */
61
+ readonly visible: boolean;
62
+ /** World-unit distance from camera position. */
63
+ readonly distance: number;
64
+ }
65
+ /** Axis-aligned bounding box in 3D. */
66
+ interface BoundingBox3D {
67
+ readonly min: Vec3;
68
+ readonly max: Vec3;
69
+ }
70
+
71
+ /** Create a Vec3. */
72
+ declare function vec3(x: number, y: number, z: number): Vec3;
73
+ /** Add two vectors. */
74
+ declare function add(a: Vec3, b: Vec3): Vec3;
75
+ /** Subtract b from a. */
76
+ declare function sub(a: Vec3, b: Vec3): Vec3;
77
+ /** Scale a vector by a scalar. */
78
+ declare function scale(v: Vec3, s: number): Vec3;
79
+ /** Dot product. */
80
+ declare function dot(a: Vec3, b: Vec3): number;
81
+ /** Cross product. */
82
+ declare function cross(a: Vec3, b: Vec3): Vec3;
83
+ /** Vector length. */
84
+ declare function length(v: Vec3): number;
85
+ /** Squared length (avoids sqrt). */
86
+ declare function lengthSq(v: Vec3): number;
87
+ /** Normalize to unit length. Returns zero vector if input is zero length. */
88
+ declare function normalize(v: Vec3): Vec3;
89
+ /** Negate a vector. */
90
+ declare function negate(v: Vec3): Vec3;
91
+ /** Linear interpolation between two vectors. */
92
+ declare function lerp(a: Vec3, b: Vec3, t: number): Vec3;
93
+ /** Distance between two points. */
94
+ declare function distance(a: Vec3, b: Vec3): number;
95
+ /** Squared distance (avoids sqrt). */
96
+ declare function distanceSq(a: Vec3, b: Vec3): number;
97
+
98
+ /** Create an identity matrix. */
99
+ declare function identity(): Mat4;
100
+ /**
101
+ * Build a view matrix (world-to-camera transform).
102
+ * Uses right-handed convention: camera looks along -Z in view space.
103
+ */
104
+ declare function lookAt(eye: Vec3, target: Vec3, up: Vec3): Mat4;
105
+ /** Build a perspective projection matrix. */
106
+ declare function perspectiveMatrix(fovDegrees: number, aspect: number, near: number, far: number): Mat4;
107
+ /** Build an orthographic projection matrix. */
108
+ declare function orthographicMatrix(left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4;
109
+ /** Multiply two 4x4 matrices: result = a * b. */
110
+ declare function multiply(a: Mat4, b: Mat4): Mat4;
111
+ /**
112
+ * Invert a 4x4 matrix. Returns null if the matrix is singular.
113
+ * Uses cofactor expansion.
114
+ */
115
+ declare function invert(m: Mat4): Mat4 | null;
116
+ /** Transform a Vec3 by a Mat4 as a point (w=1). Returns the projected Vec3 after w-divide. */
117
+ declare function transformPoint(m: Mat4, v: Vec3): Vec3;
118
+ /** Transform a Vec3 by a Mat4 as a direction (w=0). No w-divide. */
119
+ declare function transformDirection(m: Mat4, v: Vec3): Vec3;
120
+ /**
121
+ * Transform a Vec3 by a Mat4 and return the raw clip-space coordinates
122
+ * (x, y, z, w) before w-divide. Used by projection.
123
+ */
124
+ declare function transformPointRaw(m: Mat4, v: Vec3): {
125
+ x: number;
126
+ y: number;
127
+ z: number;
128
+ w: number;
129
+ };
130
+
131
+ /** Create a camera with sensible defaults. */
132
+ declare function createCamera(options?: Partial<Camera>): Camera;
133
+ /** Create a new camera with updated properties. */
134
+ declare function updateCamera(camera: Camera, updates: Partial<Camera>): Camera;
135
+ /** Compute the combined view-projection matrix for a camera and viewport. */
136
+ declare function viewProjectionMatrix(camera: Camera, viewport: Viewport): Mat4;
137
+
138
+ /**
139
+ * Project a world-space point to screen coordinates.
140
+ *
141
+ * Screen coordinates: (0,0) = top-left of viewport, (width, height) = bottom-right.
142
+ * Depth: 0 = near plane, 1 = far plane.
143
+ */
144
+ declare function project(point: Vec3, camera: Camera, viewport: Viewport): ProjectedPoint;
145
+ /**
146
+ * Project a world-space point using a precomputed view-projection matrix.
147
+ * Use this when projecting many points with the same camera to avoid
148
+ * recomputing the matrix for each point.
149
+ */
150
+ declare function projectWithMatrix(point: Vec3, vpMatrix: Mat4, camera: Camera, viewport: Viewport): ProjectedPoint;
151
+ /**
152
+ * Project many points efficiently using a single precomputed matrix.
153
+ */
154
+ declare function projectMany(points: Vec3[], camera: Camera, viewport: Viewport): ProjectedPoint[];
155
+ /**
156
+ * Unproject a screen point back to world space.
157
+ *
158
+ * @param screenX - Screen X coordinate
159
+ * @param screenY - Screen Y coordinate
160
+ * @param depth - Normalized depth 0 (near) to 1 (far)
161
+ * @param camera - Camera
162
+ * @param viewport - Viewport
163
+ * @returns World-space position, or null if the matrix is singular
164
+ */
165
+ declare function unproject(screenX: number, screenY: number, depth: number, camera: Camera, viewport: Viewport): Vec3 | null;
166
+
167
+ /**
168
+ * Test if a point is inside the camera frustum (after projection, inside NDC cube).
169
+ */
170
+ declare function frustumContainsPoint(point: Vec3, camera: Camera, viewport: Viewport): boolean;
171
+ /**
172
+ * Test a bounding box against the camera frustum.
173
+ * Returns 'inside' if fully contained, 'intersect' if partially visible, 'outside' if not visible.
174
+ *
175
+ * Uses a conservative test: projects all 8 corners and checks NDC bounds.
176
+ */
177
+ declare function frustumContainsBox(box: BoundingBox3D, camera: Camera, viewport: Viewport): "inside" | "intersect" | "outside";
178
+ /**
179
+ * Test if a face is a back face (facing away from the camera).
180
+ * Uses the dot product between the face normal and the camera-to-face direction.
181
+ */
182
+ declare function isBackFace(faceNormal: Vec3, faceCenter: Vec3, camera: Camera): boolean;
183
+ /**
184
+ * Get the world-to-screen scale factor at a given world point.
185
+ * Returns how many screen pixels correspond to 1 world unit at that depth.
186
+ */
187
+ declare function scaleAtDepth(worldPoint: Vec3, camera: Camera, viewport: Viewport): number;
188
+ /**
189
+ * Get the normalized depth (0 = near plane, 1 = far plane) of a world point.
190
+ */
191
+ declare function normalizedDepth(point: Vec3, camera: Camera, viewport: Viewport): number;
192
+ /**
193
+ * Get the world-unit distance from the camera to a point.
194
+ */
195
+ declare function distanceTo(point: Vec3, camera: Camera): number;
196
+ /**
197
+ * Compute the screen Y coordinate of the horizon line.
198
+ * The horizon is where the camera's look direction intersects Y = camera.position.y
199
+ * at infinite distance, projected to screen space.
200
+ */
201
+ declare function horizonScreenY(camera: Camera, viewport: Viewport): number;
202
+ /**
203
+ * Derive vanishing point screen positions from a camera.
204
+ *
205
+ * - `left`: VP for lines parallel to the world X axis (projects to screen position)
206
+ * - `right`: VP for lines parallel to the world -X axis (same point, opposite direction)
207
+ * - `vertical`: VP for lines parallel to the world Y axis (only meaningful for 3-point perspective)
208
+ *
209
+ * Returns undefined for a VP direction if it projects behind the camera.
210
+ */
211
+ declare function deriveVanishingPoints(camera: Camera, viewport: Viewport): {
212
+ left?: Vec2;
213
+ right?: Vec2;
214
+ vertical?: Vec2;
215
+ };
216
+
217
+ /**
218
+ * Sort items by depth (back-to-front by default: farthest first).
219
+ * Uses squared distance to avoid sqrt per item.
220
+ */
221
+ declare function depthSort<T>(items: T[], positionFn: (item: T) => Vec3, camera: Camera): T[];
222
+ /**
223
+ * Return indices sorted by depth (back-to-front: farthest first).
224
+ */
225
+ declare function depthSortIndices(positions: Vec3[], camera: Camera): number[];
226
+
227
+ /**
228
+ * Landscape camera — standing eye-height, looking toward distant horizon.
229
+ * Default: 1.7m eye height, 60° FOV, looking slightly above horizon.
230
+ */
231
+ declare function landscapeCamera(options?: {
232
+ eyeHeight?: number;
233
+ lookDistance?: number;
234
+ lookElevation?: number;
235
+ fov?: number;
236
+ }): Camera;
237
+ /**
238
+ * Aerial camera — elevated position looking down at an angle.
239
+ * Default: 200m altitude, 45° down, 50° FOV.
240
+ */
241
+ declare function aerialCamera(options?: {
242
+ altitude?: number;
243
+ lookAngle?: number;
244
+ fov?: number;
245
+ }): Camera;
246
+ /**
247
+ * Street-level camera — eye-height, optionally rotated horizontally.
248
+ * Default: 1.7m, straight ahead, 55° FOV (natural perspective).
249
+ */
250
+ declare function streetCamera(options?: {
251
+ eyeHeight?: number;
252
+ lookDirection?: number;
253
+ fov?: number;
254
+ }): Camera;
255
+ /**
256
+ * Worm's-eye camera — low position looking upward.
257
+ * Default: 0.3m height, 60° tilt upward, 75° FOV (dramatic).
258
+ */
259
+ declare function wormEyeCamera(options?: {
260
+ groundLevel?: number;
261
+ tiltAngle?: number;
262
+ fov?: number;
263
+ }): Camera;
264
+ /**
265
+ * Isometric camera — orthographic projection at a fixed angle.
266
+ * Default: 30° angle, 100 units visible vertically.
267
+ */
268
+ declare function isometricCamera(options?: {
269
+ angle?: number;
270
+ distance?: number;
271
+ orthoScale?: number;
272
+ }): Camera;
273
+
274
+ /** Linear interpolation between two cameras. */
275
+ declare function lerpCamera(a: Camera, b: Camera, t: number): Camera;
276
+ /**
277
+ * Orbit the camera around a center point.
278
+ *
279
+ * @param camera - Current camera
280
+ * @param angleX - Horizontal rotation in radians (positive = rotate right)
281
+ * @param angleY - Vertical rotation in radians (positive = rotate up)
282
+ * @param center - Orbit center (defaults to camera target)
283
+ */
284
+ declare function orbitCamera(camera: Camera, angleX: number, angleY: number, center?: Vec3): Camera;
285
+ /**
286
+ * Dolly the camera forward/backward along its look direction.
287
+ *
288
+ * @param camera - Current camera
289
+ * @param distance - Positive = move toward target, negative = move away
290
+ */
291
+ declare function dollyCamera(camera: Camera, distance: number): Camera;
292
+ /**
293
+ * Pan the camera (shift left/right/up/down) without changing the look direction.
294
+ *
295
+ * @param camera - Current camera
296
+ * @param dx - World units to move right (negative = left)
297
+ * @param dy - World units to move up (negative = down)
298
+ */
299
+ declare function panCamera(camera: Camera, dx: number, dy: number): Camera;
300
+
301
+ export { type BoundingBox3D, type Camera, type Mat4, type ProjectedPoint, type Vec2, type Vec3, type Viewport, add, aerialCamera, createCamera, cross, depthSort, depthSortIndices, deriveVanishingPoints, distance, distanceSq, distanceTo, dollyCamera, dot, frustumContainsBox, frustumContainsPoint, horizonScreenY, identity, invert, isBackFace, isometricCamera, landscapeCamera, length, lengthSq, lerpCamera, lerp as lerpVec3, lookAt, multiply, negate, normalize, normalizedDepth, orbitCamera, orthographicMatrix, panCamera, perspectiveMatrix, project, projectMany, projectWithMatrix, scaleAtDepth, scale as scaleVec3, streetCamera, sub, transformDirection, transformPoint, transformPointRaw, unproject, updateCamera, vec3, viewProjectionMatrix, wormEyeCamera };