trid3d 0.1.0 → 0.2.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,988 @@
1
+ /**
2
+ * Type definitions for Trid (trid3d).
3
+ *
4
+ * Hand-written to match trid/index.js. When a public function or
5
+ * option changes in the JavaScript, update it here too.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Basic types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface Vec2 {
13
+ x: number;
14
+ y: number;
15
+ }
16
+
17
+ export interface Vec3 {
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ }
22
+
23
+ export interface UV {
24
+ u: number;
25
+ v: number;
26
+ }
27
+
28
+ /** [r, g, b, a]. Range depends on where it comes from (see each function). */
29
+ export type RGBA = [number, number, number, number];
30
+
31
+ /**
32
+ * Camera. Every field is optional and falls back to its default.
33
+ * The camera looks down +Z when yaw and pitch are 0.
34
+ */
35
+ export interface Camera {
36
+ /** Position on X (default 0) */
37
+ x?: number;
38
+ /** Position on Y (default 0) */
39
+ y?: number;
40
+ /** Position on Z (default 0) */
41
+ z?: number;
42
+ /** Turn around Y in radians, > 0 turns right (default 0) */
43
+ yaw?: number;
44
+ /** Tilt around X in radians, > 0 looks up (default 0) */
45
+ pitch?: number;
46
+ /** Vertical field of view in radians (default Math.PI / 2) */
47
+ fov?: number;
48
+ /** Near plane distance; closer geometry is clipped (default 0.01) */
49
+ near?: number;
50
+ /** Far distance; farther geometry isn't drawn. WebGL renderer only (default 1000) */
51
+ far?: number;
52
+ }
53
+
54
+ /**
55
+ * A mesh: vertices plus the lines (`edges`) and/or polygons (`faces`) between them.
56
+ *
57
+ * An edge with 2 indices is a single segment; with 3 or more it's a closed loop.
58
+ * Faces are clockwise as seen from their front.
59
+ * Without `edges`, an object with no material draws the outline of each face.
60
+ */
61
+ export interface Mesh {
62
+ vertices: Vec3[];
63
+ edges?: number[][];
64
+ faces?: number[][];
65
+ /** Per-corner normals, used by smooth shading */
66
+ normals?: Vec3[];
67
+ /** For each face, the index into `normals` of each corner (null: none for that face) */
68
+ faceNormals?: (number[] | null)[];
69
+ /** Texture coordinates */
70
+ uvs?: UV[];
71
+ /** For each face, the index into `uvs` of each corner (null: none for that face) */
72
+ faceUVs?: (number[] | null)[];
73
+ /**
74
+ * For each face, which entry of the object's material list it uses
75
+ * (when `material` is a list). Built-in cubes and boxes number their sides 0 to 5.
76
+ */
77
+ faceMaterials?: number[];
78
+ /** Set after changing the mesh in place so renderers rebuild their caches */
79
+ dirty?: boolean;
80
+ }
81
+
82
+ /** Transform of a mesh instance. */
83
+ export interface Transform {
84
+ pos: Vec3;
85
+ rot: Vec3;
86
+ scale: Vec3;
87
+ }
88
+
89
+ /** Anything a texture can be: an image, canvas, video or bitmap. */
90
+ export type TextureSource = CanvasImageSource;
91
+
92
+ /** How an object's faces look. Objects without one are drawn as wireframes. */
93
+ export interface Material {
94
+ /** Face color, any CSS color (default: the scene's color, or white with a texture) */
95
+ fill?: string;
96
+ /** Darken faces that turn away from the light (default true) */
97
+ shade?: boolean;
98
+ /** Also draw the face outlines, with the object's `color` and `lineWidth` (default false) */
99
+ wireframe?: boolean;
100
+ /** Also draw faces seen from behind (default false) */
101
+ doubleSided?: boolean;
102
+ /**
103
+ * Blend the light across faces so curved shapes look round.
104
+ * `true` keeps edges sharper than 60° hard; a number sets that angle.
105
+ */
106
+ smooth?: boolean | number;
107
+ /** Image drawn on the faces (needs texture coordinates). `fill` tints it. */
108
+ texture?: TextureSource;
109
+ /** Keep texture pixels sharp instead of smoothing them (default false) */
110
+ pixelated?: boolean;
111
+ /** Effects applied in order, like `[toon(), rim()]` */
112
+ effects?: Effect[];
113
+ /** Where texture coordinates come from: the mesh's own ("uv", default) or made from its shape */
114
+ mapping?: Mapping;
115
+ /** How many times the texture repeats across (default 1) */
116
+ textureRepeat?: number | UV;
117
+ /** Moves the texture; 1 is its whole width or height */
118
+ textureOffset?: UV;
119
+ /** Turns the texture around its center, in radians */
120
+ textureRotation?: number;
121
+ }
122
+
123
+ /** Ways to make texture coordinates from a mesh's shape. */
124
+ export type MappingType = "uv" | "planar" | "box" | "sphere" | "cylinder";
125
+
126
+ /**
127
+ * A texture mapping: a type, or a type with an axis. `axis` is the projection
128
+ * direction for "planar" (default "z") and the wrap axis for "sphere" and "cylinder" (default "y").
129
+ */
130
+ export type Mapping = MappingType | { type: MappingType; axis?: "x" | "y" | "z" };
131
+
132
+ /** What you pass to `scene.add`. Missing transform fields get defaults. */
133
+ export interface SceneObjectInput {
134
+ mesh: Mesh;
135
+ pos?: Vec3;
136
+ rot?: Vec3;
137
+ scale?: Vec3;
138
+ /** Wireframe color (default: the scene's color) */
139
+ color?: string;
140
+ /** Wireframe width (default: the scene's lineWidth) */
141
+ lineWidth?: number;
142
+ /** Draw faces instead of only lines. A list gives each group of faces its own (see `Mesh.faceMaterials`) */
143
+ material?: Material | Material[];
144
+ /** Set to false to make `DragObject` ignore this object */
145
+ draggable?: boolean;
146
+ /** Set to false to make `HoverHighlight` ignore this object */
147
+ hoverable?: boolean;
148
+ }
149
+
150
+ /**
151
+ * An object in a scene, after `scene.add` filled in its transform.
152
+ * Extra fields you add (like `{ mesh, name: "box" }`) are kept in the type.
153
+ */
154
+ export type SceneObject<T extends SceneObjectInput = SceneObjectInput> = T & Transform;
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Scene
158
+ // ---------------------------------------------------------------------------
159
+
160
+ /** Anything that can draw a scene. */
161
+ export interface Renderer {
162
+ render(scene: Scene): void;
163
+ dispose?(): void;
164
+ }
165
+
166
+ export interface SceneOptions {
167
+ /** Sets canvas.width (keeps the current one if omitted) */
168
+ width?: number;
169
+ /** Sets canvas.height (keeps the current one if omitted) */
170
+ height?: number;
171
+ /** Clear color (default "#141414") */
172
+ background?: string;
173
+ /** Default wireframe color (default "#ffffff") */
174
+ color?: string;
175
+ /** Default wireframe width (default 3) */
176
+ lineWidth?: number;
177
+ /** Camera (default { z: -1 }) */
178
+ camera?: Camera;
179
+ /** Direction light comes from (default: above, left and in front) */
180
+ light?: Vec3;
181
+ /** Brightness of faces turned away from the light, 0..1 (default 0.25) */
182
+ ambient?: number;
183
+ /** What draws the scene (default: a new CanvasRenderer) */
184
+ renderer?: Renderer;
185
+ }
186
+
187
+ /** Holds a canvas, a camera and a list of objects, and draws them. */
188
+ export class Scene {
189
+ constructor(canvas: HTMLCanvasElement, opts?: SceneOptions);
190
+
191
+ canvas: HTMLCanvasElement;
192
+ ctx: CanvasRenderingContext2D;
193
+ background: string;
194
+ color: string;
195
+ lineWidth: number;
196
+ camera: Camera;
197
+ light: Vec3;
198
+ ambient: number;
199
+ renderer: Renderer;
200
+ objects: SceneObject[];
201
+ tools: ToolManager;
202
+
203
+ /** Adds an object, filling in missing transform fields. Returns the same object. */
204
+ add<T extends SceneObjectInput>(obj: T): SceneObject<T>;
205
+ /** Removes an object. Does nothing if it isn't in the scene. */
206
+ remove(obj: SceneObjectInput): void;
207
+ /** Adds a tool. Returns the same tool. */
208
+ use<T extends Tool>(tool: T): T;
209
+ /** Removes a tool. Does nothing if it isn't in the scene. */
210
+ unuse(tool: Tool): void;
211
+ /** Draws every object once, then lets tools draw on top. */
212
+ render(): void;
213
+ /** Starts the frame loop (restarts it if already running). */
214
+ start(update?: (dt: number, time: number, scene: Scene) => void): this;
215
+ /** Stops the frame loop. The last frame stays on the canvas. */
216
+ stop(): void;
217
+ /** Draws a new frame and returns it as an image file. The page's canvas doesn't change. */
218
+ screenshot(opts?: ScreenshotOptions): Promise<Blob>;
219
+ /** Starts recording the canvas as a video. */
220
+ record(opts?: RecordOptions): Recording;
221
+ /** Stops the loop, removes every tool and frees the renderer's resources. */
222
+ destroy(): void;
223
+ /** Whether the frame loop is running. */
224
+ readonly running: boolean;
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Shapes
229
+ // ---------------------------------------------------------------------------
230
+
231
+ export interface ShapeOptions {
232
+ /** Cube size, or plane width (default 0.5 cube, 1 plane) */
233
+ size?: number;
234
+ /** Box width (default 0.6) */
235
+ w?: number;
236
+ /** Box height (default 0.6), or cylinder / cone height (default 0.8) */
237
+ h?: number;
238
+ /** Box depth (default 0.6) */
239
+ d?: number;
240
+ /** Radius: sphere (0.45), cylinder (0.3), cone base (0.35) */
241
+ r?: number;
242
+ /** Sphere latitude bands (default 12) */
243
+ rings?: number;
244
+ /** Points around: sphere (24), cylinder / cone (32), torus (48) */
245
+ segments?: number;
246
+ /** Cone top radius: 0 = pointed, > 0 = tapered cylinder (default 0) */
247
+ rTop?: number;
248
+ /** Torus: distance from the center to the middle of the tube (default 0.35) */
249
+ R?: number;
250
+ /** Torus tube radius (default 0.12) */
251
+ tube?: number;
252
+ /** Torus points around the tube (default 20) */
253
+ tubeSegments?: number;
254
+ /** Plane cells per side (default 8) */
255
+ div?: number;
256
+ /** Plane: lifts each vertex, for terrain */
257
+ height?: (x: number, z: number) => number;
258
+ }
259
+
260
+ export type ShapeKind = "cube" | "box" | "sphere" | "cylinder" | "cone" | "torus" | "plane";
261
+
262
+ /**
263
+ * Creates a built-in mesh (with edges, faces and texture coordinates), centered
264
+ * on the origin. Cylinders, cones and tori stand along Y; the plane lies in XZ facing +Y.
265
+ */
266
+ export function makeShape(kind: ShapeKind, opts?: ShapeOptions): Mesh;
267
+
268
+ /** A point of a `lathe` profile: distance from the Y axis and height. */
269
+ export interface ProfilePoint {
270
+ r: number;
271
+ y: number;
272
+ }
273
+
274
+ export interface LatheOptions {
275
+ /** Points around the axis (default 32) */
276
+ segments?: number;
277
+ /** Close the bottom with a flat cap (default false) */
278
+ capBottom?: boolean;
279
+ /** Close the top with a flat cap (default false) */
280
+ capTop?: boolean;
281
+ /** The profile's last point joins its first, for torus-like shapes (default false) */
282
+ closed?: boolean;
283
+ }
284
+
285
+ /**
286
+ * Spins a profile around the Y axis (vases, bottles, columns, chess pieces).
287
+ * Walk the profile so the outside is on its right: bottom to top for a vase.
288
+ */
289
+ export function lathe(profile: ProfilePoint[], opts?: LatheOptions): Mesh;
290
+
291
+ /** A 2D point (x right, y up). */
292
+ export interface Point2 {
293
+ x: number;
294
+ y: number;
295
+ }
296
+
297
+ /**
298
+ * Turns a flat outline in the XY plane into a solid `depth` thick along Z
299
+ * (default 0.2), centered on the origin. Either direction, concave is fine, no holes.
300
+ */
301
+ export function extrude(outline: Point2[], depth?: number): Mesh;
302
+
303
+ /** Cuts a counter-clockwise polygon into counter-clockwise triangles (ear clipping). */
304
+ export function triangulate(pts: Point2[]): number[][];
305
+
306
+ /** Scales a mesh's vertices in place. Returns the same mesh. */
307
+ export function scaleMesh<M extends { vertices: Vec3[] }>(mesh: M, sx?: number, sy?: number, sz?: number): M;
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // Renderers
311
+ // ---------------------------------------------------------------------------
312
+
313
+ /** Direction light comes from when the scene doesn't set one. */
314
+ export const DEFAULT_LIGHT: Readonly<Vec3>;
315
+ /** How bright a face turned fully away from the light still is (0..1). */
316
+ export const DEFAULT_AMBIENT: number;
317
+ /** Default far distance (WebGL renderer). */
318
+ export const DEFAULT_FAR: number;
319
+
320
+ /** An object's materials as a list, or null when it's a wireframe. */
321
+ export function materialsOf(obj: { material?: Material | Material[] }): Material[] | null;
322
+
323
+ export interface CanvasRendererOptions {
324
+ /**
325
+ * Split faces bigger than this fraction of their mesh's size (like 0.05)
326
+ * into smaller triangles, so they sort better. Off by default (null).
327
+ */
328
+ maxFaceSize?: number | null;
329
+ }
330
+
331
+ /** Draws a scene with the canvas 2D context (painter's algorithm). */
332
+ export class CanvasRenderer implements Renderer {
333
+ constructor(opts?: CanvasRendererOptions);
334
+ maxFaceSize: number | null;
335
+ render(scene: Scene): void;
336
+ }
337
+
338
+ export interface WebGLRendererOptions {
339
+ /** Smooth edges (default true) */
340
+ antialias?: boolean;
341
+ }
342
+
343
+ /** Draws a scene with WebGL and a depth buffer. */
344
+ export class WebGLRenderer implements Renderer {
345
+ /** Whether this browser can create a WebGL context. */
346
+ static isSupported(): boolean;
347
+ /** @throws If WebGL isn't available */
348
+ constructor(opts?: WebGLRendererOptions);
349
+ /** The hidden canvas WebGL draws on. */
350
+ canvas: HTMLCanvasElement;
351
+ gl: WebGLRenderingContext | WebGL2RenderingContext;
352
+ render(scene: Scene): void;
353
+ /** Frees the graphics card memory. The renderer can't be used afterwards. */
354
+ dispose(): void;
355
+ }
356
+
357
+ // ---------------------------------------------------------------------------
358
+ // Effects
359
+ // ---------------------------------------------------------------------------
360
+
361
+ /** Values an effect uniform can hold. */
362
+ export type UniformValue = number | boolean | string | number[] | Vec3;
363
+
364
+ /**
365
+ * What an effect knows about the point being drawn.
366
+ * Colors are [r, g, b, a] from 0 to 1.
367
+ */
368
+ export interface Surface {
369
+ /** The material's fill color, times the texture's color */
370
+ base: RGBA;
371
+ /** Texture coordinates (0, 0 when the mesh has none) */
372
+ uv: UV;
373
+ /** Unit normal, in world space, facing the camera's side */
374
+ normal: Vec3;
375
+ /** World position */
376
+ position: Vec3;
377
+ /** Position in the mesh's own space */
378
+ local: Vec3;
379
+ /** Unit direction from the point to the camera */
380
+ view: Vec3;
381
+ /** Unit direction light comes from */
382
+ lightDir: Vec3;
383
+ /** How much the point faces the light (0..1) */
384
+ diffuse: number;
385
+ /** Brightness Trid used: ambient + (1 - ambient) × diffuse */
386
+ light: number;
387
+ /** The scene's ambient level */
388
+ ambient: number;
389
+ /** Distance to the camera */
390
+ depth: number;
391
+ /** Seconds since the renderer started */
392
+ time: number;
393
+ /** The scene's background color */
394
+ background: RGBA;
395
+ /** Canvas renderer only: turns a uniform value (CSS color, array, vector) into RGBA 0..1 */
396
+ rgba(value: UniformValue): RGBA;
397
+ }
398
+
399
+ /** A small shader that changes how a material looks. */
400
+ export interface Effect<U extends Record<string, UniformValue> = Record<string, UniformValue>> {
401
+ name?: string;
402
+ /** GLSL defining `vec4 effect(vec4 color, Surface s)` (WebGL renderer) */
403
+ glsl?: string;
404
+ /** Run once per face by the canvas renderer */
405
+ canvas?(color: RGBA, s: Surface, uniforms: U): number[];
406
+ /** Settings read every frame, like `{ uPower: 2 }` */
407
+ uniforms?: U;
408
+ }
409
+
410
+ /** Cartoon look: the light comes in a few hard steps. */
411
+ export function toon(opts?: { steps?: number }): Effect<{ uSteps: number }>;
412
+
413
+ /** A glow along the edges, where the surface turns away from the camera. */
414
+ export function rim(opts?: { color?: string; power?: number; strength?: number }): Effect<{
415
+ uRimColor: string;
416
+ uPower: number;
417
+ uStrength: number;
418
+ }>;
419
+
420
+ /** A glow along the border of each face (WebGL only). */
421
+ export function edgeGlow(opts?: { color?: string; width?: number; strength?: number }): Effect<{
422
+ uGlowColor: string;
423
+ uWidth: number;
424
+ uStrength: number;
425
+ }>;
426
+
427
+ /** Ignores the light: shows the fill color everywhere. */
428
+ export function unlit(): Effect<{}>;
429
+
430
+ export interface GradientOptions {
431
+ /** Color at `min` (default "#E9806E") */
432
+ from?: string;
433
+ /** Color at `max` (default "#8ab4f8") */
434
+ to?: string;
435
+ /** Direction of the change (default "y") */
436
+ axis?: "x" | "y" | "z";
437
+ /** Where `from` is fully reached (default -0.5) */
438
+ min?: number;
439
+ /** Where `to` is fully reached (default 0.5) */
440
+ max?: number;
441
+ /** Measure in the mesh's own space or in the world (default "local") */
442
+ space?: "local" | "world";
443
+ /** Keep the shading from the light (default true) */
444
+ lit?: boolean;
445
+ }
446
+
447
+ /** Color that changes along an axis. */
448
+ export function gradient(opts?: GradientOptions): Effect<{
449
+ uFrom: string;
450
+ uTo: string;
451
+ uMin: number;
452
+ uMax: number;
453
+ uLit: boolean;
454
+ }>;
455
+
456
+ /** Fades objects into a color with distance. */
457
+ export function fog(opts?: { near?: number; far?: number; color?: string }): Effect<{
458
+ uNearFog: number;
459
+ uFarFog: number;
460
+ uFogColor: string;
461
+ uOwnColor: boolean;
462
+ }>;
463
+
464
+ /** Joins a material's effects into GLSL (used by the renderers). */
465
+ export function buildEffects(effects: Effect[]): {
466
+ code: string;
467
+ calls: string;
468
+ uniforms: { effect: number; name: string; glslName: string; type: string }[];
469
+ };
470
+
471
+ /** A key that's the same for effect lists that compile to the same shader. */
472
+ export function effectsKey(effects: Effect[]): string;
473
+
474
+ /** GLSL type of a uniform value, or null if it can't be one. */
475
+ export function uniformType(value: unknown): "float" | "vec2" | "vec3" | "vec4" | null;
476
+
477
+ /** The GLSL declaration of `Surface`, shared by every effect. */
478
+ export const SURFACE_GLSL: string;
479
+
480
+ // ---------------------------------------------------------------------------
481
+ // Texture mapping
482
+ // ---------------------------------------------------------------------------
483
+
484
+ /** Texture coordinates for every face corner. */
485
+ export interface MeshUVs {
486
+ uvs: UV[];
487
+ faceUVs: number[][];
488
+ }
489
+
490
+ /** The mapping types. */
491
+ export const MAPPINGS: readonly MappingType[];
492
+
493
+ /** Texture coordinates made from a mesh's shape. The mesh isn't changed. */
494
+ export function computeUVs(mesh: Mesh, mapping: Mapping): MeshUVs;
495
+
496
+ /** Replaces a mesh's texture coordinates with ones made from its shape, in place. */
497
+ export function mapUVs<M extends Mesh>(mesh: M, mapping: Mapping): M & MeshUVs;
498
+
499
+ /** The texture coordinates a renderer uses for a mesh and mapping (cached). */
500
+ export function uvsFor(mesh: Mesh, mapping?: Mapping): { uvs?: UV[]; faceUVs?: (number[] | null)[] };
501
+
502
+ /** A material's repeat, offset and rotation as [a, b, c, d, e, f], or null when they change nothing. */
503
+ export function uvTransform(material: Material): [number, number, number, number, number, number] | null;
504
+
505
+ /** Applies a `uvTransform` matrix to one texture coordinate. */
506
+ export function applyUVTransform(m: number[] | null, uv: UV): UV;
507
+ export function applyUVTransform(m: number[] | null, uv: UV | null): UV | null;
508
+
509
+ /** A mapping as `{ type, axis }`. Throws for an unknown type or axis. */
510
+ export function mappingOf(mapping?: Mapping): { type: MappingType; axis?: "x" | "y" | "z" };
511
+
512
+ /** A short string naming a mapping, for caches ("" for "uv"). */
513
+ export function mappingKey(mapping?: Mapping): string;
514
+
515
+ // ---------------------------------------------------------------------------
516
+ // Normals
517
+ // ---------------------------------------------------------------------------
518
+
519
+ /** Default crease angle (degrees) for `smooth: true`. */
520
+ export const DEFAULT_CREASE: number;
521
+
522
+ /** For each face, one unit normal per corner (smooth shading). */
523
+ export function cornerNormals(mesh: Mesh, crease?: number): Vec3[][];
524
+
525
+ /** The `smooth` material setting as a crease angle, or null for flat shading. */
526
+ export function creaseOf(smooth: boolean | number | undefined): number | null;
527
+
528
+ /** Normal of a polygon (Newell's method). Not normalized. */
529
+ export function newell(pts: Vec3[]): Vec3;
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // Loading files
533
+ // ---------------------------------------------------------------------------
534
+
535
+ /** A mesh loaded from an OBJ file. Always has faces. */
536
+ export interface ObjMesh extends Mesh {
537
+ faces: number[][];
538
+ /** Material names from `usemtl` lines, when the file uses more than one */
539
+ materialNames?: string[];
540
+ }
541
+
542
+ export interface ObjOptions {
543
+ /** Center the model and scale it so its largest side is this long */
544
+ fit?: number;
545
+ }
546
+
547
+ /** Parses the text of an OBJ file. */
548
+ export function parseOBJ(text: string, opts?: ObjOptions): ObjMesh;
549
+ /** Downloads an OBJ file and parses it. */
550
+ export function loadOBJ(url: string, opts?: ObjOptions): Promise<ObjMesh>;
551
+ /** Parses an OBJ `File` or `Blob`. */
552
+ export function readOBJFile(file: Blob, opts?: ObjOptions): Promise<ObjMesh>;
553
+ /** Opens the file dialog for .obj files. Call it from a user action. */
554
+ export function pickOBJFile(): Promise<File | null>;
555
+ /** Centers a mesh and scales it so its largest side is `size`. In place. */
556
+ export function fitMesh<M extends { vertices: Vec3[] }>(mesh: M, size?: number): M;
557
+
558
+ /** Loads an image to use as a texture. */
559
+ export function loadTexture(url: string): Promise<HTMLImageElement>;
560
+ /** Whether a texture source has pixels to draw yet. */
561
+ export function textureReady(src: TextureSource | null | undefined): boolean;
562
+ /** Pixel size of a texture source. */
563
+ export function textureSize(src: TextureSource): { w: number; h: number };
564
+ /** Whether a source can change over time (canvas, video). */
565
+ export function textureIsLive(src: TextureSource): boolean;
566
+
567
+ // ---------------------------------------------------------------------------
568
+ // Exporting
569
+ // ---------------------------------------------------------------------------
570
+
571
+ export interface ExportOBJOptions {
572
+ /**
573
+ * Write texture coordinates (default true). For an object whose material has a
574
+ * `mapping` or texture repeat/offset/rotation, the coordinates it's drawn with are written.
575
+ */
576
+ uvs?: boolean;
577
+ /** Write the mesh's normals when it has them (default true) */
578
+ normals?: boolean;
579
+ /** Write edges that aren't the side of a face as lines (default true) */
580
+ lines?: boolean;
581
+ /** Decimals kept in numbers (default 6) */
582
+ precision?: number;
583
+ /** Comment at the top of the file; "" for none (default "Exported by Trid") */
584
+ header?: string;
585
+ }
586
+
587
+ /** Something exportOBJ can write: a mesh, or an object placed in a scene. */
588
+ export type OBJExportable = Mesh | (SceneObjectInput & Partial<Transform> & { name?: string });
589
+
590
+ /**
591
+ * Writes meshes as the text of an OBJ file. Objects are written where they are
592
+ * in the scene; a list or a Scene gives one `o` per object.
593
+ */
594
+ export function exportOBJ(input: OBJExportable | OBJExportable[] | Scene, opts?: ExportOBJOptions): string;
595
+
596
+ export interface ScreenshotOptions {
597
+ /** "image/png" (default), "image/jpeg" or "image/webp" */
598
+ type?: string;
599
+ /** For JPEG and WebP, from 0 to 1 (default 0.92) */
600
+ quality?: number;
601
+ /** Image width in pixels (default: the canvas's) */
602
+ width?: number;
603
+ /** Image height in pixels (default: the canvas's) */
604
+ height?: number;
605
+ /** Include what tools draw on top (default true) */
606
+ tools?: boolean;
607
+ }
608
+
609
+ export interface RecordOptions {
610
+ /** Frames per second (default 60) */
611
+ fps?: number;
612
+ /** Video format, like "video/webm" (default: the best one the browser has) */
613
+ type?: string;
614
+ /** Quality: higher is sharper and bigger (default 8,000,000) */
615
+ bitsPerSecond?: number;
616
+ /** Stop by itself after this many seconds */
617
+ duration?: number;
618
+ }
619
+
620
+ /** A video being recorded. */
621
+ export interface Recording {
622
+ /** The video format being recorded */
623
+ readonly type: string;
624
+ /** Whether it's still recording */
625
+ readonly recording: boolean;
626
+ /** Resolves with the video once recording stops */
627
+ readonly done: Promise<Blob>;
628
+ /** Stops recording; resolves with the video */
629
+ stop(): Promise<Blob>;
630
+ }
631
+
632
+ /** Draws a new frame of a scene and returns it as an image file. */
633
+ export function screenshot(scene: Scene, opts?: ScreenshotOptions): Promise<Blob>;
634
+
635
+ /** Records a scene's canvas as a video. Throws if the browser can't. */
636
+ export function record(scene: Scene, opts?: RecordOptions): Recording;
637
+
638
+ /** Saves a file or text in the browser's downloads. */
639
+ export function download(data: Blob | string | Promise<Blob | string>, filename: string): Promise<void>;
640
+
641
+ // ---------------------------------------------------------------------------
642
+ // Tools
643
+ // ---------------------------------------------------------------------------
644
+
645
+ export interface ToolPointerEvent {
646
+ /** Pointer X in canvas pixels */
647
+ x: number;
648
+ /** Pointer Y in canvas pixels */
649
+ y: number;
650
+ /** X movement since the last event */
651
+ dx: number;
652
+ /** Y movement since the last event */
653
+ dy: number;
654
+ /** Button that changed (0 left, 1 middle, 2 right) */
655
+ button: number;
656
+ shiftKey: boolean;
657
+ ctrlKey: boolean;
658
+ altKey: boolean;
659
+ /** The DOM event */
660
+ original: PointerEvent;
661
+ }
662
+
663
+ export interface ToolWheelEvent {
664
+ /** Pointer X in canvas pixels */
665
+ x: number;
666
+ /** Pointer Y in canvas pixels */
667
+ y: number;
668
+ /** Scroll amount (> 0 scrolls down) */
669
+ deltaY: number;
670
+ /** The DOM event */
671
+ original: WheelEvent;
672
+ }
673
+
674
+ /** Something that plugs into a Scene. Every method is optional. */
675
+ export interface Tool {
676
+ attach?(scene: Scene): void;
677
+ detach?(scene: Scene): void;
678
+ /** Return true to take the drag */
679
+ pointerDown?(e: ToolPointerEvent, scene: Scene): boolean | void;
680
+ pointerMove?(e: ToolPointerEvent, scene: Scene): void;
681
+ pointerUp?(e: ToolPointerEvent, scene: Scene): void;
682
+ pointerLeave?(scene: Scene): void;
683
+ /** Return true to use the scroll */
684
+ wheel?(e: ToolWheelEvent, scene: Scene): boolean | void;
685
+ update?(dt: number, scene: Scene): void;
686
+ draw?(ctx: CanvasRenderingContext2D, scene: Scene): void;
687
+ /** Disable the right-click menu on the canvas */
688
+ blockContextMenu?: boolean;
689
+ }
690
+
691
+ /** Keeps a scene's tools and sends them canvas pointer events. */
692
+ export class ToolManager {
693
+ constructor(scene: Scene);
694
+ scene: Scene;
695
+ list: Tool[];
696
+ add(tool: Tool): void;
697
+ remove(tool: Tool): void;
698
+ clear(): void;
699
+ update(dt: number): void;
700
+ draw(ctx: CanvasRenderingContext2D): void;
701
+ }
702
+
703
+ export interface OrbitCameraOptions {
704
+ /** Point to orbit around (default origin) */
705
+ target?: Vec3;
706
+ /** Distance to the target (default: the camera's current distance) */
707
+ distance?: number;
708
+ /** Starting yaw (default: the camera's yaw) */
709
+ yaw?: number;
710
+ /** Starting pitch (default: the camera's pitch) */
711
+ pitch?: number;
712
+ /** Radians per dragged pixel (default 0.005) */
713
+ rotateSpeed?: number;
714
+ /** Zoom amount per scroll unit (default 0.001) */
715
+ zoomSpeed?: number;
716
+ /** Closest zoom (default 0.1) */
717
+ minDistance?: number;
718
+ /** Farthest zoom (default 100) */
719
+ maxDistance?: number;
720
+ /** Allow orbiting (default true) */
721
+ rotate?: boolean;
722
+ /** Allow panning (default true) */
723
+ pan?: boolean;
724
+ /** Allow zooming (default true) */
725
+ zoom?: boolean;
726
+ /** Turn around the target on its own, paused while dragging (default false) */
727
+ autoRotate?: boolean;
728
+ /** Auto-rotate speed in radians per second; negative turns the other way (default 0.5) */
729
+ autoRotateSpeed?: number;
730
+ /** Seconds to wait after a drag before auto-rotating again (default 0) */
731
+ autoRotateDelay?: number;
732
+ }
733
+
734
+ /** Mouse camera controls that turn around a target point. */
735
+ export class OrbitCamera implements Tool {
736
+ constructor(opts?: OrbitCameraOptions);
737
+ target: Vec3;
738
+ /** Set once the tool is attached */
739
+ distance: number;
740
+ /** Set once the tool is attached */
741
+ yaw: number;
742
+ /** Set once the tool is attached */
743
+ pitch: number;
744
+ rotateSpeed: number;
745
+ zoomSpeed: number;
746
+ minDistance: number;
747
+ maxDistance: number;
748
+ rotate: boolean;
749
+ pan: boolean;
750
+ zoom: boolean;
751
+ autoRotate: boolean;
752
+ autoRotateSpeed: number;
753
+ autoRotateDelay: number;
754
+ blockContextMenu: boolean;
755
+ /** Places the camera from the target, distance, yaw and pitch. */
756
+ apply(camera: Camera): void;
757
+ attach(scene: Scene): void;
758
+ pointerDown(e: ToolPointerEvent): boolean;
759
+ pointerMove(e: ToolPointerEvent, scene: Scene): void;
760
+ pointerUp(): void;
761
+ wheel(e: ToolWheelEvent, scene: Scene): boolean;
762
+ update(dt: number, scene: Scene): void;
763
+ }
764
+
765
+ export interface FlyCameraOptions {
766
+ /** Units per second (default 1) */
767
+ speed?: number;
768
+ /** Speed multiplier while Shift is held (default 3) */
769
+ fastMultiplier?: number;
770
+ /** Radians per second with the arrow keys (default 1.5) */
771
+ turnSpeed?: number;
772
+ /** Radians per dragged pixel (default 0.005) */
773
+ lookSpeed?: number;
774
+ }
775
+
776
+ /** Keyboard + mouse camera that flies freely through the scene. */
777
+ export class FlyCamera implements Tool {
778
+ constructor(opts?: FlyCameraOptions);
779
+ speed: number;
780
+ fastMultiplier: number;
781
+ turnSpeed: number;
782
+ lookSpeed: number;
783
+ attach(): void;
784
+ detach(): void;
785
+ pointerDown(e: ToolPointerEvent): boolean;
786
+ pointerMove(e: ToolPointerEvent, scene: Scene): void;
787
+ update(dt: number, scene: Scene): void;
788
+ }
789
+
790
+ export interface DragObjectOptions {
791
+ /** Mouse button that drags (default 0, left) */
792
+ button?: number;
793
+ /** Which objects can be dragged (default: those without `draggable: false`) */
794
+ filter?: (obj: SceneObject) => boolean;
795
+ /** Called when an object is picked */
796
+ onPick?: (obj: SceneObject) => void;
797
+ /** Called each time the object moves */
798
+ onMove?: (obj: SceneObject) => void;
799
+ /** Called when the object is released */
800
+ onDrop?: (obj: SceneObject) => void;
801
+ }
802
+
803
+ /** Click an object to pick it, then drag to move it. */
804
+ export class DragObject implements Tool {
805
+ constructor(opts?: DragObjectOptions);
806
+ button: number;
807
+ filter: (obj: SceneObject) => boolean;
808
+ onPick?: (obj: SceneObject) => void;
809
+ onMove?: (obj: SceneObject) => void;
810
+ onDrop?: (obj: SceneObject) => void;
811
+ /** The object being dragged, or null. */
812
+ dragging: SceneObject | null;
813
+ pointerDown(e: ToolPointerEvent, scene: Scene): boolean;
814
+ pointerMove(e: ToolPointerEvent, scene: Scene): void;
815
+ pointerUp(): void;
816
+ detach(): void;
817
+ }
818
+
819
+ export interface HoverHighlightOptions {
820
+ /** Highlight color (default "#ffffff") */
821
+ color?: string;
822
+ /** Which objects can be highlighted (default: those without `hoverable: false`) */
823
+ filter?: (obj: SceneObject) => boolean;
824
+ /** Called when the hovered object changes */
825
+ onChange?: (obj: SceneObject | null) => void;
826
+ }
827
+
828
+ /** Changes an object's color while the pointer is over it. */
829
+ export class HoverHighlight implements Tool {
830
+ constructor(opts?: HoverHighlightOptions);
831
+ color: string;
832
+ filter: (obj: SceneObject) => boolean;
833
+ onChange?: (obj: SceneObject | null) => void;
834
+ /** The object under the pointer, or null. */
835
+ hovered: SceneObject | null;
836
+ pointerMove(e: ToolPointerEvent, scene: Scene): void;
837
+ pointerLeave(): void;
838
+ detach(): void;
839
+ }
840
+
841
+ export interface FpsMeterOptions {
842
+ /** Text color (default "#ffffff") */
843
+ color?: string;
844
+ /** CSS font (default "14px monospace") */
845
+ font?: string;
846
+ /** Where to draw it (default "top-left") */
847
+ corner?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
848
+ /** Seconds between refreshes (default 0.5) */
849
+ interval?: number;
850
+ }
851
+
852
+ /** Draws the frames per second in a corner of the canvas. */
853
+ export class FpsMeter implements Tool {
854
+ constructor(opts?: FpsMeterOptions);
855
+ color: string;
856
+ font: string;
857
+ corner: "top-left" | "top-right" | "bottom-left" | "bottom-right";
858
+ interval: number;
859
+ /** Last measured frames per second. */
860
+ fps: number;
861
+ update(dt: number): void;
862
+ draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
863
+ }
864
+
865
+ export interface ObjUploaderOptions {
866
+ /** Size of the model's largest side; null keeps the file's units (default 1) */
867
+ fit?: number | null;
868
+ /** Color of loaded models (default: the scene's color) */
869
+ color?: string;
870
+ /** Remove the previously loaded model first (default true) */
871
+ replace?: boolean;
872
+ /** Text shown while nothing is loaded; "" for none */
873
+ hint?: string;
874
+ /** Called with the object after it's added */
875
+ onLoad?: (obj: SceneObject, file: File, scene: Scene) => void;
876
+ /** Called when a file can't be read */
877
+ onError?: (err: Error, file: File) => void;
878
+ }
879
+
880
+ /** Lets the user load .obj files into the scene (drag and drop, or `open()`). */
881
+ export class ObjUploader implements Tool {
882
+ constructor(opts?: ObjUploaderOptions);
883
+ fit: number | null;
884
+ color?: string;
885
+ replace: boolean;
886
+ hint: string;
887
+ onLoad?: (obj: SceneObject, file: File, scene: Scene) => void;
888
+ onError: (err: Error, file: File) => void;
889
+ /** The last object this tool added, or null. */
890
+ object: SceneObject | null;
891
+ /** Shows the file dialog and loads the chosen file. Call it from a user action. */
892
+ open(): Promise<SceneObject | null>;
893
+ /** Loads a .obj file into the scene. */
894
+ load(file: Blob): Promise<SceneObject | null>;
895
+ attach(scene: Scene): void;
896
+ detach(scene: Scene): void;
897
+ draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
898
+ }
899
+
900
+ export interface ScreenBox {
901
+ minX: number;
902
+ minY: number;
903
+ maxX: number;
904
+ maxY: number;
905
+ }
906
+
907
+ /** Finds the object under a canvas pixel (by screen bounding box). */
908
+ export function pick(
909
+ scene: Scene,
910
+ x: number,
911
+ y: number,
912
+ opts?: { filter?: (obj: SceneObject) => boolean; padding?: number },
913
+ ): SceneObject | null;
914
+
915
+ /** Screen-space bounding box of an object, or null if it's all behind the camera. */
916
+ export function screenBounds(scene: Scene, obj: SceneObject): ScreenBox | null;
917
+
918
+ /** World units one canvas pixel covers at a distance from the camera. */
919
+ export function worldPerPixel(scene: Scene, depth: number): number;
920
+
921
+ // ---------------------------------------------------------------------------
922
+ // Drawing and the frame loop
923
+ // ---------------------------------------------------------------------------
924
+
925
+ /** Fills the whole canvas with a color. */
926
+ export function clear(ctx: CanvasRenderingContext2D, w: number, h: number, color: string): void;
927
+ /** Draws a square point centered at a screen position. */
928
+ export function point(ctx: CanvasRenderingContext2D, p: Vec2, color: string, size?: number): void;
929
+ /** Draws a line between two screen points. */
930
+ export function line(ctx: CanvasRenderingContext2D, p1: Vec2, p2: Vec2, color: string, width?: number): void;
931
+ /** Draws edges between already-projected screen points. */
932
+ export function drawWireframe(
933
+ ctx: CanvasRenderingContext2D,
934
+ points2: Vec2[],
935
+ edges: number[][],
936
+ color: string,
937
+ width?: number,
938
+ ): void;
939
+ /** Draws a mesh instance as a wireframe. */
940
+ export function drawInstance(
941
+ ctx: CanvasRenderingContext2D,
942
+ inst: { mesh: Mesh } & Transform,
943
+ camera: Camera,
944
+ w: number,
945
+ h: number,
946
+ color: string,
947
+ lineWidth?: number,
948
+ ): void;
949
+ /** Turns edge definitions into pairs of vertex indices. */
950
+ export function edgeSegments(edges?: number[][]): Generator<[number, number]>;
951
+ /** The lines a wireframe draws for a mesh: its `edges`, or its face outlines when it has none. */
952
+ export function wireEdges(mesh: { edges?: number[][]; faces?: number[][] }): number[][];
953
+
954
+ /** Runs `update` every animation frame. Returns a function that stops it. */
955
+ export function loop(update: (dt: number, time: number) => void, opts?: { maxDt?: number }): () => void;
956
+
957
+ /** Turns any CSS color into [r, g, b, a] (r, g, b in 0..255, a in 0..1). */
958
+ export function parseColor(ctx: CanvasRenderingContext2D, color: string): RGBA;
959
+
960
+ // ---------------------------------------------------------------------------
961
+ // Math
962
+ // ---------------------------------------------------------------------------
963
+
964
+ /** Default vertical field of view (90°). */
965
+ export const DEFAULT_FOV: number;
966
+ /** Default near plane distance. */
967
+ export const DEFAULT_NEAR: number;
968
+
969
+ export function localToWorld(v: Vec3, inst: Transform): Vec3;
970
+ export function worldToCamera(pWorld: Vec3, camera: Camera): Vec3;
971
+ export function cameraToScreen(pCam: Vec3, camera: Camera, w: number, h: number): Vec2;
972
+ export function worldToScreen(pWorld: Vec3, camera: Camera, w: number, h: number): Vec2;
973
+ export function cameraForward(camera: Camera): Vec3;
974
+ export function cameraRight(camera: Camera): Vec3;
975
+ export function cameraUp(camera: Camera): Vec3;
976
+ /** Clips a camera-space segment against the near plane. Null if fully behind. */
977
+ export function clipNear(a: Vec3, b: Vec3, near: number): [Vec3, Vec3] | null;
978
+ export function scale(p: Vec3, sx: number, sy: number, sz: number): Vec3;
979
+ export function translate(p: Vec3, dx: number, dy: number, dz: number): Vec3;
980
+ export function rotateX(p: Vec3, angle: number): Vec3;
981
+ export function rotateY(p: Vec3, angle: number): Vec3;
982
+ export function rotateZ(p: Vec3, angle: number): Vec3;
983
+ /** Rotates around X, then Y, then Z. */
984
+ export function rotateXYZ(p: Vec3, rot: Vec3): Vec3;
985
+ /** Perspective projection to normalized coordinates (-1..1). */
986
+ export function project(p: Vec3, fov?: number, aspect?: number): Vec2;
987
+ /** Normalized coordinates to canvas pixels. */
988
+ export function screen(p: Vec2, w: number, h: number): Vec2;