trid3d 0.5.0 → 0.7.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 +43 -20
- package/package.json +1 -1
- package/trid/index.d.ts +210 -7
- package/trid/index.js +10 -3
- package/trid/math/projection.js +8 -2
- package/trid/math/transform.js +4 -5
- package/trid/modeling/query.js +706 -0
- package/trid/render/canvasRenderer.js +268 -5
- package/trid/render/draw.js +12 -2
- package/trid/render/lights.js +14 -1
- package/trid/render/post.js +155 -1
- package/trid/render/shadows.js +184 -0
- package/trid/render/shapes.js +42 -23
- package/trid/render/webglRenderer.js +348 -17
- package/trid/scene.js +1 -1
- package/trid/tools/index.js +1 -0
- package/trid/tools/selectionOutline.js +244 -0
- package/trid/tools/transformGizmo.js +9 -1
package/README.md
CHANGED
|
@@ -4,31 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Trid</h1>
|
|
6
6
|
|
|
7
|
-
<p align="center">
|
|
7
|
+
<p align="center">Model, light and draw 3D on the HTML canvas. No dependencies, no build step.</p>
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
Trid gives you a scene,
|
|
11
|
+
Trid gives you a scene, shapes you can model in code, lights, effects, model loading and mouse/keyboard tools, all in plain ES modules you can drop next to your page. See the [docs](https://zachr622.github.io/Trid/) for live demos.
|
|
12
12
|
|
|
13
13
|
> **Status:** early (0.x): the API can still change between minor versions. See [Upgrading](#upgrading).
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **Scene** — holds a canvas, a camera and your objects, and runs the frame loop for you.
|
|
17
|
+
- **Scene** — holds a canvas, a camera, lights and your objects, and runs the frame loop for you. The background can be a color or see-through (`"transparent"`), so a model can float over your page.
|
|
18
18
|
- **Groups** — give any object `children` and they move, turn and scale with it (models made of parts, a moon orbiting a planet).
|
|
19
|
-
- **Shapes** — cubes, boxes (sharp or rounded), spheres, cylinders, cones, tori and planes built in, `lathe` for round shapes and `extrude` for flat ones, or any mesh you define as vertices and edges.
|
|
20
|
-
- **Modeling** — smooth curves through a few points (`spline`), shapes swept along a 3D path (`sweep`: tails, horns, pipes, rails), surfaces through cross-sections (`loft`: bodies, bottles, hulls, heads), subdivision surfaces with creases (`subdivideMesh`: box modeling), shapes that melt together (`blobMesh`: creatures, clay; or any distance field with `sdfMesh`), rounded or cut edges on any mesh (`bevelMesh`), taper, twist, bend and bulge deformers,
|
|
19
|
+
- **Shapes** — cubes, boxes (sharp or rounded), spheres, cylinders, cones, tori and planes built in, `lathe` for round shapes (a point on the axis closes it to a tip) and `extrude` for flat ones, or any mesh you define as vertices, faces and edges.
|
|
20
|
+
- **Modeling** — smooth curves through a few points (`spline`), shapes swept along a 3D path (`sweep`: tails, horns, pipes, rails), surfaces through cross-sections (`loft`: bodies, bottles, hulls, heads), subdivision surfaces with creases (`subdivideMesh`: box modeling), shapes that melt together (`blobMesh`: creatures, clay; or any distance field with `sdfMesh`), rounded or cut edges on any mesh (`bevelMesh`), taper, twist, bend and bulge deformers, mesh cleanup and checks (weld, clean, validate, fix winding), and rays, inside tests and distances against meshes and scenes (`meshQuery`, `raycastScene`).
|
|
21
21
|
- **Mesh helpers** — clone, transform, displace, merge and measure meshes; seeded random numbers and smooth noise (`fbm2`, `fbm3`, tileable) for rocks, terrain and procedural textures.
|
|
22
22
|
- **Filled faces** — give an object a `material` to fill and shade its faces, flat or smooth.
|
|
23
|
-
- **Lights** — ambient, directional and point lights, with colors and intensities, as many as you need (up to 8 at once besides ambient).
|
|
23
|
+
- **Lights and shadows** — ambient, directional and point lights, with colors and intensities, as many as you need (up to 8 at once besides ambient). A directional light can cast soft shadows (a shadow map with WebGL, ground shadows on the canvas).
|
|
24
24
|
- **Textures** — images, canvases or videos on faces, with texture coordinates from built-in shapes and OBJ files, or made from the shape (planar, box, sphere and cylinder mapping). Repeat, offset and rotate them, or give each face its own material. Or paint one in code: wood, marble, bricks and more, all seamless.
|
|
25
25
|
- **Effects** — stack shader effects like toon, rim and edge glow, gradients and fog on a material, or write your own in GLSL.
|
|
26
|
-
- **Post effects** — bloom, vignette, color grading
|
|
26
|
+
- **Post effects** — bloom, vignette, color grading, film grain and cartoon outlines over the whole picture, or your own full-screen shaders (several passes if needed, reading the scene's depth and normals if wanted).
|
|
27
27
|
- **Two renderers** — the canvas 2D context by default, or `WebGLRenderer` for exact depth and big models, with the same scene code.
|
|
28
28
|
- **OBJ files** — load models from a URL (with their `.mtl` colors and textures), or let users drop their own `.obj` files on the canvas.
|
|
29
29
|
- **Export** — `scene.screenshot()` and `scene.record()` save images and videos of the canvas; `exportOBJ` / `exportModel` write meshes, objects or whole scenes as OBJ files with their MTL materials and textures.
|
|
30
|
-
- **Camera** — position, yaw, pitch and field of view, with near-plane clipping.
|
|
31
|
-
- **Tools** — orbit and fly cameras, object dragging, hover highlight, a move / rotate / scale gizmo (with a pivot of your choice), a view axes gizmo, a floor grid, an FPS meter and an OBJ uploader, one line each. Exact picking by triangles, with the hit point and texture coordinates under the mouse. Writing your own is a plain object with a few methods.
|
|
30
|
+
- **Camera** — position, yaw, pitch and field of view (50° by default), with near-plane clipping.
|
|
31
|
+
- **Tools** — orbit and fly cameras, object dragging, hover highlight, a selection outline, a move / rotate / scale gizmo (with a pivot of your choice), a view axes gizmo, a floor grid, an FPS meter and an OBJ uploader, one line each. Exact picking by triangles, with the hit point and texture coordinates under the mouse. Writing your own is a plain object with a few methods.
|
|
32
32
|
- **Pure math** — every transform and projection step is exported, plus 4×4 matrices and rotations, so you can use the pipeline piece by piece.
|
|
33
33
|
|
|
34
34
|
## Install
|
|
@@ -41,7 +41,7 @@ No bundler? Use the CDN with an import map, or copy the `trid/` folder next to y
|
|
|
41
41
|
|
|
42
42
|
```html
|
|
43
43
|
<script type="importmap">
|
|
44
|
-
{ "imports": { "trid3d": "https://cdn.jsdelivr.net/npm/trid3d@0.
|
|
44
|
+
{ "imports": { "trid3d": "https://cdn.jsdelivr.net/npm/trid3d@0.7/trid/index.js" } }
|
|
45
45
|
</script>
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -56,13 +56,22 @@ No bundler? Use the CDN with an import map, or copy the `trid/` folder next to y
|
|
|
56
56
|
// main.js
|
|
57
57
|
import { Scene, makeShape, OrbitCamera } from "trid3d";
|
|
58
58
|
|
|
59
|
-
const scene = new Scene(document.getElementById("canvas"), {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const scene = new Scene(document.getElementById("canvas"), {
|
|
60
|
+
width: 800, height: 400,
|
|
61
|
+
camera: { z: -1.5 }, // a step back
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// A cube with rounded edges, filled and smoothly shaded
|
|
65
|
+
const cube = scene.add({
|
|
66
|
+
mesh: makeShape("cube", { radius: 0.08 }),
|
|
67
|
+
material: { fill: "#E9806E", smooth: true },
|
|
68
|
+
});
|
|
63
69
|
|
|
64
70
|
scene.use(new OrbitCamera()); // drag to orbit, scroll to zoom
|
|
65
|
-
scene.start((dt) =>
|
|
71
|
+
scene.start((dt) => {
|
|
72
|
+
cube.rot.x += dt * 0.5;
|
|
73
|
+
cube.rot.y += dt * 0.35;
|
|
74
|
+
});
|
|
66
75
|
```
|
|
67
76
|
|
|
68
77
|
Without a bundler, serve the folder (`python3 -m http.server`) instead of opening the file directly: browsers block module imports from `file://` pages.
|
|
@@ -87,7 +96,7 @@ Or let users load their own: `scene.use(new ObjUploader())` accepts dropped `.ob
|
|
|
87
96
|
| `trid/sceneGraph.js` | Groups: `children`, world matrices |
|
|
88
97
|
| `trid/math/` | Transforms, camera directions, projection, matrices, rotations, random numbers and noise |
|
|
89
98
|
| `trid/render/` | Renderers (Canvas 2D, WebGL), lights, effects, post effects, shapes, grids, mesh helpers, procedural textures, drawing, frame loop |
|
|
90
|
-
| `trid/modeling/` | Modeling tools: curves, sweep, loft, subdivision, blobs, bevel, deformers, cleanup |
|
|
99
|
+
| `trid/modeling/` | Modeling tools: curves, sweep, loft, subdivision, blobs, bevel, deformers, cleanup, queries (rays, inside, distance) |
|
|
91
100
|
| `trid/loaders/` | OBJ, MTL and texture loading |
|
|
92
101
|
| `trid/export/` | Screenshots, video recording, OBJ and MTL export |
|
|
93
102
|
| `trid/tools/` | Camera, object, gizmo and overlay tools |
|
|
@@ -113,10 +122,23 @@ See [`docs/README.md`](https://github.com/ZachR622/Trid/blob/master/docs/README.
|
|
|
113
122
|
npm test
|
|
114
123
|
```
|
|
115
124
|
|
|
116
|
-
Uses Node's built-in test runner (Node 21+), so there's nothing to install. The tests cover shapes, mesh helpers, curves, sweeps, lofts, subdivision, blobs, bevels, deformers, mesh cleanup, noise, textures, OBJ / MTL, picking, tools, groups and matrices, lights, post effects, grids, and the WebGL renderer's GPU memory on a fake WebGL context.
|
|
125
|
+
Uses Node's built-in test runner (Node 21+), so there's nothing to install. The tests cover shapes (lathe poles included), mesh helpers, curves, sweeps, lofts, subdivision, blobs, bevels, deformers, mesh cleanup, mesh and scene queries, noise, textures, OBJ / MTL, picking, tools, groups and matrices, lights, shadows, post effects, grids, the field of view, see-through backgrounds, and the WebGL renderer's GPU memory on a fake WebGL context.
|
|
117
126
|
|
|
118
127
|
## Upgrading
|
|
119
128
|
|
|
129
|
+
### From 0.6 to 0.7
|
|
130
|
+
|
|
131
|
+
Nothing changes for existing scenes: shadows, outlines and the selection outline are all off until asked for. Only names Trid now uses itself could clash with yours:
|
|
132
|
+
|
|
133
|
+
- In **post effect GLSL**, `Frame` has two more fields (`f.near`, `f.far`), and the uniforms `uNear`, `uFar`, `uGeometry`, `uGeoNear` and `uGeoRange` are taken. A post effect declaring its own uniform with one of those names needs to rename it.
|
|
134
|
+
- In **material effect GLSL**, the functions `tridShadow` and `tridShadowDepth` and the uniforms starting with `uShadow` (plus `uReceive`) are taken. Effect uniforms are renamed by Trid, so only helper functions with those names would clash.
|
|
135
|
+
|
|
136
|
+
### From 0.5 to 0.6
|
|
137
|
+
|
|
138
|
+
- **The cone's tip is a single point** (a pole), so the cone is closed: `isClosed` and `validateMesh` no longer report a hole there. It has fewer vertices than before (its tip ring of 32 is now 1), and its side faces are triangles. Any `lathe` profile point with `r: 0` works the same way.
|
|
139
|
+
- **The default field of view is 50°** (`DEFAULT_FOV`), instead of 90°. Scenes whose camera has no `fov` look about twice as close, like in most 3D tools. To keep the old view, give the camera `fov: Math.PI / 2`.
|
|
140
|
+
- **`background: null` is see-through**, like `"transparent"` (it used to come out black). `clear()` now empties the canvas before painting the background, so see-through and half see-through backgrounds work; opaque ones look the same.
|
|
141
|
+
|
|
120
142
|
### From 0.3 to 0.4
|
|
121
143
|
|
|
122
144
|
- **Lights are a list**: `scene.lights`. A scene still starts with one directional light and an ambient level, and looks the same. `scene.light` and `scene.ambient` are now shortcuts for the first directional light's direction and the first ambient light's intensity.
|
|
@@ -135,7 +157,8 @@ Uses Node's built-in test runner (Node 21+), so there's nothing to install. The
|
|
|
135
157
|
|
|
136
158
|
Roughly in order:
|
|
137
159
|
|
|
138
|
-
- **More
|
|
139
|
-
- **
|
|
140
|
-
- **More post effects** — outlines (from depth), antialiasing (FXAA)
|
|
160
|
+
- **More shadows** — point lights casting shadows, and several lights at once
|
|
161
|
+
- **Combining shapes** — union, subtract and intersect meshes (CSG)
|
|
141
162
|
- **More tests** — the canvas renderer's drawing and the remaining math functions
|
|
163
|
+
- **More import and export formats** — STL; basic glTF later
|
|
164
|
+
- **More post effects** — antialiasing (FXAA)
|
package/package.json
CHANGED
package/trid/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface Camera {
|
|
|
43
43
|
yaw?: number;
|
|
44
44
|
/** Tilt around X in radians, > 0 looks up (default 0) */
|
|
45
45
|
pitch?: number;
|
|
46
|
-
/** Vertical field of view in radians (default
|
|
46
|
+
/** Vertical field of view in radians (default DEFAULT_FOV: 50°) */
|
|
47
47
|
fov?: number;
|
|
48
48
|
/** Near plane distance; closer geometry is clipped (default 0.01) */
|
|
49
49
|
near?: number;
|
|
@@ -164,6 +164,10 @@ export interface SceneObjectInput {
|
|
|
164
164
|
hoverable?: boolean;
|
|
165
165
|
/** Set to false so `pick` / `pickHit` never find this object or its children (helpers like a grid) */
|
|
166
166
|
pickable?: boolean;
|
|
167
|
+
/** Set to false so this object (and its children) casts no shadow */
|
|
168
|
+
castShadow?: boolean;
|
|
169
|
+
/** Set to false so no shadow falls on this object (and its children) */
|
|
170
|
+
receiveShadow?: boolean;
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
/**
|
|
@@ -192,8 +196,8 @@ export interface SceneOptions {
|
|
|
192
196
|
width?: number;
|
|
193
197
|
/** Sets canvas.height (keeps the current one if omitted) */
|
|
194
198
|
height?: number;
|
|
195
|
-
/** Clear color (default "#141414") */
|
|
196
|
-
background?: string;
|
|
199
|
+
/** Clear color (default "#141414"); "transparent", a see-through rgba() or null: the page shows behind */
|
|
200
|
+
background?: string | null;
|
|
197
201
|
/** Default wireframe color (default "#ffffff") */
|
|
198
202
|
color?: string;
|
|
199
203
|
/** Default wireframe width (default 3) */
|
|
@@ -221,7 +225,7 @@ export class Scene {
|
|
|
221
225
|
|
|
222
226
|
canvas: HTMLCanvasElement;
|
|
223
227
|
ctx: CanvasRenderingContext2D;
|
|
224
|
-
background: string;
|
|
228
|
+
background: string | null;
|
|
225
229
|
color: string;
|
|
226
230
|
lineWidth: number;
|
|
227
231
|
camera: Camera;
|
|
@@ -459,8 +463,35 @@ export interface DirectionalLight {
|
|
|
459
463
|
color?: string;
|
|
460
464
|
intensity?: number;
|
|
461
465
|
enabled?: boolean;
|
|
466
|
+
/** Cast shadows (the first directional light with it does): true, or settings */
|
|
467
|
+
shadows?: boolean | ShadowOptions;
|
|
462
468
|
}
|
|
463
469
|
|
|
470
|
+
/** Shadow settings of a directional light. */
|
|
471
|
+
export interface ShadowOptions {
|
|
472
|
+
/** Shadow map width in pixels (WebGL, default 2048). Bigger: sharper, slower */
|
|
473
|
+
size?: number;
|
|
474
|
+
/** Edge blur, in shadow map pixels (default 1.5; 0 = sharp) */
|
|
475
|
+
softness?: number;
|
|
476
|
+
/** Raise if lit surfaces get speckles; too high and shadows come loose (default 1) */
|
|
477
|
+
bias?: number;
|
|
478
|
+
/** Floor height for the canvas renderer's ground shadows (default: found) */
|
|
479
|
+
ground?: number;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Settings used for `shadows: true`. */
|
|
483
|
+
export const DEFAULT_SHADOWS: Readonly<Required<Omit<ShadowOptions, "ground">> & { ground: null }>;
|
|
484
|
+
/** A light's shadow settings, defaults filled in. */
|
|
485
|
+
export function shadowOptions(value: boolean | ShadowOptions): Required<Omit<ShadowOptions, "ground">> & { ground: number | null };
|
|
486
|
+
/** False when the object or one of its groups sets `key` to false. */
|
|
487
|
+
export function shadowFlag(obj: SceneObject, key: "castShadow" | "receiveShadow"): boolean;
|
|
488
|
+
/** The shadow map's box around casting objects (`matrix`: world → -1..1, column-major). */
|
|
489
|
+
export function shadowFrame(casters: SceneObject[], dir: Vec3): { matrix: Float32Array; radius: number } | null;
|
|
490
|
+
/** The floor height for ground shadows: where receivers have the most area of upward flat faces. */
|
|
491
|
+
export function findGround(receivers: SceneObject[]): number | null;
|
|
492
|
+
/** A world polygon's part above `ground`, flattened onto it along the light (`dir`: where it comes from). */
|
|
493
|
+
export function flattenOnGround(pts: Vec3[], dir: Vec3, ground: number): Vec3[];
|
|
494
|
+
|
|
464
495
|
/** Light from a point, like a bulb. */
|
|
465
496
|
export interface PointLight {
|
|
466
497
|
type: "point";
|
|
@@ -490,6 +521,8 @@ export interface SceneLights {
|
|
|
490
521
|
lights: { point: boolean; dir: Vec3 | null; pos: Vec3 | null; color: [number, number, number]; range: number }[];
|
|
491
522
|
/** Unit direction of the first directional light */
|
|
492
523
|
mainDir: Vec3;
|
|
524
|
+
/** The light casting shadows (`index` in `lights`) and its settings, or null */
|
|
525
|
+
shadow: ({ index: number; dir: Vec3 } & Required<Omit<ShadowOptions, "ground">> & { ground: number | null }) | null;
|
|
493
526
|
}
|
|
494
527
|
|
|
495
528
|
/** A scene's lights, colors parsed and multiplied by intensity, directions unit length. */
|
|
@@ -704,6 +737,10 @@ export interface PostFrame {
|
|
|
704
737
|
canvas: HTMLCanvasElement;
|
|
705
738
|
/** Turns a uniform value (CSS color, array) into RGBA 0..1 */
|
|
706
739
|
rgba(value: UniformValue): RGBA;
|
|
740
|
+
/** The camera's near distance */
|
|
741
|
+
near: number;
|
|
742
|
+
/** The camera's far distance */
|
|
743
|
+
far: number;
|
|
707
744
|
}
|
|
708
745
|
|
|
709
746
|
/** One pass of a post effect. */
|
|
@@ -727,8 +764,46 @@ export interface PostEffect<U extends Record<string, UniformValue> = Record<stri
|
|
|
727
764
|
uniforms?: U;
|
|
728
765
|
/** Set to false to turn it off without removing it */
|
|
729
766
|
enabled?: boolean;
|
|
767
|
+
/** Also read the scene's depth and normals (WebGL): GLSL gets `depthAt(uv)`, `normalAt(uv)` and `maskAt(uv)` */
|
|
768
|
+
geometry?: boolean;
|
|
769
|
+
/** With `geometry`: the objects `maskAt` is 1 on (children included). Default: all */
|
|
770
|
+
objects?: SceneObject[];
|
|
771
|
+
/** Canvas renderer: draw the meshes' silhouettes and creases as lines, using `uColor`, `uWidth` and `uCreaseAngle` */
|
|
772
|
+
contour?: boolean;
|
|
730
773
|
}
|
|
731
774
|
|
|
775
|
+
/** Cartoon-style lines around objects and along their sharp creases. */
|
|
776
|
+
export function outline(opts?: {
|
|
777
|
+
color?: string;
|
|
778
|
+
width?: number;
|
|
779
|
+
/** Degrees the surface must turn for an inner line (default 40); false = outer lines only */
|
|
780
|
+
creases?: number | false;
|
|
781
|
+
/** How readily a jump in distance makes a line (default 1; 0 = none) */
|
|
782
|
+
depth?: number;
|
|
783
|
+
/** Only outline these objects (and their children) */
|
|
784
|
+
objects?: SceneObject[];
|
|
785
|
+
}): PostEffect<{
|
|
786
|
+
uColor: string;
|
|
787
|
+
uWidth: number;
|
|
788
|
+
uCreaseAngle: number;
|
|
789
|
+
uDepth: number;
|
|
790
|
+
}>;
|
|
791
|
+
|
|
792
|
+
/** Crease angle `outline()` uses by default, in degrees (40). */
|
|
793
|
+
export const DEFAULT_OUTLINE_CREASE: number;
|
|
794
|
+
|
|
795
|
+
/** Whether a post effect reads the scene's depth and normals (`geometry: true`, with GLSL). */
|
|
796
|
+
export function wantsGeometry(effect: PostEffect): boolean;
|
|
797
|
+
|
|
798
|
+
/** The objects an effect's `objects` list covers, children included (null: all). */
|
|
799
|
+
export function maskedObjects(effect: PostEffect): Set<SceneObject> | null;
|
|
800
|
+
|
|
801
|
+
/** GLSL helpers `depthAt`, `normalAt` and `maskAt`, for effects with `geometry: true`. */
|
|
802
|
+
export const GEOMETRY_GLSL: string;
|
|
803
|
+
|
|
804
|
+
/** Each edge of a mesh's faces once, with the faces on its sides (`f2` = -1 on an open border). */
|
|
805
|
+
export function meshEdges(mesh: Mesh): { a: number; b: number; f1: number; f2: number }[];
|
|
806
|
+
|
|
732
807
|
/** Darkens the corners of the picture. */
|
|
733
808
|
export function vignette(opts?: { strength?: number; radius?: number; softness?: number; color?: string }): PostEffect<{
|
|
734
809
|
uStrength: number;
|
|
@@ -1323,6 +1398,36 @@ export interface TransformGizmoOptions {
|
|
|
1323
1398
|
onChange?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1324
1399
|
/** On release: the whole operation */
|
|
1325
1400
|
onDrop?: (op: TransformOp, targets: SceneObject[]) => void;
|
|
1401
|
+
/** Outline the targets, under the handles (default false). Options: see `SelectionOutline` */
|
|
1402
|
+
outline?: boolean | Omit<SelectionOutlineOptions, "targets">;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
export interface SelectionOutlineOptions {
|
|
1406
|
+
/** Objects to outline, or a function asked every frame (default none) */
|
|
1407
|
+
targets?: SceneObject[] | (() => SceneObject[]);
|
|
1408
|
+
/** Line color (default "#ffa64d") */
|
|
1409
|
+
color?: string;
|
|
1410
|
+
/** Line width in CSS pixels (default 2) */
|
|
1411
|
+
width?: number;
|
|
1412
|
+
/** Opacity of the outline where the objects are hidden behind others (default 0.35; 0 = not shown) */
|
|
1413
|
+
hidden?: number;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* A colored line around selected objects, like a 3D editor's selection. Parts hidden
|
|
1418
|
+
* behind other objects get a faint outline. Add it before tools that draw handles.
|
|
1419
|
+
*/
|
|
1420
|
+
export class SelectionOutline implements Tool {
|
|
1421
|
+
constructor(opts?: SelectionOutlineOptions);
|
|
1422
|
+
targets: SceneObject[] | (() => SceneObject[]);
|
|
1423
|
+
color: string;
|
|
1424
|
+
width: number;
|
|
1425
|
+
hidden: number;
|
|
1426
|
+
/** Hide the outline without removing the tool. */
|
|
1427
|
+
visible: boolean;
|
|
1428
|
+
/** The objects outlined this frame. */
|
|
1429
|
+
current(): SceneObject[];
|
|
1430
|
+
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
1326
1431
|
}
|
|
1327
1432
|
|
|
1328
1433
|
/**
|
|
@@ -1352,6 +1457,8 @@ export class TransformGizmo implements Tool {
|
|
|
1352
1457
|
visible: boolean;
|
|
1353
1458
|
/** True while a handle is being dragged. */
|
|
1354
1459
|
readonly dragging: boolean;
|
|
1460
|
+
/** The outline around the targets (`outline` option), or null. */
|
|
1461
|
+
outline: SelectionOutline | null;
|
|
1355
1462
|
/** The pivot in world space, or null when there are no targets. */
|
|
1356
1463
|
center(): Vec3 | null;
|
|
1357
1464
|
draw(ctx: CanvasRenderingContext2D, scene: Scene): void;
|
|
@@ -1426,8 +1533,8 @@ export function worldPerPixel(scene: Scene, depth: number): number;
|
|
|
1426
1533
|
// Drawing and the frame loop
|
|
1427
1534
|
// ---------------------------------------------------------------------------
|
|
1428
1535
|
|
|
1429
|
-
/**
|
|
1430
|
-
export function clear(ctx: CanvasRenderingContext2D, w: number, h: number, color
|
|
1536
|
+
/** Clears the whole canvas, then fills it with a color ("transparent" or null: left see-through). */
|
|
1537
|
+
export function clear(ctx: CanvasRenderingContext2D, w: number, h: number, color?: string | null): void;
|
|
1431
1538
|
/** Draws a square point centered at a screen position. */
|
|
1432
1539
|
export function point(ctx: CanvasRenderingContext2D, p: Vec2, color: string, size?: number): void;
|
|
1433
1540
|
/** Draws a line between two screen points. */
|
|
@@ -1467,7 +1574,7 @@ export function parseColor(ctx: CanvasRenderingContext2D, color: string): RGBA;
|
|
|
1467
1574
|
// Math
|
|
1468
1575
|
// ---------------------------------------------------------------------------
|
|
1469
1576
|
|
|
1470
|
-
/** Default vertical field of view (
|
|
1577
|
+
/** Default vertical field of view: 50° (in radians). */
|
|
1471
1578
|
export const DEFAULT_FOV: number;
|
|
1472
1579
|
/** Default near plane distance. */
|
|
1473
1580
|
export const DEFAULT_NEAR: number;
|
|
@@ -1951,3 +2058,99 @@ export interface BevelOptions {
|
|
|
1951
2058
|
* Edges along an open border stay sharp.
|
|
1952
2059
|
*/
|
|
1953
2060
|
export function bevelMesh(mesh: Mesh, opts?: BevelOptions): Mesh;
|
|
2061
|
+
|
|
2062
|
+
// Modeling: queries (rays, inside, distance)
|
|
2063
|
+
|
|
2064
|
+
/** Which faces a ray counts: "both" (default), "front" (hit from outside) or "back" (hit from inside). */
|
|
2065
|
+
export type RaySide = "both" | "front" | "back";
|
|
2066
|
+
|
|
2067
|
+
export interface RaycastOptions {
|
|
2068
|
+
/** Ignore hits farther than this (default Infinity) */
|
|
2069
|
+
maxDistance?: number;
|
|
2070
|
+
/** Which faces count (default "both") */
|
|
2071
|
+
side?: RaySide;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
/** Where a ray met a mesh, in the mesh's own space. */
|
|
2075
|
+
export interface MeshHit {
|
|
2076
|
+
point: Vec3;
|
|
2077
|
+
/** Unit normal of the face, pointing out of the mesh (whichever side the ray came from) */
|
|
2078
|
+
normal: Vec3;
|
|
2079
|
+
/** From the ray's origin */
|
|
2080
|
+
distance: number;
|
|
2081
|
+
/** Index in `mesh.faces` */
|
|
2082
|
+
face: number;
|
|
2083
|
+
/** true when the ray met the face from outside */
|
|
2084
|
+
front: boolean;
|
|
2085
|
+
/** The 3 vertex indices of the triangle hit (faces with more corners are fans from their first corner) */
|
|
2086
|
+
triangle: [number, number, number];
|
|
2087
|
+
/** Weights of those vertices at the point (they add up to 1) */
|
|
2088
|
+
barycentric: [number, number, number];
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/** The nearest point of a mesh's surface. */
|
|
2092
|
+
export type MeshClosest = Omit<MeshHit, "front">;
|
|
2093
|
+
|
|
2094
|
+
/** A mesh prepared for many questions (a snapshot: editing the mesh later doesn't change it). */
|
|
2095
|
+
export interface MeshQuery {
|
|
2096
|
+
/** Triangles in the query (faces split into fans) */
|
|
2097
|
+
readonly triangleCount: number;
|
|
2098
|
+
/** The mesh's box */
|
|
2099
|
+
readonly bounds: { min: Vec3; max: Vec3 };
|
|
2100
|
+
/** The first place a ray meets the surface, or null. `direction` can be any length but zero */
|
|
2101
|
+
raycast(origin: Vec3, direction: Vec3, opts?: RaycastOptions): MeshHit | null;
|
|
2102
|
+
/** Whether a point is inside (only meaningful for a closed mesh, see `isClosed`) */
|
|
2103
|
+
contains(p: Vec3): boolean;
|
|
2104
|
+
/** The nearest point of the surface, or null when nothing is within `maxDistance` */
|
|
2105
|
+
closest(p: Vec3, opts?: { maxDistance?: number }): MeshClosest | null;
|
|
2106
|
+
/** Distance to the surface (Infinity when nothing is within `maxDistance`) */
|
|
2107
|
+
distance(p: Vec3, maxDistance?: number): number;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/** Prepares a mesh for many ray, inside and distance questions (builds a BVH once). */
|
|
2111
|
+
export function meshQuery(mesh: Mesh): MeshQuery;
|
|
2112
|
+
|
|
2113
|
+
/** One-off `meshQuery(mesh).raycast(...)`. For many rays on one mesh, keep a `meshQuery`. */
|
|
2114
|
+
export function raycastMesh(mesh: Mesh, origin: Vec3, direction: Vec3, opts?: RaycastOptions): MeshHit | null;
|
|
2115
|
+
|
|
2116
|
+
/** One-off `meshQuery(mesh).contains(p)`. */
|
|
2117
|
+
export function pointInMesh(mesh: Mesh, p: Vec3): boolean;
|
|
2118
|
+
|
|
2119
|
+
/** One-off `meshQuery(mesh).distance(p, maxDistance)`. */
|
|
2120
|
+
export function distanceToMesh(mesh: Mesh, p: Vec3, maxDistance?: number): number;
|
|
2121
|
+
|
|
2122
|
+
/** One-off `meshQuery(mesh).closest(p, opts)`. */
|
|
2123
|
+
export function closestPointOnMesh(mesh: Mesh, p: Vec3, opts?: { maxDistance?: number }): MeshClosest | null;
|
|
2124
|
+
|
|
2125
|
+
/** Whether a mesh is closed (watertight): every edge, matched by position, shared by exactly two faces. */
|
|
2126
|
+
export function isClosed(mesh: Mesh): boolean;
|
|
2127
|
+
|
|
2128
|
+
/** What `raycastScene` found: a `MeshHit` in world space, plus the object. */
|
|
2129
|
+
export interface SceneHit extends MeshHit {
|
|
2130
|
+
object: SceneObject;
|
|
2131
|
+
/** The object, then its groups up to the top: `[part, group, ..., root]` */
|
|
2132
|
+
path: SceneObject[];
|
|
2133
|
+
/** Where the hit is, in the mesh's own space (`point` is in the world) */
|
|
2134
|
+
local: Vec3;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
export interface RaycastSceneOptions extends RaycastOptions {
|
|
2138
|
+
/** Return false to skip an object */
|
|
2139
|
+
filter?: (obj: SceneObject) => boolean;
|
|
2140
|
+
/** Only these objects (and their children) */
|
|
2141
|
+
objects?: SceneObjectInput[];
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/**
|
|
2145
|
+
* The first object a ray meets, in world space (the 3D version of `pickHit`).
|
|
2146
|
+
* Hidden objects, `pickable: false` ones and objects without faces are skipped.
|
|
2147
|
+
*/
|
|
2148
|
+
export function raycastScene(
|
|
2149
|
+
scene: Scene | SceneObjectInput[],
|
|
2150
|
+
origin: Vec3,
|
|
2151
|
+
direction: Vec3,
|
|
2152
|
+
opts?: RaycastSceneOptions,
|
|
2153
|
+
): SceneHit | null;
|
|
2154
|
+
|
|
2155
|
+
/** Forgets the prepared query `raycastScene` keeps for a mesh (after editing it in place once `dirty` was cleared). */
|
|
2156
|
+
export function clearQueryCache(mesh: Mesh): void;
|
package/trid/index.js
CHANGED
|
@@ -19,11 +19,12 @@ export { gridMesh, gridHelper } from "./render/grid.js";
|
|
|
19
19
|
export { cloneMesh, transformMesh, displaceMesh, mergeMeshes, meshBounds } from "./render/meshUtils.js";
|
|
20
20
|
|
|
21
21
|
// Renderers
|
|
22
|
-
export { CanvasRenderer, DEFAULT_LIGHT, DEFAULT_AMBIENT, materialsOf } from "./render/canvasRenderer.js";
|
|
22
|
+
export { CanvasRenderer, DEFAULT_LIGHT, DEFAULT_AMBIENT, materialsOf, meshEdges } from "./render/canvasRenderer.js";
|
|
23
23
|
export { WebGLRenderer, DEFAULT_FAR } from "./render/webglRenderer.js";
|
|
24
24
|
|
|
25
25
|
// Lights
|
|
26
26
|
export { defaultLights, sceneLights, lightAt, lightLevels, rangeFade, MAX_LIGHTS } from "./render/lights.js";
|
|
27
|
+
export { shadowOptions, shadowFlag, shadowFrame, findGround, flattenOnGround, DEFAULT_SHADOWS } from "./render/shadows.js";
|
|
27
28
|
|
|
28
29
|
// Effects
|
|
29
30
|
export {
|
|
@@ -32,7 +33,10 @@ export {
|
|
|
32
33
|
} from "./render/effects.js";
|
|
33
34
|
|
|
34
35
|
// Post effects
|
|
35
|
-
export {
|
|
36
|
+
export {
|
|
37
|
+
vignette, bloom, grade, grain, outline, postPasses, activePost, runCanvasPost, wantsGeometry, maskedObjects,
|
|
38
|
+
FRAME_GLSL, GEOMETRY_GLSL, DEFAULT_OUTLINE_CREASE,
|
|
39
|
+
} from "./render/post.js";
|
|
36
40
|
|
|
37
41
|
// Procedural textures
|
|
38
42
|
export { makeTexture, texturePixels, colorBytes, TEXTURE_STYLES } from "./render/proceduralTextures.js";
|
|
@@ -56,7 +60,7 @@ export { screenshot, record, download } from "./export/capture.js";
|
|
|
56
60
|
// Tools
|
|
57
61
|
export {
|
|
58
62
|
OrbitCamera, FlyCamera, DragObject, HoverHighlight, FpsMeter, ObjUploader,
|
|
59
|
-
ViewGizmo, TransformGizmo,
|
|
63
|
+
ViewGizmo, TransformGizmo, SelectionOutline,
|
|
60
64
|
ToolManager, pick, pickHit, isPickable, screenBounds, worldPerPixel,
|
|
61
65
|
} from "./tools/index.js";
|
|
62
66
|
|
|
@@ -91,3 +95,6 @@ export { weldVertices, cleanMesh, validateMesh, fixWinding } from "./modeling/cl
|
|
|
91
95
|
export { subdivideMesh } from "./modeling/subdivide.js";
|
|
92
96
|
export { blobMesh, blobField, sdfMesh } from "./modeling/blobs.js";
|
|
93
97
|
export { bevelMesh } from "./modeling/bevel.js";
|
|
98
|
+
export {
|
|
99
|
+
meshQuery, raycastMesh, pointInMesh, distanceToMesh, closestPointOnMesh, isClosed, raycastScene, clearQueryCache,
|
|
100
|
+
} from "./modeling/query.js";
|
package/trid/math/projection.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default vertical field of view: 50°, a normal lens (like most 3D tools).
|
|
3
|
+
* Wider shows more but makes everything smaller; set `camera.fov` to change it.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_FOV = (50 * Math.PI) / 180;
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Converts normalized device coordinates (-1 to +1)
|
|
3
9
|
* into actual screen (pixel) coordinates.
|
|
@@ -36,11 +42,11 @@ export function screen(p, w, h) {
|
|
|
36
42
|
* - larger z => appears smaller
|
|
37
43
|
*
|
|
38
44
|
* @param {{x:number, y:number, z:number}} p - 3D point (camera space)
|
|
39
|
-
* @param {number} fov - Vertical field of view in radians (default:
|
|
45
|
+
* @param {number} fov - Vertical field of view in radians (default: `DEFAULT_FOV`, 50°)
|
|
40
46
|
* @param {number} aspect - Canvas width / height (default: 1)
|
|
41
47
|
* @returns {{x:number, y:number}} 2D projected point
|
|
42
48
|
*/
|
|
43
|
-
export function project(p, fov =
|
|
49
|
+
export function project(p, fov = DEFAULT_FOV, aspect = 1) {
|
|
44
50
|
const f = 1 / Math.tan(fov / 2);
|
|
45
51
|
return {
|
|
46
52
|
x: (p.x * f) / (p.z * aspect),
|
package/trid/math/transform.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { project, screen } from "./projection.js";
|
|
1
|
+
import { project, screen, DEFAULT_FOV } from "./projection.js";
|
|
2
|
+
|
|
3
|
+
export { DEFAULT_FOV };
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Camera object. Every field is optional and falls back to its default.
|
|
@@ -11,14 +13,11 @@ import { project, screen } from "./projection.js";
|
|
|
11
13
|
* @property {number} [z=0] - Position on Z
|
|
12
14
|
* @property {number} [yaw=0] - Turn around Y in radians (> 0 turns right)
|
|
13
15
|
* @property {number} [pitch=0] - Tilt around X in radians (> 0 looks up)
|
|
14
|
-
* @property {number} [fov=
|
|
16
|
+
* @property {number} [fov=DEFAULT_FOV] - Vertical field of view in radians (default 50°)
|
|
15
17
|
* @property {number} [near=0.01] - Near plane distance; closer geometry is clipped
|
|
16
18
|
* @property {number} [far=1000] - Far distance; farther geometry isn't drawn (WebGL renderer only)
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
/** Default vertical field of view (90°). */
|
|
20
|
-
export const DEFAULT_FOV = Math.PI / 2;
|
|
21
|
-
|
|
22
21
|
/** Default near plane distance. */
|
|
23
22
|
export const DEFAULT_NEAR = 0.01;
|
|
24
23
|
|