3draft 26.11.5001 → 26.11.11010
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/APP.md +118 -28
- package/README.md +6 -1
- package/dist/camera.js +3 -2
- package/dist/csg.d.ts +2 -0
- package/dist/csg.js +13 -1
- package/dist/gl.d.ts +8 -0
- package/dist/gl.js +517 -0
- package/dist/gpu.d.ts +40 -2
- package/dist/gpu.js +374 -18
- package/dist/index.d.ts +11 -3
- package/dist/index.js +8 -3
- package/dist/instances.d.ts +19 -0
- package/dist/instances.js +41 -0
- package/dist/kernel.js +6 -5
- package/dist/pick.js +3 -2
- package/dist/saw.d.ts +29 -0
- package/dist/saw.js +121 -0
- package/dist/svg.js +1 -1
- package/dist/triangulate.d.ts +4 -0
- package/dist/triangulate.js +72 -0
- package/dist/types.d.ts +4 -0
- package/dist/wire.d.ts +13 -0
- package/dist/wire.js +111 -9
- package/package.json +1 -1
package/APP.md
CHANGED
|
@@ -10,7 +10,7 @@ It is one file, `app/index.html`, plus a dependency-free static server (`app/ser
|
|
|
10
10
|
|
|
11
11
|
## The view
|
|
12
12
|
|
|
13
|
-
- **Starting view: WebGPU.** If WebGPU is unavailable, or the GPU device is lost, the app falls back to SVG by itself and says so in the footer (`
|
|
13
|
+
- **Starting view: WebGPU.** If WebGPU is unavailable, or the GPU device is lost, the app falls back to SVG by itself and says so in the footer (`9` or the `WebGPU` button switches by hand). SVG shows colour and transparency only; WebGPU also shows metal, roughness and emission. **Glow** (`C`) turns the emitting materials (`emit` above 0.05, like `lamp`) into local light sources: nearby emitting faces merge into one light (up to 16, the brightest kept) whose reach grows with the emitting area, and it lights the surfaces around it with a smooth falloff, without shadows. With `light` off only these lights (and the emission itself) show. The old filmic shader path (`tonemap` option) stays in the engine, the console no longer switches it. **Smooth** (`N`) shades faces that meet at less than 40 degrees as one round surface (box corners stay sharp), and **Studio** (`K`) lights the model with a key, a fill and a rim light that follow the camera; both are on at the start (WebGPU view only). The palette's `soft` slider gives a material skin-like light: it wraps round the form, with a warm band where light turns to shade.
|
|
14
14
|
- **The drawing fills the stage.** On a desktop it covers the whole area; on a phone it is a little larger than the short side.
|
|
15
15
|
- **Drag** rotates, **double click** resets the view, the arrow keys rotate too.
|
|
16
16
|
- **The cross** follows the pointer and lies on the surface under it. The cursor is always a cross. On a phone, with no mouse, it aims at the middle.
|
|
@@ -20,26 +20,26 @@ It is one file, `app/index.html`, plus a dependency-free static server (`app/ser
|
|
|
20
20
|
|
|
21
21
|
| key | does |
|
|
22
22
|
| --- | --- |
|
|
23
|
-
| `w a s d r f` | fly: forward, left, back, right, up, down. Every press is a speed step; the opposite key slows down |
|
|
23
|
+
| `w a s d r f` | fly: forward, left, back, right, up, down. Every press is a speed step; the opposite key slows down. In edit mode these need `Shift` (the plain keys are edit keys) |
|
|
24
24
|
| `Space` | stop moving (all speeds to zero) |
|
|
25
25
|
| `T` | target the object under the cross (see Target mode); with no target, `T` over empty space targets the nearest object. `Tab` next target (the first one when none), `Esc` let go |
|
|
26
26
|
| `/` | show only the object under the cross; again: the whole scene, and the camera flies back to where it was |
|
|
27
27
|
| `M` | place a prism on the surface under the cross, or shoot one into empty space. **In isolated mode (`/`) it fires a bullet** instead: a prism that starts at the camera point and flies along the ray through the cursor, so its long axis is always the view direction and you always see its back; if it hits the isolated object it sinks half its length and carves that part out (A − B, the cut is made of the selected material, `U` undoes it); if it misses it flies on to 4 times the camera-to-model distance and vanishes. Only the closed parts of the model are carved; open parts are ignored (see the health check below). A carve never leaves a crack: if the result would not be a closed solid it is retried with a hair's shift, and if that fails too the model stays as it was (the note "left as it was" shows). |
|
|
28
|
-
| `Q` | edit mode on the object under the cross
|
|
29
|
-
| `V` | Spider bar (in edit mode:
|
|
30
|
-
| `X`, `Delete` | delete the object under the cross |
|
|
28
|
+
| `Q` | edit mode on the object under the cross (or the target); with neither, a new 1×1×1 m cube of the selected material standing in front of the eye with its base middle as the origin; again: off (see below) |
|
|
29
|
+
| `V` | Spider bar (in edit mode: a sketch line, or a saw point in the saw mode): the first `V` remembers a surface point, the second joins the two with a thin prism (a quarter of the base of a placed one) made of the selected material. `V` over empty space forgets the first point |
|
|
30
|
+
| `Shift+X`, `Delete` | delete the object under the cross (every mode) |
|
|
31
31
|
| `U`, `Backspace` | undo: placing, deleting, painting, booleans and moves, one step at a time |
|
|
32
32
|
| `P` | show / hide the material palette |
|
|
33
33
|
| `I` | pick material: the material of the face under the cross becomes the selected one |
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
34
|
+
| `Ctrl` + press (`Cmd` on a Mac) | paint the face under the cross with the selected material (palette must be on) |
|
|
35
|
+
| `Ctrl` / `Cmd` + double click | paint the whole object |
|
|
36
36
|
| `O` | boolean (see below) |
|
|
37
37
|
| `H` | move gizmo (see below) |
|
|
38
38
|
| `E` | edges and points on / off (also the `Edges` button) |
|
|
39
|
-
| `C` |
|
|
39
|
+
| `C` | glow on / off: emitting materials light their surroundings (also the `Glow` button; WebGPU and WebGL2 views) |
|
|
40
40
|
| `N` | smooth shading on / off (the `Smooth` button; on at the start, WebGPU view only) |
|
|
41
41
|
| `K` | studio light (key, fill, rim; follows the camera) on / off (the `Studio` button; on at the start, WebGPU view only) |
|
|
42
|
-
| `
|
|
42
|
+
| `9`, `L` | SVG / WebGPU, turn the light |
|
|
43
43
|
| `?` | show / hide these shortcuts under the view (hidden at the start; also the `Keys` button) |
|
|
44
44
|
|
|
45
45
|
## Clear and example models
|
|
@@ -57,27 +57,32 @@ There is one gizmo at a time, and `H` cycles through three states:
|
|
|
57
57
|
|
|
58
58
|
Drag one of the arrows to slide the object along that axis; the arrow thickens under the pointer. While dragging only the picture changes; one write goes to the database when you let go, and one `U` takes the whole drag back.
|
|
59
59
|
|
|
60
|
-
## Edit mode:
|
|
60
|
+
## Edit mode: the workbench (`Q`)
|
|
61
61
|
|
|
62
|
-
The
|
|
62
|
+
Edit mode is a separate place from the fly-through. Put the cross on an object and press `Q`: that object becomes the **active, editable one**. Nothing moves and every other object stays visible. The object's **local origin is the middle of its base** (its lowest point, the centre of x and z); the grid (the x axis red, the z axis blue) lies in the plane under it there, and the camera looks at that point. Distances are in **metres** (no light years). `Q` again leaves: the camera goes back to where it was. `U` inside edit mode stops at the start of the session. `Reset view` / double click frame the active object. Points are drawn as dots (bigger and orange when selected). A plain white label at the top names the object and shows the mode as chips (`sketch (V)`, `points (1)`, `lines (2)`, `faces (3)`, `saw (4)`); its `?` button shows or hides the help (hidden at the start).
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
- **Two points on one face cut it.** The line becomes a real edge of the mesh: the surface gets detail exactly where you drew.
|
|
66
|
-
- **Points in the air extend the model.** With a line in progress, `V` over empty space puts the next point on the plane that faces the camera through the last point. So you can draw a new part out from the model, and lines that end on existing vertices are attached to it.
|
|
67
|
-
- **`Enter` closes a loop into a face.** The shortest closed loop (up to 8 lines) through the last line becomes a face with the selected material, wound away from the middle of the object. A loop of 4 or more becomes triangles, so faces stay flat.
|
|
68
|
-
- **Nothing moves by default** (and never while you draw with `V`). Moving is Blender-style, with a key per mode, and every drag is one `U` step:
|
|
69
|
-
- `1` **move points:** hover a vertex (white cross), drag it.
|
|
70
|
-
- `2` **move lines:** hover an edge or a line (white), drag it; both ends move.
|
|
71
|
-
- `3` **move faces:** hover a face (white outline), drag it; all its vertices move together and the face stays flat.
|
|
72
|
-
- The drag slides in the plane that faces the camera. A selected point, line or face (orange) also gets a gizmo: drag an arrow to move along one axis. Everything that uses the moved vertices follows: lines, faces and the base surface, so this is how you reshape the original model. A polygon that gets only some of its vertices moved turns into triangles.
|
|
73
|
-
- `V` goes back to drawing (and drops the selection); `Esc` does the same.
|
|
74
|
-
- **`B` turns the spider bars into lines.** The prisms made with `V` in object mode (they are objects named `bar`) become lines on the edited object: each end snaps to a vertex that is close, or is put on the surface if it is near it, or stays a free point. The bars are removed (one `U` brings them back). So a plan sketched as bars can be worked on as a wire.
|
|
75
|
-
- **`X`** removes the lines at the selected point or line (or the last line); the points stay. `U` undoes every step, one key press at a time.
|
|
76
|
-
- **`Esc`** stops the line you are drawing; then it leaves a move mode back to drawing; then it leaves edit mode (so does `Q`). The lines are only shown in edit mode.
|
|
64
|
+
Keys in edit mode:
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
| key | does |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `V` | sketch lines on the surface (any number; marks only, nothing happens yet; `U` takes the last one back). In the saw mode: a saw point |
|
|
69
|
+
| `1` `2` `3` | select **points / lines / faces**: click one, `Shift`+click adds one or takes it away, a click on nothing clears. There is no gizmo |
|
|
70
|
+
| `A`, `A A` | select everything (in the point, line or face mode); a quick second `A` clears the selection |
|
|
71
|
+
| `G` | **grab** the selection: it follows the pointer on the plane that faces the camera. `X`, `Y` or `Z` locks it to that world axis (the same key again lets go); a typed number is exact (`G X 0.25 Enter` = a quarter of a metre along x; `-` flips the sign) |
|
|
72
|
+
| `S` | **scale** the selection about its centre. `X`, `Y`, `Z` scales along that world axis only; a typed number is the factor |
|
|
73
|
+
| `E` | **extrude** the selected faces (face mode): they are pushed out and then grabbed along their normal (type metres, or move the pointer). Faces that touch each other are extruded one by one for now |
|
|
74
|
+
| `4` | the saw (below) |
|
|
75
|
+
| `Shift+X`, `Delete` | delete the object under the cross (edit mode carries on with another object on the bench, or ends); plain `X` only locks the axis during G / S |
|
|
76
|
+
| `Shift` + `W A S D R F` | fly (the plain keys are edit keys here) |
|
|
77
|
+
| `Ctrl` / `Cmd` + click | paint a face |
|
|
78
|
+
| `Esc` | forget the saw points or the sketch chain; then back to sketching; then leave edit mode. During a grab / scale / extrude: cancel |
|
|
79
|
+
|
|
80
|
+
During `G`, `S` and `E` the move is live: **a click or `Enter` confirms, `Esc` or a right click cancels**, and it is one `U` step. Faces stay flat: a polygon stays one polygon whenever the move leaves it flat (a whole face moved, scaled or stretched, the sides of an extrusion); only a face that would be bent becomes triangles.
|
|
81
|
+
|
|
82
|
+
- **The saw (`4`).** Only in this mode does `V` saw. One cut at a time. `V` puts a point (on a vertex, or on the surface), up to three:
|
|
83
|
+
- **two points** are a line drawn on a face: the saw plane contains the line and stands **square on that face**, so it runs straight into the material and out beyond the ends of the line. Both points must lie on one face.
|
|
84
|
+
- **three points** are a triangle: the saw plane is the plane of the three points (a slanted cut, a mitre).
|
|
85
|
+
- The plane is previewed as an orange outline where it meets the object. `Enter` cuts, `Esc` forgets the points. The cut object is replaced by closed solids (two, or more when the plane splits an odd shape into several pieces), named `prism-xxxx` with a fresh short id each (an object that is not a prism keeps its name plus a short id); the new faces take the material of the face that was sawn (the one the points were put on) and lie exactly in the plane (no kerf, no triangle sprinkles). The pieces stay where they were, on the bench: move them apart with `G`. One `U` puts the original back. You stay in edit mode, on the first piece, so the next cut can follow at once. Only closed solids can be sawn. The engine part is `saw`, `planeThroughLine`, `planeThroughTriangle`, `sectionSegments`.
|
|
81
86
|
|
|
82
87
|
## Boolean operations (`O`)
|
|
83
88
|
|
|
@@ -97,7 +102,7 @@ The math is the engine's `boolean(a, b, op)` (README, part 1). Only closed parts
|
|
|
97
102
|
|
|
98
103
|
## Materials (`P`)
|
|
99
104
|
|
|
100
|
-
Twelve starting materials: plaster, sand, wood, dark wood, concrete, slate, grass, brick, steel, gold, glass, lamp. Pick one, then `
|
|
105
|
+
Twelve starting materials: plaster, sand, wood, dark wood, concrete, slate, grass, brick, steel, gold, glass, lamp. Pick one, then `Ctrl`+press (`Cmd` on a Mac) to paint a face or `Ctrl`+double click for the whole object.
|
|
101
106
|
|
|
102
107
|
**Pick material (`I`, or the `pick` button):** the eyedropper. `I` takes the material of the face under the cross. The `pick` button arms it, and the next click on a surface takes it (`Esc` cancels). The palette opens and that material is selected, so you can read its sliders or paint with it right away. A face that has only a colour selects the material with that colour, or starts a new material from it.
|
|
103
108
|
|
|
@@ -128,3 +133,88 @@ Collections:
|
|
|
128
133
|
- `palette`: one document per material, `{ color, metal, rough, alpha, emit, order }`.
|
|
129
134
|
|
|
130
135
|
Undo is a per-session stack in the page; it does not reach back past a reload.
|
|
136
|
+
|
|
137
|
+
## Two views, two engines: build and store (`7`)
|
|
138
|
+
|
|
139
|
+
`7` (or the **store** switch) opens the store as a second 3D view on the left, a third of the width; the build keeps the rest. The store is this same page loaded in an iframe with `?store` (`EMBED` in the code): a separate engine with its own objects, undo steps and camera, so no data is copied between the two. The keyboard focus follows the pointer (and a click): over the store the keys go there, over the build they come back; a teal frame marks the focused view. The two talk by `postMessage` (same origin only): `focus`, `store-toggle` (7 in the store closes it) and `brush` (the targeted model, base middle at the origin) and `drag-start` / `drag-move` / `drag-end` / `drag-cancel` (drag and drop: Alt+press an object in the store; the pointer is captured, so it can leave the pane; the build moves its cursor to the pointer and, on `drag-end` over its own area, puts a copy down on the surface under it: the grabbed point projected to the floor (y = 0) of the store is the origin of the new object, put on the hit point; the store freezes while it is carried; the model's up turned to the surface normal by the shortest turn). On the phone layout the store takes the top 38% of the screen.
|
|
140
|
+
- The store starts with loose timber stock lying on the floor: `lath`, `board`, `plank`, `beam`, `plywood` (the `wood` material, 2 m long). It lives in this visit only (no db, a fresh stock each time it opens) and has no buttons of its own: you can edit, saw or add to it like any scene.
|
|
141
|
+
- In the store: target an object (`T`): it becomes the **model of `M`**, when you press `M` there. In the build, `M` puts a copy of that model down (base middle at the cross, or on the cursor ray over empty space), each press one `U` step. A chip next to the place button names the model; click it to go back to the plain prism.
|
|
142
|
+
|
|
143
|
+
## Side panels, modals and the help page
|
|
144
|
+
|
|
145
|
+
The material palette (`P`) and the help page (`?` or the console's ? button) are **vertical side panels** on the right, one at a time (the desktop console lies over their foot); `Esc` or ✕ closes them, and they do not block the view. The keyboard map is the only modal (a link inside the help; a tap on the scrim or `Esc` closes it). The palette has its own colour picker (a saturation / brightness square, a hue slider and a hex field) instead of the browser's, which is unusable on a phone.
|
|
146
|
+
|
|
147
|
+
## The desktop console
|
|
148
|
+
|
|
149
|
+
On a desktop window the 3D view fills the whole window. A dark **console** lies over its bottom edge (about two or three lines high, chamfered, with engraved group labels and a few LEDs); the readouts (yaw, pitch, speed, pick, objects, view, live) are one quiet line at the top, the title and version in the corners. The phone / installed-app layout is unchanged.
|
|
150
|
+
|
|
151
|
+
- **Switches** (edges, view, glow, smooth, studio, freeze) are real toggles; the action buttons are icons (hover for the tooltip); **?** opens the help, the key explainer with every mode and key.
|
|
152
|
+
- **Two panels:** the standard console shows `view` (two rows of switches: freeze, view mode, edges, light, palette / store, smooth, studio, glow, edit (red while editing); light off = no lighting, only emission and edges; a switch is blue when on), `scene` (examples, clear). Place, delete and target (M, ⇧X, T) have no console buttons any more: they act on what the pointer is over, so they live on the keys and the phone pad (the old buttons stay in the page, hidden, for the code that updates them); in edit mode these are replaced by an `edit` panel: points / lines / faces / saw (1–4), move / scale / extrude (G S E), delete (⇧X), done (Q). Each button presses its key.
|
|
153
|
+
- **Marks in the GPU:** in the WebGPU and WebGL2 views the editor's marks (points, lines, selection, sketch, saw, snap, gizmo axes, the cross) are drawn by the GPU itself (`marks` render option, `buildMarks`: round-capped screen-space segments over the picture), not by an SVG layer. The SVG view still draws them as SVG.
|
|
154
|
+
- **Keys are data:** `ACTIONS` (what the program can do) and `KEYMAP` (which key does it) are plain objects, also on `window`; `bindKey('q', 'scene')` rebinds a key, `bindKey('q')` frees it. The console buttons call the actions directly, so they work whatever the keys are. A mode or a user setting can swap `KEYMAP` as a whole.
|
|
155
|
+
|
|
156
|
+
On the phone layout the console is hidden; the **Exa** menu has a `store` button at its top (the same switch as `B`). If the page script never runs (the engine did not load), a red note appears after 8 seconds instead of a blank view.
|
|
157
|
+
|
|
158
|
+
## The help page is Markdown
|
|
159
|
+
|
|
160
|
+
`app/help.md` is the help: edit that file and the side panel follows (no source change; a tiny renderer in the page turns headings, lists, tables, code, bold, italic and links into HTML). Spider-Paint gets a copy of it from `scripts/sync.mjs`.
|
|
161
|
+
|
|
162
|
+
## Rotate in edit mode (`R`)
|
|
163
|
+
|
|
164
|
+
`R` turns the selection like `G` and `S` move and scale it: about the middle of the selected **points** or **faces** (a face selection turns about its normal, points about the viewing direction); with a selected **line** that line is the axis, and the other selected parts turn about it (the whole object when the line is all that is selected). `X`, `Y`, `Z` turn it about that world axis through the pivot, a typed number is degrees, the pointer going round the pivot on the screen turns it, click or Enter confirms, Esc cancels. Flying with R needs Shift.
|
|
165
|
+
|
|
166
|
+
## Loop cut (edit mode, `Ctrl+R`)
|
|
167
|
+
|
|
168
|
+
Select a line (`2`), press Ctrl+R: every four-sided face on the ring through that line splits in two, with new points on the ring's lines. It is modal like `G`: type a number for the count of cuts (default 1), move the pointer along the line to slide the cuts (clamped inside the faces), `Enter` confirms, `Esc` cancels. The new loop stays selected, so `G`/`S` act on it at once. Undo restores the mesh.
|
|
169
|
+
|
|
170
|
+
## Fill (edit mode, `F`)
|
|
171
|
+
|
|
172
|
+
`F` makes one new face of the selected **points** (or the points of the selected **lines**): they are ordered round their middle in their own plane, the face is wound so that it faces away from the middle of the object, and it takes the material of a neighbouring face (else the palette's). It closes a hole in a solid. It needs three or more points that do not lie on a line. One `U` step. Flying down with F needs Shift.
|
|
173
|
+
|
|
174
|
+
Concave outlines (an L, a V, a star) are filled too: the lines are **followed** instead of the points being ordered by angle (`outlineOf` in `src/wire.ts`). The selected lines, or for selected points the open lines of the mesh between them, make a graph; the walk starts at the outermost point and at a junction goes on where the direction turns least (the tangent rule of Rustroke's fill). With no closed walk it falls back to the angle order, which is right for any convex outline. A face may be concave: every consumer (the GPU / WebGL vertex list, the kernel's pick triangles, `pick`, `nearestSurface`, the faces `moveVertex` / `reshape` split) cuts it with `triangulate` (`src/triangulate.ts`): a fan for a convex polygon, ear clipping for a concave one, so no triangle lies outside the outline.
|
|
175
|
+
|
|
176
|
+
## Object grab, duplicate, delete (every mode)
|
|
177
|
+
|
|
178
|
+
`G` outside edit mode grabs the whole target (`T`) or the object under the cross, with the same `X`/`Y`/`Z` axis locks and typed metres as in edit mode (the same modal code, `startXf` with an `obj`). `Ctrl+D` makes a copy as a new model (not an instance; also from edit mode, which it leaves) and grabs it at once; Esc leaves the copy where the original is. Delete is `Shift+X` (or `Delete`) in every mode; plain `X` only locks the axis during a grab, scale or rotate.
|
|
179
|
+
|
|
180
|
+
## Extrude points and lines, build the sketch (edit mode)
|
|
181
|
+
|
|
182
|
+
`E` extrudes whatever is selected and grabs it at once (`startXf` with `wire0` / `fixFaces`): a point (`1`) becomes a line (a new point joined to it by a sketch line, `wire`), a line (`2`) becomes a four-sided face whose new line is selected, so `E` again goes on; neighbours in the selection share their new points. The face turns to show its front to the camera while you pull. Undo restores mesh and lines in one step.
|
|
183
|
+
|
|
184
|
+
Key `5` is the sketch mode (`emode` `draw`). `V` draws lines on the surface (`obj.lines`); `Enter` (or `5` again) turns them into the mesh. Lines that follow on from each other form a chain. A chain of four or more points whose last point is within a third of the mean stroke of its first (or exactly on it) is closed: it becomes a face, wound away from the middle of the object, in the current material, reusing the vertices that already exist at its points. Any other chain becomes `wire` lines. The faces are selected afterwards.
|
|
185
|
+
|
|
186
|
+
## Smooth shading follows the material's soft
|
|
187
|
+
|
|
188
|
+
The Smooth switch (`N`) rounds the shading of shallow folds (up to 40°), but only where the materials allow it: a face's `soft` of 0 keeps all its edges hard, `soft` 0.5 and above allows the whole 40°, between them the angle grows in step (`cornerNormals(mesh, crease, soft)`); between two faces the harder one rules.
|
|
189
|
+
|
|
190
|
+
## Delete in edit mode
|
|
191
|
+
|
|
192
|
+
`Shift+X` with a selection deletes only that (`deleteSelection`): points go with the faces and lines that use them (and are removed from the vertex list), a line goes with the faces on it, a face goes alone. One undo step. With nothing selected it deletes the object under the cross, as everywhere.
|
|
193
|
+
|
|
194
|
+
## Sketch faces and paint, refined
|
|
195
|
+
|
|
196
|
+
The faces `5` builds are put a hair (0.4% of the object's radius) above the surface, outward, with new points of their own, and take the selected material. Ctrl (Cmd) + press paints a face; two presses within 450 ms paint the whole model (counted in `pointerdown`, because a Ctrl+click on a Mac is a right click and never sends `dblclick`).
|
|
197
|
+
|
|
198
|
+
## Paint by hovering (Alt+Ctrl)
|
|
199
|
+
|
|
200
|
+
With the palette open, holding `Alt`+`Ctrl` (`Cmd` on a Mac) paints every face the pointer passes over with the selected material, no click needed, in edit mode as well (`scrub`, driven by the hit of each frame). One stroke, until Alt or Ctrl is let go, is one undo step.
|
|
201
|
+
|
|
202
|
+
## Record and play
|
|
203
|
+
|
|
204
|
+
`rec` / `play` in the console (sys group): a recording is JSON `{ format: '3draft-recording', version: 1, seed, view: [w, h], start: { materials, current, toggles }, duration, events: [{ t, e, ... }] }`. Events are the trusted `pointerdown/move/up/cancel`, `keydown/up`, `wheel`, `click`, `dblclick`, `contextmenu`, `input`, `change` (target as a CSS path, pointer positions in window pixels, modifiers `c s a m`). Mouse moves are thinned to ~12 ms, the newest dropped one is kept before the next other event.
|
|
205
|
+
|
|
206
|
+
Play clears the scene (and the undo history), restores the palette and switches, seeds `Math.random`, then dispatches synthetic events at their times (gaps over 3 s cut, a frame is let to draw between a move and the next key so the cross is known). Pointer capture is faked while playing and real input is blocked. Prisms still flying in are waited for. The saved palette is not written during a play.
|
|
207
|
+
|
|
208
|
+
API: `rec.start()`, `rec.stop()` → recording, `rec.play(data, { speed, cap })` → `{ done, total, stopped }`, `rec.stopPlay()`, `rec.scene()` (fingerprint), `rec.last`, `?play=<url>`.
|
|
209
|
+
|
|
210
|
+
## Saw and boolean with concave and loose shells
|
|
211
|
+
|
|
212
|
+
`saw` cuts a model of several shells shell by shell: every closed shell is sawn on its own, a shell the plane misses (or one that is not closed) goes whole to the side its middle is on; it still throws when there is no closed shell at all. The BSP of `boolean` / `saw` takes a concave face as the triangles of its ear clipping (`polysOf` in `src/csg.ts`). `E` on a free face (a sketch outline: none of its lines is shared with another face) keeps the old face as a reversed back, so what is pulled out is a closed shell that can be sawn.
|
|
213
|
+
|
|
214
|
+
## Console layout, edge lines, palette
|
|
215
|
+
|
|
216
|
+
Every console group hangs from the same top line; `view` is first, so it stays put when edit mode swaps `make` and `scene` for `edit`. `edit` (1 2 3 4 5 Q / G S R E F ⇧X), `view` (freeze, view, edges, light, palette / store, smooth, studio, glow) and `sys` (undo, reset, zen, help / rec, play) are two rows each (`.grp.rows` with `.row`). `light` is a switch: every press (or `L`) turns the sun a step of 45°, it is on while the sun is turned from its start. `rec` is red.
|
|
217
|
+
|
|
218
|
+
`edges` (the E switch) works in the GPU views too: `render({ edges: true })` draws the face edges (`buildEdges` / `sceneEdges` in `src/gpu.ts`, each vertex pair once, cached per part, none above 300 000 faces) as 1-pixel `line-list` / `gl.LINES` after the solid faces, dark at 40% alpha, depth tested with a hair of nudge towards the eye so a line wins against the face it lies on.
|
|
219
|
+
|
|
220
|
+
With the palette open (`body.pal`, desktop) the view, the top line and the console get narrower by the width of the panel (`--palR`) instead of staying behind it; on the phone layout the panel still covers the view.
|
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ The engine is small enough to read whole (about 2000 lines) and everything it wo
|
|
|
163
163
|
|
|
164
164
|
Y is up, the ground is y = 0, faces are counter-clockwise seen from outside.
|
|
165
165
|
|
|
166
|
-
Examples: `examples/table.js`, `examples/bridge.js` (50 named objects), `examples/cessna.js` (one mesh, 9 colours), and `examples/head.js` (a stylized head: 7 objects with their own palette, soft skin; try N and K), and `examples/house.js` (a house converted from Blender with `tools/blend/`, 45 objects, approximate colours).
|
|
166
|
+
Examples: `examples/table.js`, `examples/bridge.js` (50 named objects), `examples/cessna.js` (one mesh, 9 colours), and `examples/head.js` (a stylized head: 7 objects with their own palette, soft skin; try N and K), `examples/globe.js` (a globe: an ocean ball and seven continents, each one closed mesh), and `examples/house.js` (a house converted from Blender with `tools/blend/`, 45 objects, approximate colours).
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
@@ -179,3 +179,8 @@ npm run app # the viewer
|
|
|
179
179
|
Edit `src/*.ts`, never `dist/`. Versions are date based, `YY.MM.DD.NNN`, published as semver `YY.M.DNNN` (`26.11.01.002` is `26.11.1002`); `npm run bump` sets the next one.
|
|
180
180
|
|
|
181
181
|
MIT
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## UI
|
|
185
|
+
|
|
186
|
+
The viewer has a control-panel look (black ground, cream/teal/red keycaps). **?** (or the Keys button) opens an interactive key explainer: hover or tap a key to read what it does (always / target / isolated / edit mode), press a real key to see it light up, **run this key** performs it. **Z** (or Zen) hides everything but the model; Z or Esc brings the UI back. The layout is responsive and never leaves the screen.
|
package/dist/camera.js
CHANGED
|
@@ -24,12 +24,13 @@ export const makeCamera = (view, mesh, size, fit) => {
|
|
|
24
24
|
const ortho = Boolean(spec.ortho);
|
|
25
25
|
const { center, radius } = fit ?? bounds(mesh);
|
|
26
26
|
const r0 = radius || 1;
|
|
27
|
-
const dist = ortho ? r0 * 4 : (fit?.dist ?? (r0 * 1.25) / Math.sin(FOV / 2));
|
|
27
|
+
const dist = ortho ? r0 * 4 : (fit?.dist ?? (r0 * 1.25) / Math.sin(FOV / 2));
|
|
28
|
+
const fov = fit?.fov ?? FOV; // fit.dist: eye distance override (a first-person eye sits almost on the center)
|
|
28
29
|
const eye = plus(center)(times(dist)(norm(spec.dir)));
|
|
29
30
|
const f = norm(minus(eye)(center)); // forward
|
|
30
31
|
const r = norm(cross(f, spec.up)); // right
|
|
31
32
|
const u = cross(r, f); // up
|
|
32
|
-
const k = ortho ? ((size / 2) * 0.8) / r0 : size / 2 / Math.tan(
|
|
33
|
+
const k = ortho ? ((size / 2) * 0.8) / r0 : size / 2 / Math.tan(fov / 2);
|
|
33
34
|
return { name, eye, f, r, u, ortho, k, size };
|
|
34
35
|
};
|
|
35
36
|
/**
|
package/dist/csg.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface Health {
|
|
|
19
19
|
/** true when every part is a closed solid. */
|
|
20
20
|
allSolid: boolean;
|
|
21
21
|
}
|
|
22
|
+
/** The connected parts of a mesh as separate meshes (each with its own vertex list), in face order of their first face. */
|
|
23
|
+
export declare const splitParts: (mesh: Mesh) => Mesh[];
|
|
22
24
|
/** Splits a mesh into connected parts and says which of them are closed solids (the health check). */
|
|
23
25
|
export declare const health: (mesh: Mesh) => Health;
|
|
24
26
|
/**
|
package/dist/csg.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// the rest of the engine assumes. The result is welded and its T-junctions are closed, so it is a closed
|
|
5
5
|
// solid again and can go into the next boolean. Faces keep the color and material of the face they came from;
|
|
6
6
|
// a face cut into a solid by the other one takes the color of the cutting face.
|
|
7
|
+
import { triangulate } from './triangulate.js';
|
|
7
8
|
import { sub, dot, cross, len } from './vec.js';
|
|
8
9
|
import { faceNormal, bounds } from './mesh.js';
|
|
9
10
|
import { validate } from './check.js';
|
|
@@ -12,10 +13,16 @@ const unit = (v) => {
|
|
|
12
13
|
const l = len(v) || 1;
|
|
13
14
|
return [v[0] / l, v[1] / l, v[2] / l];
|
|
14
15
|
};
|
|
16
|
+
// The BSP needs convex polygons: a concave face (an L, a V, a sketch outline) goes in as the triangles of its ear clipping.
|
|
15
17
|
const polysOf = (mesh) => mesh.faces.flatMap((f) => {
|
|
16
18
|
const pts = f.v.map((k) => mesh.positions[k]);
|
|
17
19
|
const n = unit(faceNormal(pts));
|
|
18
|
-
|
|
20
|
+
if (len(faceNormal(pts)) < 1e-12)
|
|
21
|
+
return [];
|
|
22
|
+
const tris = triangulate(pts);
|
|
23
|
+
if (tris.every(([a, b, c], j) => a === 0 && b === j + 1 && c === j + 2))
|
|
24
|
+
return [{ pts, n, w: dot(n, pts[0]), color: f.color, mat: f.mat }];
|
|
25
|
+
return tris.map((t) => ({ pts: t.map((i) => pts[i]), n, w: dot(n, pts[0]), color: f.color, mat: f.mat }));
|
|
19
26
|
});
|
|
20
27
|
const flip = (p) => ({ ...p, pts: [...p.pts].reverse(), n: [-p.n[0], -p.n[1], -p.n[2]], w: -p.w });
|
|
21
28
|
/** Cuts `p` by the plane into the four buckets. */
|
|
@@ -205,6 +212,11 @@ const subMesh = (mesh, idx) => {
|
|
|
205
212
|
});
|
|
206
213
|
return { ...mesh, positions, faces };
|
|
207
214
|
};
|
|
215
|
+
/** The connected parts of a mesh as separate meshes (each with its own vertex list), in face order of their first face. */
|
|
216
|
+
export const splitParts = (mesh) => health(mesh)
|
|
217
|
+
.parts.map((p) => p.faces)
|
|
218
|
+
.sort((a, b) => a[0] - b[0])
|
|
219
|
+
.map((faces) => subMesh(mesh, faces));
|
|
208
220
|
/** Splits a mesh into connected parts and says which of them are closed solids (the health check). */
|
|
209
221
|
export const health = (mesh) => {
|
|
210
222
|
const n = mesh.faces.length;
|
package/dist/gl.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Gpu } from './gpu.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sets up WebGL2 on a canvas. Rejects when WebGL2 is not available. Same call and options as createGpu:
|
|
4
|
+
* returns { render(mesh, view, options), pixels(...), destroy() }. `device.lost` resolves when the context is lost.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createGl: (canvas: HTMLCanvasElement, { onError }?: {
|
|
7
|
+
onError?: (message: string) => void;
|
|
8
|
+
}) => Promise<Gpu>;
|