trid3d 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/package.json +1 -1
- package/trid/export/mtl.js +119 -0
- package/trid/export/obj.js +107 -21
- package/trid/index.d.ts +417 -8
- package/trid/index.js +19 -3
- package/trid/loaders/mtl.js +63 -0
- package/trid/loaders/obj.js +58 -6
- package/trid/loaders/texture.js +47 -6
- package/trid/math/matrix.js +200 -0
- package/trid/math/noise.js +162 -0
- package/trid/math/rotation.js +152 -0
- package/trid/render/canvasRenderer.js +40 -23
- package/trid/render/draw.js +6 -2
- package/trid/render/meshUtils.js +257 -0
- package/trid/render/proceduralTextures.js +211 -0
- package/trid/render/shapes.js +95 -27
- package/trid/render/webglRenderer.js +98 -27
- package/trid/scene.js +20 -9
- package/trid/sceneGraph.js +131 -0
- package/trid/tools/dragObject.js +33 -9
- package/trid/tools/helpers.js +109 -10
- package/trid/tools/hoverHighlight.js +4 -2
- package/trid/tools/index.js +3 -1
- package/trid/tools/orbitCamera.js +9 -0
- package/trid/tools/transformGizmo.js +316 -0
- package/trid/tools/viewGizmo.js +178 -0
package/trid/index.d.ts
CHANGED
|
@@ -106,6 +106,11 @@ export interface Material {
|
|
|
106
106
|
smooth?: boolean | number;
|
|
107
107
|
/** Image drawn on the faces (needs texture coordinates). `fill` tints it. */
|
|
108
108
|
texture?: TextureSource;
|
|
109
|
+
/**
|
|
110
|
+
* Read the texture again every frame (default: only videos). For a canvas redrawn every
|
|
111
|
+
* frame; for one redrawn now and then, call `updateTexture(canvas)` instead.
|
|
112
|
+
*/
|
|
113
|
+
liveTexture?: boolean;
|
|
109
114
|
/** Keep texture pixels sharp instead of smoothing them (default false) */
|
|
110
115
|
pixelated?: boolean;
|
|
111
116
|
/** Effects applied in order, like `[toon(), rim()]` */
|
|
@@ -129,12 +134,22 @@ export type MappingType = "uv" | "planar" | "box" | "sphere" | "cylinder";
|
|
|
129
134
|
*/
|
|
130
135
|
export type Mapping = MappingType | { type: MappingType; axis?: "x" | "y" | "z" };
|
|
131
136
|
|
|
132
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* What you pass to `scene.add`. Missing transform fields get defaults.
|
|
139
|
+
* Give it `children` to make a group (and leave out `mesh` for a group that only holds them).
|
|
140
|
+
*/
|
|
133
141
|
export interface SceneObjectInput {
|
|
134
|
-
|
|
142
|
+
/** What's drawn. Leave it out for a group that only holds `children` */
|
|
143
|
+
mesh?: Mesh;
|
|
135
144
|
pos?: Vec3;
|
|
136
145
|
rot?: Vec3;
|
|
137
146
|
scale?: Vec3;
|
|
147
|
+
/** Objects placed relative to this one: they move, turn and scale with it. Read every frame */
|
|
148
|
+
children?: SceneObjectInput[];
|
|
149
|
+
/** Set to false to hide this object and everything under it (default true) */
|
|
150
|
+
visible?: boolean;
|
|
151
|
+
/** A name, used by `exportOBJ` and handy for finding objects */
|
|
152
|
+
name?: string;
|
|
138
153
|
/** Wireframe color (default: the scene's color) */
|
|
139
154
|
color?: string;
|
|
140
155
|
/** Wireframe width (default: the scene's lineWidth) */
|
|
@@ -151,7 +166,12 @@ export interface SceneObjectInput {
|
|
|
151
166
|
* An object in a scene, after `scene.add` filled in its transform.
|
|
152
167
|
* Extra fields you add (like `{ mesh, name: "box" }`) are kept in the type.
|
|
153
168
|
*/
|
|
154
|
-
export type SceneObject<T extends SceneObjectInput = SceneObjectInput> = T & Transform
|
|
169
|
+
export type SceneObject<T extends SceneObjectInput = SceneObjectInput> = T & Transform & {
|
|
170
|
+
/** The group it's a child of, or null at the top. Kept up to date by the scene (not enumerable) */
|
|
171
|
+
parent: SceneObject | null;
|
|
172
|
+
/** World matrix from the last frame: parents' transforms × its own (not enumerable). See `worldMatrix` */
|
|
173
|
+
world: Mat4;
|
|
174
|
+
};
|
|
155
175
|
|
|
156
176
|
// ---------------------------------------------------------------------------
|
|
157
177
|
// Scene
|
|
@@ -298,7 +318,16 @@ export interface Point2 {
|
|
|
298
318
|
* Turns a flat outline in the XY plane into a solid `depth` thick along Z
|
|
299
319
|
* (default 0.2), centered on the origin. Either direction, concave is fine, no holes.
|
|
300
320
|
*/
|
|
301
|
-
export
|
|
321
|
+
export interface ExtrudeOptions {
|
|
322
|
+
/** How much the edges between sides and front / back are cut, up to half the depth (default 0) */
|
|
323
|
+
bevel?: number;
|
|
324
|
+
/** Steps around a bevel: 1 = flat chamfer, more = rounded (default 1) */
|
|
325
|
+
bevelSegments?: number;
|
|
326
|
+
/** Size of the back compared to the front, around the outline's middle (default 1) */
|
|
327
|
+
taper?: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function extrude(outline: Point2[], depth?: number, opts?: ExtrudeOptions): Mesh;
|
|
302
331
|
|
|
303
332
|
/** Cuts a counter-clockwise polygon into counter-clockwise triangles (ear clipping). */
|
|
304
333
|
export function triangulate(pts: Point2[]): number[][];
|
|
@@ -306,6 +335,53 @@ export function triangulate(pts: Point2[]): number[][];
|
|
|
306
335
|
/** Scales a mesh's vertices in place. Returns the same mesh. */
|
|
307
336
|
export function scaleMesh<M extends { vertices: Vec3[] }>(mesh: M, sx?: number, sy?: number, sz?: number): M;
|
|
308
337
|
|
|
338
|
+
/** A transform to bake into a mesh; parts left out change nothing. A scene object fits. */
|
|
339
|
+
export interface MeshTransform {
|
|
340
|
+
pos?: Partial<Vec3>;
|
|
341
|
+
rot?: Partial<Vec3>;
|
|
342
|
+
scale?: Partial<Vec3>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Deep copy of a mesh (vertices, edges, faces, UVs, normals, materials). */
|
|
346
|
+
export function cloneMesh<M extends Mesh>(mesh: M): M;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Bakes a transform into a mesh's vertices, in place (scale, rotate X→Y→Z, move).
|
|
350
|
+
* Normals turn with it; a mirroring scale reverses the faces. Returns the same mesh.
|
|
351
|
+
*/
|
|
352
|
+
export function transformMesh<M extends Mesh>(mesh: M, transform: MeshTransform): M;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Moves every vertex to `fn(vertex, index)`, in place. Removes the mesh's own normals
|
|
356
|
+
* (they no longer fit), so the renderers compute new ones. Returns the same mesh.
|
|
357
|
+
*/
|
|
358
|
+
export function displaceMesh<M extends Mesh>(mesh: M, fn: (v: Vec3, index: number) => Vec3): M;
|
|
359
|
+
|
|
360
|
+
export interface MergeOptions {
|
|
361
|
+
/** Faces of the i-th mesh use material i (default false: keep each mesh's faceMaterials) */
|
|
362
|
+
materialPerMesh?: boolean;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Joins meshes into a new one, keeping faces, edges, UVs, normals and materials.
|
|
367
|
+
* Scene objects in the list are merged where they stand (transform baked in).
|
|
368
|
+
*/
|
|
369
|
+
export function mergeMeshes(items: (Mesh | SceneObjectInput)[], opts?: MergeOptions): Mesh;
|
|
370
|
+
|
|
371
|
+
/** A box around some geometry. */
|
|
372
|
+
export interface Bounds {
|
|
373
|
+
min: Vec3;
|
|
374
|
+
max: Vec3;
|
|
375
|
+
center: Vec3;
|
|
376
|
+
size: Vec3;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* The box around a mesh, a scene object, a list of them or a Scene (objects measured
|
|
381
|
+
* where they stand). Null when there are no vertices.
|
|
382
|
+
*/
|
|
383
|
+
export function meshBounds(input: Mesh | SceneObjectInput | (Mesh | SceneObjectInput)[] | Scene): Bounds | null;
|
|
384
|
+
|
|
309
385
|
// ---------------------------------------------------------------------------
|
|
310
386
|
// Renderers
|
|
311
387
|
// ---------------------------------------------------------------------------
|
|
@@ -338,6 +414,8 @@ export class CanvasRenderer implements Renderer {
|
|
|
338
414
|
export interface WebGLRendererOptions {
|
|
339
415
|
/** Smooth edges (default true) */
|
|
340
416
|
antialias?: boolean;
|
|
417
|
+
/** Free meshes and textures not drawn for this many frames (default 300, about 5 s). Infinity: never */
|
|
418
|
+
releaseAfter?: number;
|
|
341
419
|
}
|
|
342
420
|
|
|
343
421
|
/** Draws a scene with WebGL and a depth buffer. */
|
|
@@ -349,7 +427,13 @@ export class WebGLRenderer implements Renderer {
|
|
|
349
427
|
/** The hidden canvas WebGL draws on. */
|
|
350
428
|
canvas: HTMLCanvasElement;
|
|
351
429
|
gl: WebGLRenderingContext | WebGL2RenderingContext;
|
|
430
|
+
/** Frames a mesh or texture can go undrawn before it's freed (Infinity: never). */
|
|
431
|
+
releaseAfter: number;
|
|
432
|
+
/** What's on the graphics card right now. */
|
|
433
|
+
readonly memory: { meshes: number; buffers: number; textures: number };
|
|
352
434
|
render(scene: Scene): void;
|
|
435
|
+
/** Frees a mesh's buffers or a texture now; it's sent again if drawn again. */
|
|
436
|
+
release(item: Mesh | TextureSource): void;
|
|
353
437
|
/** Frees the graphics card memory. The renderer can't be used afterwards. */
|
|
354
438
|
dispose(): void;
|
|
355
439
|
}
|
|
@@ -535,15 +619,36 @@ export function newell(pts: Vec3[]): Vec3;
|
|
|
535
619
|
/** A mesh loaded from an OBJ file. Always has faces. */
|
|
536
620
|
export interface ObjMesh extends Mesh {
|
|
537
621
|
faces: number[][];
|
|
538
|
-
/** Material names from `usemtl` lines,
|
|
622
|
+
/** Material names from `usemtl` lines, in the order they first appear */
|
|
539
623
|
materialNames?: string[];
|
|
624
|
+
/** The .mtl file the OBJ names (`mtllib`) */
|
|
625
|
+
mtllib?: string;
|
|
626
|
+
/** With `loadOBJ(url, { materials: true })`: one material per `materialNames` entry, from the .mtl file */
|
|
627
|
+
materials?: Material[];
|
|
540
628
|
}
|
|
541
629
|
|
|
542
630
|
export interface ObjOptions {
|
|
543
631
|
/** Center the model and scale it so its largest side is this long */
|
|
544
632
|
fit?: number;
|
|
633
|
+
/** `loadOBJ` only: also load the .mtl file and its textures into `mesh.materials` (default false) */
|
|
634
|
+
materials?: boolean;
|
|
545
635
|
}
|
|
546
636
|
|
|
637
|
+
/** A material from an MTL file. */
|
|
638
|
+
export interface MtlMaterial {
|
|
639
|
+
name: string;
|
|
640
|
+
/** "#rrggbb" from `Kd`, or "rgba(...)" when see-through */
|
|
641
|
+
fill?: string;
|
|
642
|
+
/** From `d` / `Tr`, 0 to 1 */
|
|
643
|
+
opacity?: number;
|
|
644
|
+
/** The texture's file name (`map_Kd`), relative to the .mtl file */
|
|
645
|
+
textureFile?: string;
|
|
646
|
+
}
|
|
647
|
+
/** Parses the text of an MTL file: materials by name. */
|
|
648
|
+
export function parseMTL(text: string): Record<string, MtlMaterial>;
|
|
649
|
+
/** The materials of a parsed OBJ from its .mtl file (one per `materialNames` entry), textures loaded. */
|
|
650
|
+
export function loadMaterials(mesh: ObjMesh, base: URL | string): Promise<Material[]>;
|
|
651
|
+
|
|
547
652
|
/** Parses the text of an OBJ file. */
|
|
548
653
|
export function parseOBJ(text: string, opts?: ObjOptions): ObjMesh;
|
|
549
654
|
/** Downloads an OBJ file and parses it. */
|
|
@@ -561,8 +666,15 @@ export function loadTexture(url: string): Promise<HTMLImageElement>;
|
|
|
561
666
|
export function textureReady(src: TextureSource | null | undefined): boolean;
|
|
562
667
|
/** Pixel size of a texture source. */
|
|
563
668
|
export function textureSize(src: TextureSource): { w: number; h: number };
|
|
564
|
-
/** Whether a
|
|
565
|
-
export function textureIsLive(src: TextureSource): boolean;
|
|
669
|
+
/** Whether a texture is read again every frame: videos, or any source whose material has `liveTexture: true`. */
|
|
670
|
+
export function textureIsLive(src: TextureSource, material?: { liveTexture?: boolean }): boolean;
|
|
671
|
+
/**
|
|
672
|
+
* Tells the renderers a texture changed (call it after drawing on a canvas used as a texture):
|
|
673
|
+
* it's read again once, on the next frame. Canvases are otherwise read only once.
|
|
674
|
+
*/
|
|
675
|
+
export function updateTexture(src: TextureSource): void;
|
|
676
|
+
/** How many times `updateTexture` was called for a source (0 when never). */
|
|
677
|
+
export function textureVersion(src: TextureSource): number;
|
|
566
678
|
|
|
567
679
|
// ---------------------------------------------------------------------------
|
|
568
680
|
// Exporting
|
|
@@ -582,8 +694,37 @@ export interface ExportOBJOptions {
|
|
|
582
694
|
precision?: number;
|
|
583
695
|
/** Comment at the top of the file; "" for none (default "Exported by Trid") */
|
|
584
696
|
header?: string;
|
|
697
|
+
/** Link this .mtl file and name every material (the names `exportMTL` writes) */
|
|
698
|
+
mtllib?: string;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** A material description for `exportMTL`. */
|
|
702
|
+
export interface MtlDescription {
|
|
703
|
+
name: string;
|
|
704
|
+
fill?: string;
|
|
705
|
+
opacity?: number;
|
|
706
|
+
/** Texture file name */
|
|
707
|
+
texture?: string;
|
|
585
708
|
}
|
|
586
709
|
|
|
710
|
+
/** The materials an export names, with each texture's file name. */
|
|
711
|
+
export function objMaterials(
|
|
712
|
+
input: OBJExportable | OBJExportable[] | Scene,
|
|
713
|
+
opts?: { textureFile?: (name: string, texture: TextureSource) => string },
|
|
714
|
+
): { name: string; material: Material | null; texture: TextureSource | null; file: string | null }[];
|
|
715
|
+
/** Writes an MTL file: for what `exportOBJ` takes (same names), or for plain descriptions. */
|
|
716
|
+
export function exportMTL(
|
|
717
|
+
input: OBJExportable | OBJExportable[] | Scene | MtlDescription[],
|
|
718
|
+
opts?: { textureFile?: (name: string, texture: TextureSource) => string; header?: string },
|
|
719
|
+
): string;
|
|
720
|
+
/** A texture image as a PNG (or other) file. */
|
|
721
|
+
export function textureToBlob(src: TextureSource, type?: string): Promise<Blob>;
|
|
722
|
+
/** Every file of a model: name.obj, name.mtl and one PNG per texture. */
|
|
723
|
+
export function exportModel(
|
|
724
|
+
input: OBJExportable | OBJExportable[] | Scene,
|
|
725
|
+
opts?: { name?: string; obj?: ExportOBJOptions },
|
|
726
|
+
): Promise<{ name: string; data: string | Blob }[]>;
|
|
727
|
+
|
|
587
728
|
/** Something exportOBJ can write: a mesh, or an object placed in a scene. */
|
|
588
729
|
export type OBJExportable = Mesh | (SceneObjectInput & Partial<Transform> & { name?: string });
|
|
589
730
|
|
|
@@ -754,6 +895,8 @@ export class OrbitCamera implements Tool {
|
|
|
754
895
|
blockContextMenu: boolean;
|
|
755
896
|
/** Places the camera from the target, distance, yaw and pitch. */
|
|
756
897
|
apply(camera: Camera): void;
|
|
898
|
+
/** Restarts the auto-rotate wait, as if the user had just dragged (for tools that turn the camera). */
|
|
899
|
+
holdAutoRotate(): void;
|
|
757
900
|
attach(scene: Scene): void;
|
|
758
901
|
pointerDown(e: ToolPointerEvent): boolean;
|
|
759
902
|
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
@@ -792,6 +935,10 @@ export interface DragObjectOptions {
|
|
|
792
935
|
button?: number;
|
|
793
936
|
/** Which objects can be dragged (default: those without `draggable: false`) */
|
|
794
937
|
filter?: (obj: SceneObject) => boolean;
|
|
938
|
+
/** Pick by triangles instead of bounding boxes (default false, see `pickHit`) */
|
|
939
|
+
exact?: boolean;
|
|
940
|
+
/** Drag the top-level object (the whole group) of what was clicked (default false) */
|
|
941
|
+
root?: boolean;
|
|
795
942
|
/** Called when an object is picked */
|
|
796
943
|
onPick?: (obj: SceneObject) => void;
|
|
797
944
|
/** Called each time the object moves */
|
|
@@ -805,6 +952,8 @@ export class DragObject implements Tool {
|
|
|
805
952
|
constructor(opts?: DragObjectOptions);
|
|
806
953
|
button: number;
|
|
807
954
|
filter: (obj: SceneObject) => boolean;
|
|
955
|
+
exact: boolean;
|
|
956
|
+
root: boolean;
|
|
808
957
|
onPick?: (obj: SceneObject) => void;
|
|
809
958
|
onMove?: (obj: SceneObject) => void;
|
|
810
959
|
onDrop?: (obj: SceneObject) => void;
|
|
@@ -821,6 +970,8 @@ export interface HoverHighlightOptions {
|
|
|
821
970
|
color?: string;
|
|
822
971
|
/** Which objects can be highlighted (default: those without `hoverable: false`) */
|
|
823
972
|
filter?: (obj: SceneObject) => boolean;
|
|
973
|
+
/** Pick by triangles instead of bounding boxes (default false, see `pickHit`) */
|
|
974
|
+
exact?: boolean;
|
|
824
975
|
/** Called when the hovered object changes */
|
|
825
976
|
onChange?: (obj: SceneObject | null) => void;
|
|
826
977
|
}
|
|
@@ -830,6 +981,7 @@ export class HoverHighlight implements Tool {
|
|
|
830
981
|
constructor(opts?: HoverHighlightOptions);
|
|
831
982
|
color: string;
|
|
832
983
|
filter: (obj: SceneObject) => boolean;
|
|
984
|
+
exact: boolean;
|
|
833
985
|
onChange?: (obj: SceneObject | null) => void;
|
|
834
986
|
/** The object under the pointer, or null. */
|
|
835
987
|
hovered: SceneObject | null;
|
|
@@ -897,6 +1049,97 @@ export class ObjUploader implements Tool {
|
|
|
897
1049
|
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
898
1050
|
}
|
|
899
1051
|
|
|
1052
|
+
export interface ViewGizmoOptions {
|
|
1053
|
+
/** Camera controls to turn (default: the scene's first OrbitCamera). Without one, it only shows the axes */
|
|
1054
|
+
orbit?: OrbitCamera;
|
|
1055
|
+
/** Where it sits (default "top-right") */
|
|
1056
|
+
corner?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
|
|
1057
|
+
/** Radius in CSS pixels (default 42) */
|
|
1058
|
+
size?: number;
|
|
1059
|
+
/** Distance from the canvas corner in CSS pixels (default 14) */
|
|
1060
|
+
margin?: number | { x: number; y: number };
|
|
1061
|
+
/** Seconds the camera takes to turn to a clicked axis (default 0.35) */
|
|
1062
|
+
duration?: number;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* The X, Y, Z axes in a corner, turning with the camera; click one to look along it.
|
|
1067
|
+
* Add it after the OrbitCamera.
|
|
1068
|
+
*/
|
|
1069
|
+
export class ViewGizmo implements Tool {
|
|
1070
|
+
constructor(opts?: ViewGizmoOptions);
|
|
1071
|
+
orbit: OrbitCamera | null;
|
|
1072
|
+
corner: "top-right" | "top-left" | "bottom-right" | "bottom-left";
|
|
1073
|
+
size: number;
|
|
1074
|
+
margin: { x: number; y: number };
|
|
1075
|
+
duration: number;
|
|
1076
|
+
/** Hide or show the gizmo without removing the tool. */
|
|
1077
|
+
visible: boolean;
|
|
1078
|
+
/** Whether a canvas pixel is on the gizmo (as last drawn). */
|
|
1079
|
+
contains(x: number, y: number): boolean;
|
|
1080
|
+
/** Turns the camera smoothly to look at its target from `dir` ({ x: 0, y: 1, z: 0 }: from above). */
|
|
1081
|
+
lookAlong(dir: Vec3): void;
|
|
1082
|
+
attach(scene: Scene): void;
|
|
1083
|
+
detach(): void;
|
|
1084
|
+
pointerDown(e: ToolPointerEvent): boolean;
|
|
1085
|
+
update(dt: number, scene: Scene): void;
|
|
1086
|
+
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export type TransformMode = "move" | "rotate" | "scale";
|
|
1090
|
+
|
|
1091
|
+
/** A move, rotate or scale in world space, as `TransformGizmo` reports it. */
|
|
1092
|
+
export type TransformOp =
|
|
1093
|
+
| { mode: "move"; delta: Vec3 }
|
|
1094
|
+
/** A right-handed turn of `angle` radians around the unit `axis`, about `center` */
|
|
1095
|
+
| { mode: "rotate"; axis: Vec3; angle: number; center: Vec3 }
|
|
1096
|
+
/** `factor` about `center`: uniform when `axis` is null, otherwise along that unit axis */
|
|
1097
|
+
| { mode: "scale"; axis: Vec3 | null; factor: number; center: Vec3 };
|
|
1098
|
+
|
|
1099
|
+
export interface TransformGizmoOptions {
|
|
1100
|
+
/** Default "move" */
|
|
1101
|
+
mode?: TransformMode;
|
|
1102
|
+
/** Objects it edits (default none) */
|
|
1103
|
+
targets?: SceneObject[];
|
|
1104
|
+
/** Handle length in CSS pixels (default 70) */
|
|
1105
|
+
size?: number;
|
|
1106
|
+
/** Change the targets' transforms while dragging (default true) */
|
|
1107
|
+
apply?: boolean;
|
|
1108
|
+
/** A handle was grabbed */
|
|
1109
|
+
onGrab?: (targets: SceneObject[]) => void;
|
|
1110
|
+
/** While dragging: the operation since the grab */
|
|
1111
|
+
onChange?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1112
|
+
/** On release: the whole operation */
|
|
1113
|
+
onDrop?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Move arrows, rotate rings or scale handles on one or more objects, around their
|
|
1118
|
+
* shared center. Add it after the camera tool.
|
|
1119
|
+
*/
|
|
1120
|
+
export class TransformGizmo implements Tool {
|
|
1121
|
+
constructor(opts?: TransformGizmoOptions);
|
|
1122
|
+
mode: TransformMode;
|
|
1123
|
+
targets: SceneObject[];
|
|
1124
|
+
size: number;
|
|
1125
|
+
apply: boolean;
|
|
1126
|
+
onGrab?: (targets: SceneObject[]) => void;
|
|
1127
|
+
onChange?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1128
|
+
onDrop?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1129
|
+
/** Hide the handles (and ignore clicks) without removing the tool. */
|
|
1130
|
+
visible: boolean;
|
|
1131
|
+
/** True while a handle is being dragged. */
|
|
1132
|
+
readonly dragging: boolean;
|
|
1133
|
+
/** Middle of the targets' bounding box in world space, or null. */
|
|
1134
|
+
center(): Vec3 | null;
|
|
1135
|
+
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
1136
|
+
pointerDown(e: ToolPointerEvent, scene: Scene): boolean;
|
|
1137
|
+
pointerMove(e: ToolPointerEvent, scene: Scene): void;
|
|
1138
|
+
pointerUp(): void;
|
|
1139
|
+
pointerLeave(scene: Scene): void;
|
|
1140
|
+
detach(scene: Scene): void;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
900
1143
|
export interface ScreenBox {
|
|
901
1144
|
minX: number;
|
|
902
1145
|
minY: number;
|
|
@@ -909,9 +1152,31 @@ export function pick(
|
|
|
909
1152
|
scene: Scene,
|
|
910
1153
|
x: number,
|
|
911
1154
|
y: number,
|
|
912
|
-
opts?: { filter?: (obj: SceneObject) => boolean; padding?: number },
|
|
1155
|
+
opts?: { filter?: (obj: SceneObject) => boolean; padding?: number; exact?: boolean },
|
|
913
1156
|
): SceneObject | null;
|
|
914
1157
|
|
|
1158
|
+
/** What `pickHit` found. */
|
|
1159
|
+
export interface PickHit {
|
|
1160
|
+
object: SceneObject;
|
|
1161
|
+
/** Index in `mesh.faces`, or -1 for a line of a wireframe */
|
|
1162
|
+
face: number;
|
|
1163
|
+
/** Distance in front of the camera */
|
|
1164
|
+
depth: number;
|
|
1165
|
+
/** The object, then its groups up to the top: `[part, group, ..., root]` */
|
|
1166
|
+
path: SceneObject[];
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Exact picking: the nearest object whose triangles (or, without faces, lines
|
|
1171
|
+
* within `padding` pixels) are under a canvas pixel.
|
|
1172
|
+
*/
|
|
1173
|
+
export function pickHit(
|
|
1174
|
+
scene: Scene,
|
|
1175
|
+
x: number,
|
|
1176
|
+
y: number,
|
|
1177
|
+
opts?: { filter?: (obj: SceneObject) => boolean; padding?: number; objects?: SceneObject[] },
|
|
1178
|
+
): PickHit | null;
|
|
1179
|
+
|
|
915
1180
|
/** Screen-space bounding box of an object, or null if it's all behind the camera. */
|
|
916
1181
|
export function screenBounds(scene: Scene, obj: SceneObject): ScreenBox | null;
|
|
917
1182
|
|
|
@@ -945,6 +1210,8 @@ export function drawInstance(
|
|
|
945
1210
|
h: number,
|
|
946
1211
|
color: string,
|
|
947
1212
|
lineWidth?: number,
|
|
1213
|
+
/** World matrix to place it with (e.g. `obj.world` inside a group); default: its own transform */
|
|
1214
|
+
world?: Mat4,
|
|
948
1215
|
): void;
|
|
949
1216
|
/** Turns edge definitions into pairs of vertex indices. */
|
|
950
1217
|
export function edgeSegments(edges?: number[][]): Generator<[number, number]>;
|
|
@@ -986,3 +1253,145 @@ export function rotateXYZ(p: Vec3, rot: Vec3): Vec3;
|
|
|
986
1253
|
export function project(p: Vec3, fov?: number, aspect?: number): Vec2;
|
|
987
1254
|
/** Normalized coordinates to canvas pixels. */
|
|
988
1255
|
export function screen(p: Vec2, w: number, h: number): Vec2;
|
|
1256
|
+
|
|
1257
|
+
// ---------------------------------------------------------------------------
|
|
1258
|
+
// Rotations
|
|
1259
|
+
// ---------------------------------------------------------------------------
|
|
1260
|
+
|
|
1261
|
+
/** 3×3 matrix, row-major (m[row][col]), applied as M · v. */
|
|
1262
|
+
export type Mat3 = [[number, number, number], [number, number, number], [number, number, number]];
|
|
1263
|
+
|
|
1264
|
+
/** The matrix `rotateXYZ(p, rot)` applies. */
|
|
1265
|
+
export function matFromEuler(rot: Vec3): Mat3;
|
|
1266
|
+
/** Angles for `rotateXYZ` (an object's `rot`) from a rotation matrix; the inverse of `matFromEuler`. */
|
|
1267
|
+
export function eulerFromMat(m: Mat3): Vec3;
|
|
1268
|
+
/** A right-handed turn of `angle` radians around a unit `axis`. */
|
|
1269
|
+
export function matFromAxisAngle(axis: Vec3, angle: number): Mat3;
|
|
1270
|
+
/** Matrix product a · b (b is applied first). */
|
|
1271
|
+
export function mulMat(a: Mat3, b: Mat3): Mat3;
|
|
1272
|
+
/** m · v */
|
|
1273
|
+
export function mulVec(m: Mat3, v: Vec3): Vec3;
|
|
1274
|
+
/** Applies a world-space move / rotate / scale to a transform and returns the new one. */
|
|
1275
|
+
export function applyTransformOp(t: Transform, op: TransformOp): Transform;
|
|
1276
|
+
|
|
1277
|
+
// ---------------------------------------------------------------------------
|
|
1278
|
+
// Procedural textures
|
|
1279
|
+
// ---------------------------------------------------------------------------
|
|
1280
|
+
|
|
1281
|
+
export type TextureStyle = "noise" | "marble" | "wood" | "bricks" | "checker" | "bark" | "spots" | "stripes" | "solid";
|
|
1282
|
+
/** The built-in styles of `makeTexture`. */
|
|
1283
|
+
export const TEXTURE_STYLES: TextureStyle[];
|
|
1284
|
+
|
|
1285
|
+
export interface MakeTextureOptions {
|
|
1286
|
+
/** Width and height in pixels (default 256) */
|
|
1287
|
+
size?: number;
|
|
1288
|
+
/** [first, second]: hex, rgb() / rgba(), CSS names or [r, g, b, a?] (default: suits the style) */
|
|
1289
|
+
colors?: (string | number[])[];
|
|
1290
|
+
/** How many times the pattern repeats across, a whole number (default 4) */
|
|
1291
|
+
scale?: number;
|
|
1292
|
+
/** Another whole number gives another texture of the same style (default 0) */
|
|
1293
|
+
seed?: number;
|
|
1294
|
+
/** Fine speckles on top, 0 to 1 (default 0) */
|
|
1295
|
+
grain?: number;
|
|
1296
|
+
/** Paint into this canvas instead of a new one (calls `updateTexture` for you) */
|
|
1297
|
+
canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Paints a seamless procedural texture on a canvas. `style` is a built-in style, or
|
|
1302
|
+
* `(u, v) => t` (0: first color, 1: second).
|
|
1303
|
+
*/
|
|
1304
|
+
export function makeTexture(
|
|
1305
|
+
style?: TextureStyle | ((u: number, v: number) => number),
|
|
1306
|
+
opts?: MakeTextureOptions,
|
|
1307
|
+
): HTMLCanvasElement | OffscreenCanvas;
|
|
1308
|
+
/** The pixels of a procedural texture (RGBA, top row first), without a canvas. */
|
|
1309
|
+
export function texturePixels(
|
|
1310
|
+
style?: TextureStyle | ((u: number, v: number) => number),
|
|
1311
|
+
opts?: Omit<MakeTextureOptions, "canvas">,
|
|
1312
|
+
): { width: number; height: number; data: Uint8ClampedArray };
|
|
1313
|
+
/** [r, g, b, a] (0..255, a 0..1) from a color string or array. */
|
|
1314
|
+
export function colorBytes(color: string | number[]): [number, number, number, number];
|
|
1315
|
+
|
|
1316
|
+
// ---------------------------------------------------------------------------
|
|
1317
|
+
// Groups (scene graph)
|
|
1318
|
+
// ---------------------------------------------------------------------------
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Every visible object of a scene (or of objects and their subtrees), parents first,
|
|
1322
|
+
* with `parent` and `world` set. Groups without a mesh are included.
|
|
1323
|
+
*/
|
|
1324
|
+
export function worldObjects(input: Scene | SceneObjectInput | SceneObjectInput[]): SceneObject[];
|
|
1325
|
+
/** An object's world matrix right now, from its transform and its parents'. */
|
|
1326
|
+
export function worldMatrix(obj: SceneObjectInput): Mat4;
|
|
1327
|
+
/** Where an object's origin is in the world right now. */
|
|
1328
|
+
export function worldPosition(obj: SceneObjectInput): Vec3;
|
|
1329
|
+
/** The chain from an object up to the top: `[obj, parent, ..., root]`. */
|
|
1330
|
+
export function ancestors(obj: SceneObject): SceneObject[];
|
|
1331
|
+
/**
|
|
1332
|
+
* Calls `fn(obj, world, parent)` for every visible object under some roots, parents first,
|
|
1333
|
+
* without changing anything. `baseOf(root)` is the matrix a root goes under (null: none).
|
|
1334
|
+
*/
|
|
1335
|
+
export function forEachPlaced(
|
|
1336
|
+
roots: SceneObjectInput[],
|
|
1337
|
+
baseOf: (root: SceneObjectInput) => Mat4 | null,
|
|
1338
|
+
fn: (obj: SceneObjectInput, world: Mat4, parent: SceneObjectInput | null) => void,
|
|
1339
|
+
): void;
|
|
1340
|
+
|
|
1341
|
+
// ---------------------------------------------------------------------------
|
|
1342
|
+
// Matrices
|
|
1343
|
+
// ---------------------------------------------------------------------------
|
|
1344
|
+
|
|
1345
|
+
/** 4×4 transform matrix, 16 numbers in column-major order (`m[col * 4 + row]`), like WebGL. */
|
|
1346
|
+
export type Mat4 = number[];
|
|
1347
|
+
|
|
1348
|
+
/** The identity matrix. */
|
|
1349
|
+
export function identityMat4(): Mat4;
|
|
1350
|
+
/** The matrix of a transform: scale, rotate X → Y → Z, then move. */
|
|
1351
|
+
export function mat4FromTransform(t: MeshTransform): Mat4;
|
|
1352
|
+
/** a · b (b applied first): a child's world matrix is `mulMat4(parentWorld, childLocal)`. */
|
|
1353
|
+
export function mulMat4(a: Mat4, b: Mat4): Mat4;
|
|
1354
|
+
/** Inverse of a transform matrix (identity if it flattens space). */
|
|
1355
|
+
export function invertMat4(m: Mat4): Mat4;
|
|
1356
|
+
/** Determinant of the 3×3 part: < 0 when the matrix mirrors. */
|
|
1357
|
+
export function determinantMat4(m: Mat4): number;
|
|
1358
|
+
/** A point through a matrix. */
|
|
1359
|
+
export function transformPoint(m: Mat4, p: Vec3): Vec3;
|
|
1360
|
+
/** A direction or offset through a matrix (not moved). */
|
|
1361
|
+
export function transformDirection(m: Mat4, v: Vec3): Vec3;
|
|
1362
|
+
/** The 3×3 normal matrix (inverse transpose), column-major. */
|
|
1363
|
+
export function normalMatrix(m: Mat4): number[];
|
|
1364
|
+
/** A normal through a normal matrix, unit length. */
|
|
1365
|
+
export function transformNormal(nm: number[], n: Vec3): Vec3;
|
|
1366
|
+
/** A world-space `TransformOp` in a parent's space, for objects inside groups. */
|
|
1367
|
+
export function transformOpToLocal(op: TransformOp, parentWorld: Mat4): TransformOp;
|
|
1368
|
+
|
|
1369
|
+
// ---------------------------------------------------------------------------
|
|
1370
|
+
// Random numbers and noise
|
|
1371
|
+
// ---------------------------------------------------------------------------
|
|
1372
|
+
|
|
1373
|
+
/** A seeded random generator (mulberry32): each call returns the next number in [0, 1). */
|
|
1374
|
+
export function rng(seed?: number): () => number;
|
|
1375
|
+
|
|
1376
|
+
export interface NoiseOptions {
|
|
1377
|
+
/** Whole number; another seed gives other noise (default 0) */
|
|
1378
|
+
seed?: number;
|
|
1379
|
+
/** Repeat every this many units, a whole number, for seamless textures (default 0: never) */
|
|
1380
|
+
period?: number;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
export interface FbmOptions extends NoiseOptions {
|
|
1384
|
+
/** Layers of detail (default 5) */
|
|
1385
|
+
octaves?: number;
|
|
1386
|
+
/** Strength of each layer compared to the one before (default 0.5) */
|
|
1387
|
+
gain?: number;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** Smooth 2D value noise in [0, 1). */
|
|
1391
|
+
export function noise2(x: number, y: number, opts?: NoiseOptions): number;
|
|
1392
|
+
/** Smooth 3D value noise in [0, 1). */
|
|
1393
|
+
export function noise3(x: number, y: number, z: number, opts?: NoiseOptions): number;
|
|
1394
|
+
/** Fractal 2D noise: octaves of noise2, each twice as fine. About [0, 1), averaging 0.5. */
|
|
1395
|
+
export function fbm2(x: number, y: number, opts?: FbmOptions): number;
|
|
1396
|
+
/** Fractal 3D noise: octaves of noise3, each twice as fine. About [0, 1), averaging 0.5. */
|
|
1397
|
+
export function fbm3(x: number, y: number, z: number, opts?: FbmOptions): number;
|
package/trid/index.js
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
// Scene
|
|
13
13
|
export { Scene } from "./scene.js";
|
|
14
|
+
export { worldObjects, worldMatrix, worldPosition, ancestors, forEachPlaced } from "./sceneGraph.js";
|
|
14
15
|
|
|
15
16
|
// Shapes and meshes
|
|
16
17
|
export { makeShape, scaleMesh, lathe, extrude, triangulate } from "./render/shapes.js";
|
|
18
|
+
export { cloneMesh, transformMesh, displaceMesh, mergeMeshes, meshBounds } from "./render/meshUtils.js";
|
|
17
19
|
|
|
18
20
|
// Renderers
|
|
19
21
|
export { CanvasRenderer, DEFAULT_LIGHT, DEFAULT_AMBIENT, materialsOf } from "./render/canvasRenderer.js";
|
|
@@ -25,6 +27,9 @@ export {
|
|
|
25
27
|
buildEffects, effectsKey, uniformType, SURFACE_GLSL,
|
|
26
28
|
} from "./render/effects.js";
|
|
27
29
|
|
|
30
|
+
// Procedural textures
|
|
31
|
+
export { makeTexture, texturePixels, colorBytes, TEXTURE_STYLES } from "./render/proceduralTextures.js";
|
|
32
|
+
|
|
28
33
|
// Texture mapping
|
|
29
34
|
export { computeUVs, mapUVs, uvsFor, uvTransform, applyUVTransform, mappingOf, mappingKey, MAPPINGS } from "./render/uvMapping.js";
|
|
30
35
|
|
|
@@ -32,17 +37,20 @@ export { computeUVs, mapUVs, uvsFor, uvTransform, applyUVTransform, mappingOf, m
|
|
|
32
37
|
export { cornerNormals, creaseOf, newell, DEFAULT_CREASE } from "./render/normals.js";
|
|
33
38
|
|
|
34
39
|
// Loading files
|
|
35
|
-
export { parseOBJ, loadOBJ, readOBJFile, pickOBJFile, fitMesh } from "./loaders/obj.js";
|
|
36
|
-
export {
|
|
40
|
+
export { parseOBJ, loadOBJ, loadMaterials, readOBJFile, pickOBJFile, fitMesh } from "./loaders/obj.js";
|
|
41
|
+
export { parseMTL } from "./loaders/mtl.js";
|
|
42
|
+
export { loadTexture, textureReady, textureSize, textureIsLive, updateTexture, textureVersion } from "./loaders/texture.js";
|
|
37
43
|
|
|
38
44
|
// Exporting
|
|
39
45
|
export { exportOBJ } from "./export/obj.js";
|
|
46
|
+
export { exportMTL, objMaterials, exportModel, textureToBlob } from "./export/mtl.js";
|
|
40
47
|
export { screenshot, record, download } from "./export/capture.js";
|
|
41
48
|
|
|
42
49
|
// Tools
|
|
43
50
|
export {
|
|
44
51
|
OrbitCamera, FlyCamera, DragObject, HoverHighlight, FpsMeter, ObjUploader,
|
|
45
|
-
|
|
52
|
+
ViewGizmo, TransformGizmo,
|
|
53
|
+
ToolManager, pick, pickHit, screenBounds, worldPerPixel,
|
|
46
54
|
} from "./tools/index.js";
|
|
47
55
|
|
|
48
56
|
// Drawing and the frame loop
|
|
@@ -58,3 +66,11 @@ export {
|
|
|
58
66
|
DEFAULT_FOV, DEFAULT_NEAR,
|
|
59
67
|
} from "./math/transform.js";
|
|
60
68
|
export { project, screen } from "./math/projection.js";
|
|
69
|
+
export { matFromEuler, eulerFromMat, matFromAxisAngle, mulMat, mulVec, applyTransformOp } from "./math/rotation.js";
|
|
70
|
+
export {
|
|
71
|
+
identityMat4, mat4FromTransform, mulMat4, invertMat4, determinantMat4,
|
|
72
|
+
transformPoint, transformDirection, normalMatrix, transformNormal, transformOpToLocal,
|
|
73
|
+
} from "./math/matrix.js";
|
|
74
|
+
|
|
75
|
+
// Random numbers and noise
|
|
76
|
+
export { rng, noise2, noise3, fbm2, fbm3 } from "./math/noise.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MTL loading: the material file an OBJ file names with `mtllib`.
|
|
3
|
+
*
|
|
4
|
+
* Reads each `newmtl` block's diffuse color (`Kd`), see-through (`d`, or
|
|
5
|
+
* `Tr` = 1 - d) and diffuse texture (`map_Kd`, options like `-s 1 1 1`
|
|
6
|
+
* skipped). Everything else is ignored.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} MtlMaterial
|
|
11
|
+
* @property {string} name
|
|
12
|
+
* @property {string} [fill] - "#rrggbb" from `Kd` (or "rgba(...)" when see-through)
|
|
13
|
+
* @property {number} [opacity] - From `d` / `Tr`, 0 to 1
|
|
14
|
+
* @property {string} [textureFile] - The texture's file name (`map_Kd`), relative to the .mtl file
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parses the text of an MTL file.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} text
|
|
21
|
+
* @returns {Object<string, MtlMaterial>} Materials by name
|
|
22
|
+
*/
|
|
23
|
+
export function parseMTL(text) {
|
|
24
|
+
const materials = {};
|
|
25
|
+
let m = null;
|
|
26
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
27
|
+
const line = raw.trim();
|
|
28
|
+
if (line === "" || line[0] === "#") continue;
|
|
29
|
+
const [key, ...rest] = line.split(/\s+/);
|
|
30
|
+
const k = key.toLowerCase();
|
|
31
|
+
if (k === "newmtl") {
|
|
32
|
+
const name = rest.join(" ");
|
|
33
|
+
m = materials[name] = { name };
|
|
34
|
+
} else if (!m) {
|
|
35
|
+
continue;
|
|
36
|
+
} else if (k === "kd") {
|
|
37
|
+
const [r, g, b] = rest.map(Number);
|
|
38
|
+
const hex = (x) => Math.round(Math.min(1, Math.max(0, x || 0)) * 255).toString(16).padStart(2, "0");
|
|
39
|
+
m.fill = `#${hex(r)}${hex(g ?? r)}${hex(b ?? r)}`;
|
|
40
|
+
} else if (k === "d") {
|
|
41
|
+
m.opacity = Math.min(1, Math.max(0, Number(rest[0])));
|
|
42
|
+
} else if (k === "tr") {
|
|
43
|
+
m.opacity = Math.min(1, Math.max(0, 1 - Number(rest[0])));
|
|
44
|
+
} else if (k === "map_kd") {
|
|
45
|
+
// Skip options ("-s 1 1 1", "-bm 0.5", "-clamp on"...): the file name is what's left
|
|
46
|
+
const words = [...rest];
|
|
47
|
+
const takes = { "-s": 3, "-o": 3, "-t": 3, "-mm": 2, "-bm": 1, "-blendu": 1, "-blendv": 1, "-clamp": 1, "-texres": 1, "-imfchan": 1, "-boost": 1, "-cc": 1 };
|
|
48
|
+
while (words.length && words[0][0] === "-") {
|
|
49
|
+
const opt = words.shift().toLowerCase();
|
|
50
|
+
words.splice(0, takes[opt] ?? 1);
|
|
51
|
+
}
|
|
52
|
+
if (words.length) m.textureFile = words.join(" ");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// See-through fills as rgba, so materials can use them directly
|
|
56
|
+
for (const mat of Object.values(materials)) {
|
|
57
|
+
if (mat.fill && mat.opacity !== undefined && mat.opacity < 1) {
|
|
58
|
+
const [r, g, b] = [1, 3, 5].map((i) => parseInt(mat.fill.slice(i, i + 2), 16));
|
|
59
|
+
mat.fill = `rgba(${r}, ${g}, ${b}, ${mat.opacity})`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return materials;
|
|
63
|
+
}
|