@zkmake/three-audit 0.2.1 → 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 +110 -14
- package/dist/audit-scene-CJG1DgRF.js +725 -0
- package/dist/audit-scene-CJG1DgRF.js.map +1 -0
- package/dist/cli.js +11 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +184 -24
- package/dist/index.js +459 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/audit-scene-D5ZaENGZ.js +0 -425
- package/dist/audit-scene-D5ZaENGZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -27,10 +27,20 @@ test("windmill has no z-fighting and no NaN geometry", () => {
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A failure lists each pair of meshes with faces in one plane, facing the same way, overlapping:
|
|
30
|
+
A failure lists each pair of meshes with faces in one plane, facing the same way, overlapping: the
|
|
31
|
+
meshes by their named ancestors, the plane they share, and where they overlap.
|
|
31
32
|
|
|
32
|
-
```
|
|
33
|
-
[
|
|
33
|
+
```ts
|
|
34
|
+
[
|
|
35
|
+
{
|
|
36
|
+
a: "windmill/plinth [stone] @ (0, 0.1, 0)",
|
|
37
|
+
b: "windmill/wall [plaster] @ (0, 0.5, 0)",
|
|
38
|
+
triangles: 4,
|
|
39
|
+
planes: ["y = 0.000, facing -y"],
|
|
40
|
+
overlap: { area: 1, centre: [0, 0, 0] },
|
|
41
|
+
count: 1,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
34
44
|
```
|
|
35
45
|
|
|
36
46
|
## From the command line
|
|
@@ -42,10 +52,11 @@ npx @zkmake/three-audit assets/*.glb # or bunx
|
|
|
42
52
|
```
|
|
43
53
|
|
|
44
54
|
```
|
|
45
|
-
windmill.glb 4,812 triangles
|
|
55
|
+
windmill.glb 4,812 triangles, 14 draws
|
|
46
56
|
✗ 1 z-fighting pair
|
|
47
57
|
windmill/plinth [stone] @ (0, 0.1, 0)
|
|
48
|
-
↔ windmill/wall [plaster] @ (0, 0.5, 0)
|
|
58
|
+
↔ windmill/wall [plaster] @ (0, 0.5, 0)
|
|
59
|
+
4 triangles in y = 0.000, facing -y; overlap 1 m² at (0, 0, 0)
|
|
49
60
|
✓ no NaN geometry
|
|
50
61
|
most triangles:
|
|
51
62
|
1,920 sails
|
|
@@ -93,16 +104,38 @@ useLayoutEffect(() => installAuditHelpers(scene, { renderer: gl }), [scene, gl])
|
|
|
93
104
|
|
|
94
105
|
| Global | Returns |
|
|
95
106
|
| --------------------- | ----------------------------------------------------------------------------------- |
|
|
107
|
+
| `summary(tag?)` | One count per check: the call to run after every edit |
|
|
96
108
|
| `tris()` | Triangles one pass draws: visible meshes, instances and batches counted, no culling |
|
|
97
109
|
| `meshes()` | Every visible mesh: triangles, instances, total, most first |
|
|
98
|
-
| `census()`
|
|
110
|
+
| `census({ budget? })` | Geometries by triangles drawn, batches broken down, hidden meshes included |
|
|
99
111
|
| `audit()` | Meshes with NaN positions or normals, or zero-length normals |
|
|
100
112
|
| `zfight(gap?, self?)` | Pairs of meshes that z-fight |
|
|
101
|
-
| `report()`
|
|
102
|
-
| `
|
|
113
|
+
| `report(tag?)` | Every check in one JSON-safe object |
|
|
114
|
+
| `bbox(target)` | An object's world envelope: min, max, size, centre |
|
|
115
|
+
| `clearance(a, b)` | The gap between two objects, and which of their meshes come closest |
|
|
116
|
+
| `ledger(options?)` | One frame's draw calls by object and pass, and each pass's size (WebGL) |
|
|
117
|
+
| `beginLedger()` | `{ end() }`: draw calls between the two, for any span you choose (WebGL) |
|
|
103
118
|
| `blackFrames(10)` | Frames in the next 10 s that came out black (WebGL) |
|
|
104
119
|
|
|
105
|
-
|
|
120
|
+
A `tag` or `target` is a `userData.studioObject` value (or your `tagKey`), falling back to an
|
|
121
|
+
object's name; `bbox` and `clearance` also take the object itself.
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
threeAudit.summary("bridge");
|
|
125
|
+
// { triangles: 4812, draws: 14, meshes: 9, zFighting: 0, selfZFighting: 0, badGeometry: 0, emptyMeshes: 0 }
|
|
126
|
+
threeAudit.clearance("train", "tower");
|
|
127
|
+
// { gap: 0.15, axis: "y", between: ["train/car [paint]", "tower/crossbar [steel]"] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Each one is also on `threeAudit` (`threeAudit.report()`). Where short names like `report` collide
|
|
131
|
+
with other tooling, `installAuditHelpers(scene, { globals: false })` installs only `threeAudit`.
|
|
132
|
+
|
|
133
|
+
From a headless browser: `agent-browser eval 'JSON.stringify(threeAudit.report())'`.
|
|
134
|
+
|
|
135
|
+
A tab an agent drives is often in the background, where the browser pauses animation frames.
|
|
136
|
+
`ledger()` and `blackFrames()` fail after 2 s with an error that says so, instead of waiting for
|
|
137
|
+
ever. To record a background tab's draws, have the ledger record one call of your app's own
|
|
138
|
+
render: `ledger({ render: () => app.renderOnce() })`. Black frames need a visible tab.
|
|
106
139
|
|
|
107
140
|
## The checks
|
|
108
141
|
|
|
@@ -119,7 +152,19 @@ World space, so parent transforms count.
|
|
|
119
152
|
- `skip(object)`: leave an object and its subtree out, e.g. a merged copy whose sources are still in
|
|
120
153
|
the scene: `skip: (o) => o.userData.bakedResult === true`.
|
|
121
154
|
|
|
122
|
-
|
|
155
|
+
Each row names both meshes by their named ancestors (`bridge/deck/slab [concrete]`), the planes they
|
|
156
|
+
share (`z = -3.000, facing -z`; opposite-facing faces never pair), and the overlap's area in world
|
|
157
|
+
units² and its centre. Identical pairs, such as pooled copies of one prop parked at one spot, fold
|
|
158
|
+
into one row with a `count`.
|
|
159
|
+
|
|
160
|
+
Left out because they can't fight:
|
|
161
|
+
|
|
162
|
+
- hidden objects
|
|
163
|
+
- instanced and batched meshes, and `InstancedBufferGeometry`, which a shader positions. Check
|
|
164
|
+
their source parts as plain meshes.
|
|
165
|
+
- pairs where neither material writes depth, or one ignores depth entirely. A decal that doesn't
|
|
166
|
+
write depth, over a wall that does, still fights and is still listed.
|
|
167
|
+
- pairs where either material has a polygon offset, the usual decal fix
|
|
123
168
|
|
|
124
169
|
Typical fixes: stand the smaller part 5 mm off, shorten it inside the larger one, or fit panels
|
|
125
170
|
between each other instead of over each other's ends.
|
|
@@ -129,17 +174,62 @@ between each other instead of over each other's ends.
|
|
|
129
174
|
Vertices with a non-finite position or normal, and zero-length normals on triangles with area.
|
|
130
175
|
Either lights a pixel NaN, and bloom smears one NaN pixel into a black block.
|
|
131
176
|
|
|
132
|
-
### `
|
|
177
|
+
### `summarizeScene(root, options?)`
|
|
178
|
+
|
|
179
|
+
Every check as one count: `{ triangles, draws, meshes, zFighting, selfZFighting, badGeometry,
|
|
180
|
+
emptyMeshes }`. `tag` narrows it to one tagged object. `zFighting: false` skips the slowest check
|
|
181
|
+
(its counts come back `null`) for a quick look at a large scene.
|
|
182
|
+
|
|
183
|
+
### `countTriangles`, `listMeshes`, `geometryCensus`, `countDraws`
|
|
133
184
|
|
|
134
185
|
Where a triangle budget goes. Name meshes and geometries (`mesh.name`, `geometry.name`) so the rows
|
|
135
|
-
say something.
|
|
186
|
+
say something. A mesh row's `name` is its named ancestors, then its own name, or its geometry's name
|
|
187
|
+
or type when it has none (`CharacterHands/left/BoxGeometry`).
|
|
188
|
+
|
|
189
|
+
`geometryCensus` lists a `BatchedMesh` one geometry at a time: visible instances × triangles each,
|
|
190
|
+
which is where a batch's budget actually goes. Rows are `batch#id` unless `label(batch, geometryId)`
|
|
191
|
+
names them from the app's own records. Each row has its `share` of the total; with `budget: 0.2`,
|
|
192
|
+
rows over 20% get `overBudget: true`.
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
geometry uses triangles total share overBudget
|
|
196
|
+
treadmill_belt 570 513 292,410 0.306 true
|
|
197
|
+
treadmill_coupler 570 297 169,290 0.177 false
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`countDraws` counts the draw calls one main pass makes: one per visible mesh, line or point cloud,
|
|
201
|
+
one per material group when a mesh has several. It doesn't cull and doesn't count shadows; the draw
|
|
202
|
+
ledger counts the real thing.
|
|
203
|
+
|
|
204
|
+
### `findEmptyMeshes(root)`
|
|
205
|
+
|
|
206
|
+
Meshes with no vertices, such as what merging nothing leaves. Each one still costs a draw call and
|
|
207
|
+
a geometry to dispose. A zero draw range doesn't count, since that's usually a pool waiting to fill.
|
|
208
|
+
|
|
209
|
+
### `measureBounds(root, target)` / `measureClearance(root, a, b)`
|
|
210
|
+
|
|
211
|
+
An object's world envelope, and the smallest gap between two objects, from their vertices as drawn:
|
|
212
|
+
instances and batches included, rotations exact. The gap is measured between triangles' bounding
|
|
213
|
+
boxes, which is exact for faces square to the axes and slightly under for sloped ones. So a tower
|
|
214
|
+
merged into one mesh still measures to its crossbar, not to the box around the whole tower. `axis`
|
|
215
|
+
says which way the gap runs when it's straight along one axis. Draw ranges are respected; a shadow pass adds roughly as much again per light.
|
|
136
216
|
|
|
137
217
|
### `recordDrawLedger(renderer)` / `beginDrawLedger(renderer)`
|
|
138
218
|
|
|
139
219
|
Wraps `WebGLRenderer.renderBufferDirect` and counts every draw by object name, by group (the name up
|
|
140
220
|
to its first `:` or `#`), and by pass: `shadow`, then each `render()` call by scene name, or
|
|
141
|
-
`render 1`, `render 2`…
|
|
142
|
-
|
|
221
|
+
`render 1`, `render 2`… An unnamed object goes by its named ancestors, type and material
|
|
222
|
+
(`CharacterHands/Mesh [skin]`).
|
|
223
|
+
|
|
224
|
+
`recordDrawLedger` records exactly one animation frame, and fails after `timeout` (2 s) when no
|
|
225
|
+
frame comes. `{ render }` records one call of your own render instead, frames or not.
|
|
226
|
+
`beginDrawLedger` returns `{ end() }` for any span. `printDrawLedger` prints the tables.
|
|
227
|
+
|
|
228
|
+
`passStats` sizes each pass: draws, distinct objects (for `shadow`, the casters), triangles, and
|
|
229
|
+
the targets drawn to in device pixels (`screen 2880×1800` for the canvas). A pass is `fullscreen`
|
|
230
|
+
when each call drew one small mesh through an orthographic camera, the shape of a post-processing
|
|
231
|
+
pass. `fullscreenPasses` and `fullscreenPixels` total them: 8 fullscreen passes at 2880×1800 shade
|
|
232
|
+
41 Mpx a frame before the scene itself draws a pixel.
|
|
143
233
|
|
|
144
234
|
### `watchBlackFrames(renderer)`
|
|
145
235
|
|
|
@@ -153,6 +243,12 @@ stalls the GPU, so watch, read `blackFrames`, `stop()`.
|
|
|
153
243
|
non-enumerable property (`row.mesh`, `row.meshes`), so they print and serialise without dragging the
|
|
154
244
|
scene along.
|
|
155
245
|
|
|
246
|
+
## Types
|
|
247
|
+
|
|
248
|
+
The checks take any object with three's `isObject3D` flag, and the renderer as only the methods
|
|
249
|
+
they call. An app on a newer `@types/three` than the one this package was built against passes its
|
|
250
|
+
scene and renderer without a cast.
|
|
251
|
+
|
|
156
252
|
## License
|
|
157
253
|
|
|
158
254
|
MIT
|