trid3d 0.1.0 → 0.1.1
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/README.md +5 -3
- package/package.json +7 -3
- package/trid/index.d.ts +783 -0
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="docs/public/logo.svg" width="96" alt="Trid logo">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ZachR622/Trid/master/docs/public/logo.svg" width="96" alt="Trid logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Trid</h1>
|
|
@@ -86,14 +86,16 @@ Or let users load their own: `scene.use(new ObjUploader())` accepts dropped `.ob
|
|
|
86
86
|
|
|
87
87
|
## Documentation
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
**[zachr622.github.io/Trid](https://zachr622.github.io/Trid/)**: every class, option and function, with live demos.
|
|
90
|
+
|
|
91
|
+
To run the docs locally (Node 18+):
|
|
90
92
|
|
|
91
93
|
```sh
|
|
92
94
|
npm install
|
|
93
95
|
npm run docs:dev
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
See [`docs/README.md`](docs/README.md) for building and editing the docs.
|
|
98
|
+
See [`docs/README.md`](https://github.com/ZachR622/Trid/blob/master/docs/README.md) for building and editing the docs.
|
|
97
99
|
|
|
98
100
|
## Roadmap
|
|
99
101
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trid3d",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A small 3D library for the HTML canvas: wireframes, filled and smooth faces, textures and shader effects, drawn with Canvas 2D or WebGL. No dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"3d",
|
|
@@ -17,15 +17,19 @@
|
|
|
17
17
|
"type": "git",
|
|
18
18
|
"url": "git+https://github.com/ZachR622/Trid.git"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://github.
|
|
20
|
+
"homepage": "https://zachr622.github.io/Trid/",
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/ZachR622/Trid/issues"
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "./trid/index.js",
|
|
26
26
|
"module": "./trid/index.js",
|
|
27
|
+
"types": "./trid/index.d.ts",
|
|
27
28
|
"exports": {
|
|
28
|
-
".":
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./trid/index.d.ts",
|
|
31
|
+
"default": "./trid/index.js"
|
|
32
|
+
},
|
|
29
33
|
"./*": "./trid/*"
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
package/trid/index.d.ts
ADDED
|
@@ -0,0 +1,783 @@
|
|
|
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
|
+
*/
|
|
60
|
+
export interface Mesh {
|
|
61
|
+
vertices: Vec3[];
|
|
62
|
+
edges: number[][];
|
|
63
|
+
faces?: number[][];
|
|
64
|
+
/** Per-corner normals, used by smooth shading */
|
|
65
|
+
normals?: Vec3[];
|
|
66
|
+
/** For each face, the index into `normals` of each corner (null: none for that face) */
|
|
67
|
+
faceNormals?: (number[] | null)[];
|
|
68
|
+
/** Texture coordinates */
|
|
69
|
+
uvs?: UV[];
|
|
70
|
+
/** For each face, the index into `uvs` of each corner (null: none for that face) */
|
|
71
|
+
faceUVs?: (number[] | null)[];
|
|
72
|
+
/** Set after changing the mesh in place so renderers rebuild their caches */
|
|
73
|
+
dirty?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Transform of a mesh instance. */
|
|
77
|
+
export interface Transform {
|
|
78
|
+
pos: Vec3;
|
|
79
|
+
rot: Vec3;
|
|
80
|
+
scale: Vec3;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Anything a texture can be: an image, canvas, video or bitmap. */
|
|
84
|
+
export type TextureSource = CanvasImageSource;
|
|
85
|
+
|
|
86
|
+
/** How an object's faces look. Objects without one are drawn as wireframes. */
|
|
87
|
+
export interface Material {
|
|
88
|
+
/** Face color, any CSS color (default: the scene's color, or white with a texture) */
|
|
89
|
+
fill?: string;
|
|
90
|
+
/** Darken faces that turn away from the light (default true) */
|
|
91
|
+
shade?: boolean;
|
|
92
|
+
/** Also draw the face outlines, with the object's `color` and `lineWidth` (default false) */
|
|
93
|
+
wireframe?: boolean;
|
|
94
|
+
/** Also draw faces seen from behind (default false) */
|
|
95
|
+
doubleSided?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Blend the light across faces so curved shapes look round.
|
|
98
|
+
* `true` keeps edges sharper than 60° hard; a number sets that angle.
|
|
99
|
+
*/
|
|
100
|
+
smooth?: boolean | number;
|
|
101
|
+
/** Image drawn on the faces (needs texture coordinates). `fill` tints it. */
|
|
102
|
+
texture?: TextureSource;
|
|
103
|
+
/** Keep texture pixels sharp instead of smoothing them (default false) */
|
|
104
|
+
pixelated?: boolean;
|
|
105
|
+
/** Effects applied in order, like `[toon(), rim()]` */
|
|
106
|
+
effects?: Effect[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** What you pass to `scene.add`. Missing transform fields get defaults. */
|
|
110
|
+
export interface SceneObjectInput {
|
|
111
|
+
mesh: Mesh;
|
|
112
|
+
pos?: Vec3;
|
|
113
|
+
rot?: Vec3;
|
|
114
|
+
scale?: Vec3;
|
|
115
|
+
/** Wireframe color (default: the scene's color) */
|
|
116
|
+
color?: string;
|
|
117
|
+
/** Wireframe width (default: the scene's lineWidth) */
|
|
118
|
+
lineWidth?: number;
|
|
119
|
+
/** Draw faces instead of only lines */
|
|
120
|
+
material?: Material;
|
|
121
|
+
/** Set to false to make `DragObject` ignore this object */
|
|
122
|
+
draggable?: boolean;
|
|
123
|
+
/** Set to false to make `HoverHighlight` ignore this object */
|
|
124
|
+
hoverable?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* An object in a scene, after `scene.add` filled in its transform.
|
|
129
|
+
* Extra fields you add (like `{ mesh, name: "box" }`) are kept in the type.
|
|
130
|
+
*/
|
|
131
|
+
export type SceneObject<T extends SceneObjectInput = SceneObjectInput> = T & Transform;
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Scene
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/** Anything that can draw a scene. */
|
|
138
|
+
export interface Renderer {
|
|
139
|
+
render(scene: Scene): void;
|
|
140
|
+
dispose?(): void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface SceneOptions {
|
|
144
|
+
/** Sets canvas.width (keeps the current one if omitted) */
|
|
145
|
+
width?: number;
|
|
146
|
+
/** Sets canvas.height (keeps the current one if omitted) */
|
|
147
|
+
height?: number;
|
|
148
|
+
/** Clear color (default "#141414") */
|
|
149
|
+
background?: string;
|
|
150
|
+
/** Default wireframe color (default "#ffffff") */
|
|
151
|
+
color?: string;
|
|
152
|
+
/** Default wireframe width (default 3) */
|
|
153
|
+
lineWidth?: number;
|
|
154
|
+
/** Camera (default { z: -1 }) */
|
|
155
|
+
camera?: Camera;
|
|
156
|
+
/** Direction light comes from (default: above, left and in front) */
|
|
157
|
+
light?: Vec3;
|
|
158
|
+
/** Brightness of faces turned away from the light, 0..1 (default 0.25) */
|
|
159
|
+
ambient?: number;
|
|
160
|
+
/** What draws the scene (default: a new CanvasRenderer) */
|
|
161
|
+
renderer?: Renderer;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Holds a canvas, a camera and a list of objects, and draws them. */
|
|
165
|
+
export class Scene {
|
|
166
|
+
constructor(canvas: HTMLCanvasElement, opts?: SceneOptions);
|
|
167
|
+
|
|
168
|
+
canvas: HTMLCanvasElement;
|
|
169
|
+
ctx: CanvasRenderingContext2D;
|
|
170
|
+
background: string;
|
|
171
|
+
color: string;
|
|
172
|
+
lineWidth: number;
|
|
173
|
+
camera: Camera;
|
|
174
|
+
light: Vec3;
|
|
175
|
+
ambient: number;
|
|
176
|
+
renderer: Renderer;
|
|
177
|
+
objects: SceneObject[];
|
|
178
|
+
tools: ToolManager;
|
|
179
|
+
|
|
180
|
+
/** Adds an object, filling in missing transform fields. Returns the same object. */
|
|
181
|
+
add<T extends SceneObjectInput>(obj: T): SceneObject<T>;
|
|
182
|
+
/** Removes an object. Does nothing if it isn't in the scene. */
|
|
183
|
+
remove(obj: SceneObjectInput): void;
|
|
184
|
+
/** Adds a tool. Returns the same tool. */
|
|
185
|
+
use<T extends Tool>(tool: T): T;
|
|
186
|
+
/** Removes a tool. Does nothing if it isn't in the scene. */
|
|
187
|
+
unuse(tool: Tool): void;
|
|
188
|
+
/** Draws every object once, then lets tools draw on top. */
|
|
189
|
+
render(): void;
|
|
190
|
+
/** Starts the frame loop (restarts it if already running). */
|
|
191
|
+
start(update?: (dt: number, time: number, scene: Scene) => void): this;
|
|
192
|
+
/** Stops the frame loop. The last frame stays on the canvas. */
|
|
193
|
+
stop(): void;
|
|
194
|
+
/** Stops the loop, removes every tool and frees the renderer's resources. */
|
|
195
|
+
destroy(): void;
|
|
196
|
+
/** Whether the frame loop is running. */
|
|
197
|
+
readonly running: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Shapes
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
export interface ShapeOptions {
|
|
205
|
+
/** Cube size (default 0.5) */
|
|
206
|
+
size?: number;
|
|
207
|
+
/** Box width (default 0.6) */
|
|
208
|
+
w?: number;
|
|
209
|
+
/** Box height (default 0.6) */
|
|
210
|
+
h?: number;
|
|
211
|
+
/** Box depth (default 0.6) */
|
|
212
|
+
d?: number;
|
|
213
|
+
/** Sphere radius (default 0.45) */
|
|
214
|
+
r?: number;
|
|
215
|
+
/** Sphere latitude bands (default 12) */
|
|
216
|
+
rings?: number;
|
|
217
|
+
/** Sphere points per ring (default 24) */
|
|
218
|
+
segments?: number;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type ShapeKind = "cube" | "box" | "sphere";
|
|
222
|
+
|
|
223
|
+
/** Creates a built-in mesh (with faces and texture coordinates). */
|
|
224
|
+
export function makeShape(kind: ShapeKind, opts?: ShapeOptions): Mesh;
|
|
225
|
+
|
|
226
|
+
/** Scales a mesh's vertices in place. Returns the same mesh. */
|
|
227
|
+
export function scaleMesh<M extends { vertices: Vec3[] }>(mesh: M, sx?: number, sy?: number, sz?: number): M;
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Renderers
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/** Direction light comes from when the scene doesn't set one. */
|
|
234
|
+
export const DEFAULT_LIGHT: Readonly<Vec3>;
|
|
235
|
+
/** How bright a face turned fully away from the light still is (0..1). */
|
|
236
|
+
export const DEFAULT_AMBIENT: number;
|
|
237
|
+
/** Default far distance (WebGL renderer). */
|
|
238
|
+
export const DEFAULT_FAR: number;
|
|
239
|
+
|
|
240
|
+
export interface CanvasRendererOptions {
|
|
241
|
+
/**
|
|
242
|
+
* Split faces bigger than this fraction of their mesh's size (like 0.05)
|
|
243
|
+
* into smaller triangles, so they sort better. Off by default (null).
|
|
244
|
+
*/
|
|
245
|
+
maxFaceSize?: number | null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Draws a scene with the canvas 2D context (painter's algorithm). */
|
|
249
|
+
export class CanvasRenderer implements Renderer {
|
|
250
|
+
constructor(opts?: CanvasRendererOptions);
|
|
251
|
+
maxFaceSize: number | null;
|
|
252
|
+
render(scene: Scene): void;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface WebGLRendererOptions {
|
|
256
|
+
/** Smooth edges (default true) */
|
|
257
|
+
antialias?: boolean;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Draws a scene with WebGL and a depth buffer. */
|
|
261
|
+
export class WebGLRenderer implements Renderer {
|
|
262
|
+
/** Whether this browser can create a WebGL context. */
|
|
263
|
+
static isSupported(): boolean;
|
|
264
|
+
/** @throws If WebGL isn't available */
|
|
265
|
+
constructor(opts?: WebGLRendererOptions);
|
|
266
|
+
/** The hidden canvas WebGL draws on. */
|
|
267
|
+
canvas: HTMLCanvasElement;
|
|
268
|
+
gl: WebGLRenderingContext | WebGL2RenderingContext;
|
|
269
|
+
render(scene: Scene): void;
|
|
270
|
+
/** Frees the graphics card memory. The renderer can't be used afterwards. */
|
|
271
|
+
dispose(): void;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Effects
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
/** Values an effect uniform can hold. */
|
|
279
|
+
export type UniformValue = number | boolean | string | number[] | Vec3;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* What an effect knows about the point being drawn.
|
|
283
|
+
* Colors are [r, g, b, a] from 0 to 1.
|
|
284
|
+
*/
|
|
285
|
+
export interface Surface {
|
|
286
|
+
/** The material's fill color, times the texture's color */
|
|
287
|
+
base: RGBA;
|
|
288
|
+
/** Texture coordinates (0, 0 when the mesh has none) */
|
|
289
|
+
uv: UV;
|
|
290
|
+
/** Unit normal, in world space, facing the camera's side */
|
|
291
|
+
normal: Vec3;
|
|
292
|
+
/** World position */
|
|
293
|
+
position: Vec3;
|
|
294
|
+
/** Position in the mesh's own space */
|
|
295
|
+
local: Vec3;
|
|
296
|
+
/** Unit direction from the point to the camera */
|
|
297
|
+
view: Vec3;
|
|
298
|
+
/** Unit direction light comes from */
|
|
299
|
+
lightDir: Vec3;
|
|
300
|
+
/** How much the point faces the light (0..1) */
|
|
301
|
+
diffuse: number;
|
|
302
|
+
/** Brightness Trid used: ambient + (1 - ambient) × diffuse */
|
|
303
|
+
light: number;
|
|
304
|
+
/** The scene's ambient level */
|
|
305
|
+
ambient: number;
|
|
306
|
+
/** Distance to the camera */
|
|
307
|
+
depth: number;
|
|
308
|
+
/** Seconds since the renderer started */
|
|
309
|
+
time: number;
|
|
310
|
+
/** The scene's background color */
|
|
311
|
+
background: RGBA;
|
|
312
|
+
/** Canvas renderer only: turns a uniform value (CSS color, array, vector) into RGBA 0..1 */
|
|
313
|
+
rgba(value: UniformValue): RGBA;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** A small shader that changes how a material looks. */
|
|
317
|
+
export interface Effect<U extends Record<string, UniformValue> = Record<string, UniformValue>> {
|
|
318
|
+
name?: string;
|
|
319
|
+
/** GLSL defining `vec4 effect(vec4 color, Surface s)` (WebGL renderer) */
|
|
320
|
+
glsl?: string;
|
|
321
|
+
/** Run once per face by the canvas renderer */
|
|
322
|
+
canvas?(color: RGBA, s: Surface, uniforms: U): number[];
|
|
323
|
+
/** Settings read every frame, like `{ uPower: 2 }` */
|
|
324
|
+
uniforms?: U;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Cartoon look: the light comes in a few hard steps. */
|
|
328
|
+
export function toon(opts?: { steps?: number }): Effect<{ uSteps: number }>;
|
|
329
|
+
|
|
330
|
+
/** A glow along the edges, where the surface turns away from the camera. */
|
|
331
|
+
export function rim(opts?: { color?: string; power?: number; strength?: number }): Effect<{
|
|
332
|
+
uRimColor: string;
|
|
333
|
+
uPower: number;
|
|
334
|
+
uStrength: number;
|
|
335
|
+
}>;
|
|
336
|
+
|
|
337
|
+
/** A glow along the border of each face (WebGL only). */
|
|
338
|
+
export function edgeGlow(opts?: { color?: string; width?: number; strength?: number }): Effect<{
|
|
339
|
+
uGlowColor: string;
|
|
340
|
+
uWidth: number;
|
|
341
|
+
uStrength: number;
|
|
342
|
+
}>;
|
|
343
|
+
|
|
344
|
+
/** Ignores the light: shows the fill color everywhere. */
|
|
345
|
+
export function unlit(): Effect<{}>;
|
|
346
|
+
|
|
347
|
+
export interface GradientOptions {
|
|
348
|
+
/** Color at `min` (default "#E9806E") */
|
|
349
|
+
from?: string;
|
|
350
|
+
/** Color at `max` (default "#8ab4f8") */
|
|
351
|
+
to?: string;
|
|
352
|
+
/** Direction of the change (default "y") */
|
|
353
|
+
axis?: "x" | "y" | "z";
|
|
354
|
+
/** Where `from` is fully reached (default -0.5) */
|
|
355
|
+
min?: number;
|
|
356
|
+
/** Where `to` is fully reached (default 0.5) */
|
|
357
|
+
max?: number;
|
|
358
|
+
/** Measure in the mesh's own space or in the world (default "local") */
|
|
359
|
+
space?: "local" | "world";
|
|
360
|
+
/** Keep the shading from the light (default true) */
|
|
361
|
+
lit?: boolean;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Color that changes along an axis. */
|
|
365
|
+
export function gradient(opts?: GradientOptions): Effect<{
|
|
366
|
+
uFrom: string;
|
|
367
|
+
uTo: string;
|
|
368
|
+
uMin: number;
|
|
369
|
+
uMax: number;
|
|
370
|
+
uLit: boolean;
|
|
371
|
+
}>;
|
|
372
|
+
|
|
373
|
+
/** Fades objects into a color with distance. */
|
|
374
|
+
export function fog(opts?: { near?: number; far?: number; color?: string }): Effect<{
|
|
375
|
+
uNearFog: number;
|
|
376
|
+
uFarFog: number;
|
|
377
|
+
uFogColor: string;
|
|
378
|
+
uOwnColor: boolean;
|
|
379
|
+
}>;
|
|
380
|
+
|
|
381
|
+
/** Joins a material's effects into GLSL (used by the renderers). */
|
|
382
|
+
export function buildEffects(effects: Effect[]): {
|
|
383
|
+
code: string;
|
|
384
|
+
calls: string;
|
|
385
|
+
uniforms: { effect: number; name: string; glslName: string; type: string }[];
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/** A key that's the same for effect lists that compile to the same shader. */
|
|
389
|
+
export function effectsKey(effects: Effect[]): string;
|
|
390
|
+
|
|
391
|
+
/** GLSL type of a uniform value, or null if it can't be one. */
|
|
392
|
+
export function uniformType(value: unknown): "float" | "vec2" | "vec3" | "vec4" | null;
|
|
393
|
+
|
|
394
|
+
/** The GLSL declaration of `Surface`, shared by every effect. */
|
|
395
|
+
export const SURFACE_GLSL: string;
|
|
396
|
+
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Normals
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
/** Default crease angle (degrees) for `smooth: true`. */
|
|
402
|
+
export const DEFAULT_CREASE: number;
|
|
403
|
+
|
|
404
|
+
/** For each face, one unit normal per corner (smooth shading). */
|
|
405
|
+
export function cornerNormals(mesh: Mesh, crease?: number): Vec3[][];
|
|
406
|
+
|
|
407
|
+
/** The `smooth` material setting as a crease angle, or null for flat shading. */
|
|
408
|
+
export function creaseOf(smooth: boolean | number | undefined): number | null;
|
|
409
|
+
|
|
410
|
+
/** Normal of a polygon (Newell's method). Not normalized. */
|
|
411
|
+
export function newell(pts: Vec3[]): Vec3;
|
|
412
|
+
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
// Loading files
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
/** A mesh loaded from an OBJ file. Always has faces. */
|
|
418
|
+
export interface ObjMesh extends Mesh {
|
|
419
|
+
faces: number[][];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface ObjOptions {
|
|
423
|
+
/** Center the model and scale it so its largest side is this long */
|
|
424
|
+
fit?: number;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Parses the text of an OBJ file. */
|
|
428
|
+
export function parseOBJ(text: string, opts?: ObjOptions): ObjMesh;
|
|
429
|
+
/** Downloads an OBJ file and parses it. */
|
|
430
|
+
export function loadOBJ(url: string, opts?: ObjOptions): Promise<ObjMesh>;
|
|
431
|
+
/** Parses an OBJ `File` or `Blob`. */
|
|
432
|
+
export function readOBJFile(file: Blob, opts?: ObjOptions): Promise<ObjMesh>;
|
|
433
|
+
/** Opens the file dialog for .obj files. Call it from a user action. */
|
|
434
|
+
export function pickOBJFile(): Promise<File | null>;
|
|
435
|
+
/** Centers a mesh and scales it so its largest side is `size`. In place. */
|
|
436
|
+
export function fitMesh<M extends { vertices: Vec3[] }>(mesh: M, size?: number): M;
|
|
437
|
+
|
|
438
|
+
/** Loads an image to use as a texture. */
|
|
439
|
+
export function loadTexture(url: string): Promise<HTMLImageElement>;
|
|
440
|
+
/** Whether a texture source has pixels to draw yet. */
|
|
441
|
+
export function textureReady(src: TextureSource | null | undefined): boolean;
|
|
442
|
+
/** Pixel size of a texture source. */
|
|
443
|
+
export function textureSize(src: TextureSource): { w: number; h: number };
|
|
444
|
+
/** Whether a source can change over time (canvas, video). */
|
|
445
|
+
export function textureIsLive(src: TextureSource): boolean;
|
|
446
|
+
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
// Tools
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
export interface ToolPointerEvent {
|
|
452
|
+
/** Pointer X in canvas pixels */
|
|
453
|
+
x: number;
|
|
454
|
+
/** Pointer Y in canvas pixels */
|
|
455
|
+
y: number;
|
|
456
|
+
/** X movement since the last event */
|
|
457
|
+
dx: number;
|
|
458
|
+
/** Y movement since the last event */
|
|
459
|
+
dy: number;
|
|
460
|
+
/** Button that changed (0 left, 1 middle, 2 right) */
|
|
461
|
+
button: number;
|
|
462
|
+
shiftKey: boolean;
|
|
463
|
+
ctrlKey: boolean;
|
|
464
|
+
altKey: boolean;
|
|
465
|
+
/** The DOM event */
|
|
466
|
+
original: PointerEvent;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export interface ToolWheelEvent {
|
|
470
|
+
/** Pointer X in canvas pixels */
|
|
471
|
+
x: number;
|
|
472
|
+
/** Pointer Y in canvas pixels */
|
|
473
|
+
y: number;
|
|
474
|
+
/** Scroll amount (> 0 scrolls down) */
|
|
475
|
+
deltaY: number;
|
|
476
|
+
/** The DOM event */
|
|
477
|
+
original: WheelEvent;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Something that plugs into a Scene. Every method is optional. */
|
|
481
|
+
export interface Tool {
|
|
482
|
+
attach?(scene: Scene): void;
|
|
483
|
+
detach?(scene: Scene): void;
|
|
484
|
+
/** Return true to take the drag */
|
|
485
|
+
pointerDown?(e: ToolPointerEvent, scene: Scene): boolean | void;
|
|
486
|
+
pointerMove?(e: ToolPointerEvent, scene: Scene): void;
|
|
487
|
+
pointerUp?(e: ToolPointerEvent, scene: Scene): void;
|
|
488
|
+
pointerLeave?(scene: Scene): void;
|
|
489
|
+
/** Return true to use the scroll */
|
|
490
|
+
wheel?(e: ToolWheelEvent, scene: Scene): boolean | void;
|
|
491
|
+
update?(dt: number, scene: Scene): void;
|
|
492
|
+
draw?(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
493
|
+
/** Disable the right-click menu on the canvas */
|
|
494
|
+
blockContextMenu?: boolean;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Keeps a scene's tools and sends them canvas pointer events. */
|
|
498
|
+
export class ToolManager {
|
|
499
|
+
constructor(scene: Scene);
|
|
500
|
+
scene: Scene;
|
|
501
|
+
list: Tool[];
|
|
502
|
+
add(tool: Tool): void;
|
|
503
|
+
remove(tool: Tool): void;
|
|
504
|
+
clear(): void;
|
|
505
|
+
update(dt: number): void;
|
|
506
|
+
draw(ctx: CanvasRenderingContext2D): void;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export interface OrbitCameraOptions {
|
|
510
|
+
/** Point to orbit around (default origin) */
|
|
511
|
+
target?: Vec3;
|
|
512
|
+
/** Distance to the target (default: the camera's current distance) */
|
|
513
|
+
distance?: number;
|
|
514
|
+
/** Starting yaw (default: the camera's yaw) */
|
|
515
|
+
yaw?: number;
|
|
516
|
+
/** Starting pitch (default: the camera's pitch) */
|
|
517
|
+
pitch?: number;
|
|
518
|
+
/** Radians per dragged pixel (default 0.005) */
|
|
519
|
+
rotateSpeed?: number;
|
|
520
|
+
/** Zoom amount per scroll unit (default 0.001) */
|
|
521
|
+
zoomSpeed?: number;
|
|
522
|
+
/** Closest zoom (default 0.1) */
|
|
523
|
+
minDistance?: number;
|
|
524
|
+
/** Farthest zoom (default 100) */
|
|
525
|
+
maxDistance?: number;
|
|
526
|
+
/** Allow orbiting (default true) */
|
|
527
|
+
rotate?: boolean;
|
|
528
|
+
/** Allow panning (default true) */
|
|
529
|
+
pan?: boolean;
|
|
530
|
+
/** Allow zooming (default true) */
|
|
531
|
+
zoom?: boolean;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** Mouse camera controls that turn around a target point. */
|
|
535
|
+
export class OrbitCamera implements Tool {
|
|
536
|
+
constructor(opts?: OrbitCameraOptions);
|
|
537
|
+
target: Vec3;
|
|
538
|
+
/** Set once the tool is attached */
|
|
539
|
+
distance: number;
|
|
540
|
+
/** Set once the tool is attached */
|
|
541
|
+
yaw: number;
|
|
542
|
+
/** Set once the tool is attached */
|
|
543
|
+
pitch: number;
|
|
544
|
+
rotateSpeed: number;
|
|
545
|
+
zoomSpeed: number;
|
|
546
|
+
minDistance: number;
|
|
547
|
+
maxDistance: number;
|
|
548
|
+
rotate: boolean;
|
|
549
|
+
pan: boolean;
|
|
550
|
+
zoom: boolean;
|
|
551
|
+
blockContextMenu: boolean;
|
|
552
|
+
/** Places the camera from the target, distance, yaw and pitch. */
|
|
553
|
+
apply(camera: Camera): void;
|
|
554
|
+
attach(scene: Scene): void;
|
|
555
|
+
pointerDown(e: ToolPointerEvent): boolean;
|
|
556
|
+
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
557
|
+
pointerUp(): void;
|
|
558
|
+
wheel(e: ToolWheelEvent, scene: Scene): boolean;
|
|
559
|
+
update(dt: number, scene: Scene): void;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export interface FlyCameraOptions {
|
|
563
|
+
/** Units per second (default 1) */
|
|
564
|
+
speed?: number;
|
|
565
|
+
/** Speed multiplier while Shift is held (default 3) */
|
|
566
|
+
fastMultiplier?: number;
|
|
567
|
+
/** Radians per second with the arrow keys (default 1.5) */
|
|
568
|
+
turnSpeed?: number;
|
|
569
|
+
/** Radians per dragged pixel (default 0.005) */
|
|
570
|
+
lookSpeed?: number;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Keyboard + mouse camera that flies freely through the scene. */
|
|
574
|
+
export class FlyCamera implements Tool {
|
|
575
|
+
constructor(opts?: FlyCameraOptions);
|
|
576
|
+
speed: number;
|
|
577
|
+
fastMultiplier: number;
|
|
578
|
+
turnSpeed: number;
|
|
579
|
+
lookSpeed: number;
|
|
580
|
+
attach(): void;
|
|
581
|
+
detach(): void;
|
|
582
|
+
pointerDown(e: ToolPointerEvent): boolean;
|
|
583
|
+
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
584
|
+
update(dt: number, scene: Scene): void;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export interface DragObjectOptions {
|
|
588
|
+
/** Mouse button that drags (default 0, left) */
|
|
589
|
+
button?: number;
|
|
590
|
+
/** Which objects can be dragged (default: those without `draggable: false`) */
|
|
591
|
+
filter?: (obj: SceneObject) => boolean;
|
|
592
|
+
/** Called when an object is picked */
|
|
593
|
+
onPick?: (obj: SceneObject) => void;
|
|
594
|
+
/** Called each time the object moves */
|
|
595
|
+
onMove?: (obj: SceneObject) => void;
|
|
596
|
+
/** Called when the object is released */
|
|
597
|
+
onDrop?: (obj: SceneObject) => void;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** Click an object to pick it, then drag to move it. */
|
|
601
|
+
export class DragObject implements Tool {
|
|
602
|
+
constructor(opts?: DragObjectOptions);
|
|
603
|
+
button: number;
|
|
604
|
+
filter: (obj: SceneObject) => boolean;
|
|
605
|
+
onPick?: (obj: SceneObject) => void;
|
|
606
|
+
onMove?: (obj: SceneObject) => void;
|
|
607
|
+
onDrop?: (obj: SceneObject) => void;
|
|
608
|
+
/** The object being dragged, or null. */
|
|
609
|
+
dragging: SceneObject | null;
|
|
610
|
+
pointerDown(e: ToolPointerEvent, scene: Scene): boolean;
|
|
611
|
+
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
612
|
+
pointerUp(): void;
|
|
613
|
+
detach(): void;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export interface HoverHighlightOptions {
|
|
617
|
+
/** Highlight color (default "#ffffff") */
|
|
618
|
+
color?: string;
|
|
619
|
+
/** Which objects can be highlighted (default: those without `hoverable: false`) */
|
|
620
|
+
filter?: (obj: SceneObject) => boolean;
|
|
621
|
+
/** Called when the hovered object changes */
|
|
622
|
+
onChange?: (obj: SceneObject | null) => void;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** Changes an object's color while the pointer is over it. */
|
|
626
|
+
export class HoverHighlight implements Tool {
|
|
627
|
+
constructor(opts?: HoverHighlightOptions);
|
|
628
|
+
color: string;
|
|
629
|
+
filter: (obj: SceneObject) => boolean;
|
|
630
|
+
onChange?: (obj: SceneObject | null) => void;
|
|
631
|
+
/** The object under the pointer, or null. */
|
|
632
|
+
hovered: SceneObject | null;
|
|
633
|
+
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
634
|
+
pointerLeave(): void;
|
|
635
|
+
detach(): void;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export interface FpsMeterOptions {
|
|
639
|
+
/** Text color (default "#ffffff") */
|
|
640
|
+
color?: string;
|
|
641
|
+
/** CSS font (default "14px monospace") */
|
|
642
|
+
font?: string;
|
|
643
|
+
/** Where to draw it (default "top-left") */
|
|
644
|
+
corner?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
645
|
+
/** Seconds between refreshes (default 0.5) */
|
|
646
|
+
interval?: number;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Draws the frames per second in a corner of the canvas. */
|
|
650
|
+
export class FpsMeter implements Tool {
|
|
651
|
+
constructor(opts?: FpsMeterOptions);
|
|
652
|
+
color: string;
|
|
653
|
+
font: string;
|
|
654
|
+
corner: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
655
|
+
interval: number;
|
|
656
|
+
/** Last measured frames per second. */
|
|
657
|
+
fps: number;
|
|
658
|
+
update(dt: number): void;
|
|
659
|
+
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export interface ObjUploaderOptions {
|
|
663
|
+
/** Size of the model's largest side; null keeps the file's units (default 1) */
|
|
664
|
+
fit?: number | null;
|
|
665
|
+
/** Color of loaded models (default: the scene's color) */
|
|
666
|
+
color?: string;
|
|
667
|
+
/** Remove the previously loaded model first (default true) */
|
|
668
|
+
replace?: boolean;
|
|
669
|
+
/** Text shown while nothing is loaded; "" for none */
|
|
670
|
+
hint?: string;
|
|
671
|
+
/** Called with the object after it's added */
|
|
672
|
+
onLoad?: (obj: SceneObject, file: File, scene: Scene) => void;
|
|
673
|
+
/** Called when a file can't be read */
|
|
674
|
+
onError?: (err: Error, file: File) => void;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Lets the user load .obj files into the scene (drag and drop, or `open()`). */
|
|
678
|
+
export class ObjUploader implements Tool {
|
|
679
|
+
constructor(opts?: ObjUploaderOptions);
|
|
680
|
+
fit: number | null;
|
|
681
|
+
color?: string;
|
|
682
|
+
replace: boolean;
|
|
683
|
+
hint: string;
|
|
684
|
+
onLoad?: (obj: SceneObject, file: File, scene: Scene) => void;
|
|
685
|
+
onError: (err: Error, file: File) => void;
|
|
686
|
+
/** The last object this tool added, or null. */
|
|
687
|
+
object: SceneObject | null;
|
|
688
|
+
/** Shows the file dialog and loads the chosen file. Call it from a user action. */
|
|
689
|
+
open(): Promise<SceneObject | null>;
|
|
690
|
+
/** Loads a .obj file into the scene. */
|
|
691
|
+
load(file: Blob): Promise<SceneObject | null>;
|
|
692
|
+
attach(scene: Scene): void;
|
|
693
|
+
detach(scene: Scene): void;
|
|
694
|
+
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export interface ScreenBox {
|
|
698
|
+
minX: number;
|
|
699
|
+
minY: number;
|
|
700
|
+
maxX: number;
|
|
701
|
+
maxY: number;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** Finds the object under a canvas pixel (by screen bounding box). */
|
|
705
|
+
export function pick(
|
|
706
|
+
scene: Scene,
|
|
707
|
+
x: number,
|
|
708
|
+
y: number,
|
|
709
|
+
opts?: { filter?: (obj: SceneObject) => boolean; padding?: number },
|
|
710
|
+
): SceneObject | null;
|
|
711
|
+
|
|
712
|
+
/** Screen-space bounding box of an object, or null if it's all behind the camera. */
|
|
713
|
+
export function screenBounds(scene: Scene, obj: SceneObject): ScreenBox | null;
|
|
714
|
+
|
|
715
|
+
/** World units one canvas pixel covers at a distance from the camera. */
|
|
716
|
+
export function worldPerPixel(scene: Scene, depth: number): number;
|
|
717
|
+
|
|
718
|
+
// ---------------------------------------------------------------------------
|
|
719
|
+
// Drawing and the frame loop
|
|
720
|
+
// ---------------------------------------------------------------------------
|
|
721
|
+
|
|
722
|
+
/** Fills the whole canvas with a color. */
|
|
723
|
+
export function clear(ctx: CanvasRenderingContext2D, w: number, h: number, color: string): void;
|
|
724
|
+
/** Draws a square point centered at a screen position. */
|
|
725
|
+
export function point(ctx: CanvasRenderingContext2D, p: Vec2, color: string, size?: number): void;
|
|
726
|
+
/** Draws a line between two screen points. */
|
|
727
|
+
export function line(ctx: CanvasRenderingContext2D, p1: Vec2, p2: Vec2, color: string, width?: number): void;
|
|
728
|
+
/** Draws edges between already-projected screen points. */
|
|
729
|
+
export function drawWireframe(
|
|
730
|
+
ctx: CanvasRenderingContext2D,
|
|
731
|
+
points2: Vec2[],
|
|
732
|
+
edges: number[][],
|
|
733
|
+
color: string,
|
|
734
|
+
width?: number,
|
|
735
|
+
): void;
|
|
736
|
+
/** Draws a mesh instance as a wireframe. */
|
|
737
|
+
export function drawInstance(
|
|
738
|
+
ctx: CanvasRenderingContext2D,
|
|
739
|
+
inst: { mesh: Mesh } & Transform,
|
|
740
|
+
camera: Camera,
|
|
741
|
+
w: number,
|
|
742
|
+
h: number,
|
|
743
|
+
color: string,
|
|
744
|
+
lineWidth?: number,
|
|
745
|
+
): void;
|
|
746
|
+
/** Turns edge definitions into pairs of vertex indices. */
|
|
747
|
+
export function edgeSegments(edges: number[][]): Generator<[number, number]>;
|
|
748
|
+
|
|
749
|
+
/** Runs `update` every animation frame. Returns a function that stops it. */
|
|
750
|
+
export function loop(update: (dt: number, time: number) => void, opts?: { maxDt?: number }): () => void;
|
|
751
|
+
|
|
752
|
+
/** Turns any CSS color into [r, g, b, a] (r, g, b in 0..255, a in 0..1). */
|
|
753
|
+
export function parseColor(ctx: CanvasRenderingContext2D, color: string): RGBA;
|
|
754
|
+
|
|
755
|
+
// ---------------------------------------------------------------------------
|
|
756
|
+
// Math
|
|
757
|
+
// ---------------------------------------------------------------------------
|
|
758
|
+
|
|
759
|
+
/** Default vertical field of view (90°). */
|
|
760
|
+
export const DEFAULT_FOV: number;
|
|
761
|
+
/** Default near plane distance. */
|
|
762
|
+
export const DEFAULT_NEAR: number;
|
|
763
|
+
|
|
764
|
+
export function localToWorld(v: Vec3, inst: Transform): Vec3;
|
|
765
|
+
export function worldToCamera(pWorld: Vec3, camera: Camera): Vec3;
|
|
766
|
+
export function cameraToScreen(pCam: Vec3, camera: Camera, w: number, h: number): Vec2;
|
|
767
|
+
export function worldToScreen(pWorld: Vec3, camera: Camera, w: number, h: number): Vec2;
|
|
768
|
+
export function cameraForward(camera: Camera): Vec3;
|
|
769
|
+
export function cameraRight(camera: Camera): Vec3;
|
|
770
|
+
export function cameraUp(camera: Camera): Vec3;
|
|
771
|
+
/** Clips a camera-space segment against the near plane. Null if fully behind. */
|
|
772
|
+
export function clipNear(a: Vec3, b: Vec3, near: number): [Vec3, Vec3] | null;
|
|
773
|
+
export function scale(p: Vec3, sx: number, sy: number, sz: number): Vec3;
|
|
774
|
+
export function translate(p: Vec3, dx: number, dy: number, dz: number): Vec3;
|
|
775
|
+
export function rotateX(p: Vec3, angle: number): Vec3;
|
|
776
|
+
export function rotateY(p: Vec3, angle: number): Vec3;
|
|
777
|
+
export function rotateZ(p: Vec3, angle: number): Vec3;
|
|
778
|
+
/** Rotates around X, then Y, then Z. */
|
|
779
|
+
export function rotateXYZ(p: Vec3, rot: Vec3): Vec3;
|
|
780
|
+
/** Perspective projection to normalized coordinates (-1..1). */
|
|
781
|
+
export function project(p: Vec3, fov?: number, aspect?: number): Vec2;
|
|
782
|
+
/** Normalized coordinates to canvas pixels. */
|
|
783
|
+
export function screen(p: Vec2, w: number, h: number): Vec2;
|