@zkmake/three-audit 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -47
- package/dist/{audit-scene-DHTFd-zs.js → audit-scene-BjB8raDp.js} +2 -2
- package/dist/{audit-scene-DHTFd-zs.js.map → audit-scene-BjB8raDp.js.map} +1 -1
- package/dist/{budgets-DQggDhEu.js → budgets-BtXN-bkX.js} +2 -53
- package/dist/{budgets-DQggDhEu.js.map → budgets-BtXN-bkX.js.map} +1 -1
- package/dist/{budgets-Dl-AOd9T.d.ts → budgets-DWEObe-R.d.ts} +3 -45
- package/dist/budgets.d.ts +2 -0
- package/dist/budgets.js +1 -0
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/define-budgets-77olu5Xb.js +55 -0
- package/dist/define-budgets-77olu5Xb.js.map +1 -0
- package/dist/define-budgets-DH6-ZyZy.d.ts +50 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/vitest.d.ts +2 -1
- package/dist/vitest.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -52,7 +52,10 @@ or a `setupFiles` module:
|
|
|
52
52
|
```ts
|
|
53
53
|
import "@zkmake/three-audit/vitest";
|
|
54
54
|
|
|
55
|
+
import { expect, test } from "vitest";
|
|
56
|
+
|
|
55
57
|
import budgets from "../src/budgets.ts";
|
|
58
|
+
import { buildKart } from "../src/kart.ts";
|
|
56
59
|
|
|
57
60
|
test("the kart", () => {
|
|
58
61
|
const kart = buildKart();
|
|
@@ -81,10 +84,10 @@ A failure says what to fix, as the CLI does:
|
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
`toFitBudget` takes `{ skip, top }` (meshes listed, default 5). A frame budget's `calls` counts as
|
|
84
|
-
`draws
|
|
85
|
-
(`expect.toFitBudget(…)`), and their types come with
|
|
86
|
-
them again, for a setup that resets `expect`; `matchers`
|
|
87
|
-
entry re-exports `triangles`. Vitest 5 or later.
|
|
87
|
+
`draws`; a budget that sets none of triangles, draws (calls) or meshes throws. The matchers work
|
|
88
|
+
negated (`.not`) and as asymmetric matchers (`expect.toFitBudget(…)`), and their types come with
|
|
89
|
+
the import. `installMatchers()` registers them again, for a setup that resets `expect`; `matchers`
|
|
90
|
+
is the object for `expect.extend`. The entry re-exports `triangles`. Vitest 5 or later.
|
|
88
91
|
|
|
89
92
|
## Budgets
|
|
90
93
|
|
|
@@ -94,7 +97,7 @@ place.
|
|
|
94
97
|
|
|
95
98
|
```ts
|
|
96
99
|
// src/budgets.ts
|
|
97
|
-
import { defineBudgets } from "@zkmake/three-audit";
|
|
100
|
+
import { defineBudgets } from "@zkmake/three-audit/budgets";
|
|
98
101
|
|
|
99
102
|
export default defineBudgets({
|
|
100
103
|
frame: { fps: 60, calls: 150, triangles: 250_000 },
|
|
@@ -115,10 +118,13 @@ export default defineBudgets({
|
|
|
115
118
|
| `parts.*.draws` | Draw calls one main pass makes for it (`countDraws`) |
|
|
116
119
|
| `parts.*.meshes` | Visible meshes in it |
|
|
117
120
|
|
|
121
|
+
`@zkmake/three-audit/budgets` holds `defineBudgets` and `parseBudgets` (and their types) alone,
|
|
122
|
+
with no imports, so the app's own code can import the file (its HUD reads it) and ship a few hundred
|
|
123
|
+
bytes, not the checks; the main entry exports both too. `frame` is required, `parts` optional.
|
|
118
124
|
`defineBudgets` returns them frozen, with each part in the type: `budgets.parts.truck` is an error
|
|
119
125
|
when no truck is listed. `parseBudgets(value)` checks budgets from anywhere else (JSON) and throws
|
|
120
|
-
on a bad key. `checkBudget(object, budget)
|
|
121
|
-
`{ metric, actual, limit, over }`.
|
|
126
|
+
on a bad key. `checkBudget(object, budget, { skip }?)`, in the main entry, measures an object
|
|
127
|
+
against one, a row per limit: `{ metric, actual, limit, over }`.
|
|
122
128
|
|
|
123
129
|
- In a test: `expect(kart).toFitBudget(budgets.parts.kart)`.
|
|
124
130
|
- In the HUD: `mountPerfHud(monitor, { budgets })` (or `<PerfHud budgets={budgets} />`) applies the
|
|
@@ -145,9 +151,11 @@ windmill.glb 4,812 triangles, 14 draws
|
|
|
145
151
|
…
|
|
146
152
|
```
|
|
147
153
|
|
|
148
|
-
It exits 1 when any model fails a check (2 when one can't be read), so it can gate CI.
|
|
149
|
-
`--gap <m>`, `--self`, `--skip <regex>` (leave out
|
|
150
|
-
`--json
|
|
154
|
+
It exits 1 when any model fails a check (2 when one can't be read), so it can gate CI. Empty meshes
|
|
155
|
+
are listed (`!`) but don't fail. Options: `--gap <m>`, `--self`, `--skip <regex>` (leave out
|
|
156
|
+
objects by name, e.g. decals), `--top <n>` (default 5, 0 for none), `--json` (an array of
|
|
157
|
+
`{ file, report, budget }`, `report` being `auditScene`'s), `--no-fail`,
|
|
158
|
+
`-h` / `--help`, `-v` / `--version`.
|
|
151
159
|
|
|
152
160
|
`--budgets <file>` also holds each model to the app's [budgets](#budgets): a JSON file, or a module
|
|
153
161
|
whose default export is them (`.js`, or `.ts` where Node strips types). A model answers to the part
|
|
@@ -172,12 +180,18 @@ In a test, load a model the same way:
|
|
|
172
180
|
```ts
|
|
173
181
|
import { findZFighting } from "@zkmake/three-audit";
|
|
174
182
|
import { loadModel } from "@zkmake/three-audit/node";
|
|
183
|
+
import { expect, test } from "vitest";
|
|
175
184
|
|
|
176
185
|
test("windmill.glb has no z-fighting", async () => {
|
|
177
186
|
expect(findZFighting(await loadModel("assets/windmill.glb"))).toEqual([]);
|
|
178
187
|
});
|
|
179
188
|
```
|
|
180
189
|
|
|
190
|
+
`parseModel(bytes, directory?)` does the same from bytes already read (GLB or glTF JSON, its
|
|
191
|
+
buffers resolved from `directory`). `createNodeDracoLoader()` is the in-process Draco decoder it
|
|
192
|
+
uses, for your own `GLTFLoader` in Node: it has the two methods `GLTFLoader` calls on a
|
|
193
|
+
`DRACOLoader`, so pass it with a cast (`loader.setDRACOLoader(draco as never)`).
|
|
194
|
+
|
|
181
195
|
glTF is in metres, which is what `--gap`'s default assumes. Instanced meshes
|
|
182
196
|
(`EXT_mesh_gpu_instancing`) aren't checked for z-fighting; skinned and morphing meshes are
|
|
183
197
|
checked in their rest pose.
|
|
@@ -198,23 +212,24 @@ const { scene, gl } = useThree();
|
|
|
198
212
|
useLayoutEffect(() => installAuditHelpers(scene, { renderer: gl }), [scene, gl]);
|
|
199
213
|
```
|
|
200
214
|
|
|
201
|
-
| Global
|
|
202
|
-
|
|
|
203
|
-
| `summary(tag?)`
|
|
204
|
-
| `tris()`
|
|
205
|
-
| `meshes()`
|
|
206
|
-
| `census({ budget? })` | Geometries by triangles drawn, batches broken down, hidden meshes included
|
|
207
|
-
| `audit()`
|
|
208
|
-
| `zfight(gap?, self?)`
|
|
209
|
-
| `report(tag?)`
|
|
210
|
-
| `bbox(target)`
|
|
211
|
-
| `clearance(a, b)`
|
|
212
|
-
| `ledger(options?)`
|
|
213
|
-
| `beginLedger()`
|
|
214
|
-
| `blackFrames(10)`
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
| Global | Returns |
|
|
216
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
217
|
+
| `summary(tag?, options?)` | One count per check: the call to run after every edit. `{ zFighting: false }` skips the slow one |
|
|
218
|
+
| `tris()` | Triangles one pass draws: visible meshes, instances and batches counted, no culling |
|
|
219
|
+
| `meshes()` | Every visible mesh: triangles, instances, total, most first |
|
|
220
|
+
| `census({ budget?, label? })` | Geometries by triangles drawn, batches broken down, hidden meshes included |
|
|
221
|
+
| `audit()` | Meshes with NaN positions or normals, or zero-length normals |
|
|
222
|
+
| `zfight(gap?, self?)` | Pairs of meshes that z-fight |
|
|
223
|
+
| `report(tag?)` | Every check in one JSON-safe object |
|
|
224
|
+
| `bbox(target)` | An object's world envelope: min, max, size, centre |
|
|
225
|
+
| `clearance(a, b)` | The gap between two objects, and which of their meshes come closest |
|
|
226
|
+
| `ledger(options?)` | One frame's draw calls by object and pass, and each pass's size (WebGL) |
|
|
227
|
+
| `beginLedger()` | `{ end() }`: draw calls between the two, for any span you choose (WebGL) |
|
|
228
|
+
| `blackFrames(10)` | `{ frames, blackFrames }`: frames in the next 10 s that came out black (WebGL) |
|
|
229
|
+
| `auditRoot` | The root the helpers were installed on |
|
|
230
|
+
|
|
231
|
+
A `tag` is a `userData.studioObject` value (or your `tagKey`); a tag nothing carries throws. A
|
|
232
|
+
`target` (`bbox`, `clearance`) is a tag too, falling back to an object's name, or the object itself.
|
|
218
233
|
|
|
219
234
|
```js
|
|
220
235
|
threeAudit.summary("bridge");
|
|
@@ -225,6 +240,11 @@ threeAudit.clearance("train", "tower");
|
|
|
225
240
|
|
|
226
241
|
Each one is also on `threeAudit` (`threeAudit.report()`). Where short names like `report` collide
|
|
227
242
|
with other tooling, `installAuditHelpers(scene, { globals: false })` installs only `threeAudit`.
|
|
243
|
+
The other options: `renderer` (the WebGL renderer `ledger`, `beginLedger` and `blackFrames` need;
|
|
244
|
+
without it they throw), `skip`, `tagKey`, [`bakes`](#baked-groups), and `batchLabel`, which names
|
|
245
|
+
the geometries inside a batch in `census()` (`geometryCensus`'s `label`).
|
|
246
|
+
[three-devtools](../three-devtools) installs them behind `?debug` (its `audit` option), and
|
|
247
|
+
[three-studio](../three-studio)'s pages install them on their own scene.
|
|
228
248
|
|
|
229
249
|
From a headless browser: `agent-browser eval 'JSON.stringify(threeAudit.report())'`.
|
|
230
250
|
|
|
@@ -268,7 +288,9 @@ between each other instead of over each other's ends.
|
|
|
268
288
|
### `findBadGeometry(root, options?)`
|
|
269
289
|
|
|
270
290
|
Vertices with a non-finite position or normal, and zero-length normals on triangles with area.
|
|
271
|
-
Either lights a pixel NaN, and bloom smears one NaN pixel into a black block.
|
|
291
|
+
Either lights a pixel NaN, and bloom smears one NaN pixel into a black block. Hidden meshes are
|
|
292
|
+
checked too. A row is `{ mesh, under, nonFinite, zeroNormals }`, `under` being the tagged part it
|
|
293
|
+
sits in (or its parent's name). Options: `skip`, `tagKey`, `bakes`.
|
|
272
294
|
|
|
273
295
|
### `summarizeScene(root, options?)`
|
|
274
296
|
|
|
@@ -278,10 +300,12 @@ emptyMeshes }`. `tag` narrows it to one tagged object. `zFighting: false` skips
|
|
|
278
300
|
|
|
279
301
|
### `countTriangles`, `listMeshes`, `geometryCensus`, `countDraws`, `triangles`
|
|
280
302
|
|
|
281
|
-
Where a triangle budget goes. `
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
303
|
+
Where a triangle budget goes. `countTriangles` counts what one pass draws, with no culling; a shadow
|
|
304
|
+
pass adds roughly as much again per shadow-casting light. `triangles(target)` is the one a test
|
|
305
|
+
reaches for: a geometry's own triangles (`triangleCount`, within its draw range), or what an object
|
|
306
|
+
draws (`countTriangles`). Name meshes and geometries (`mesh.name`, `geometry.name`) so the rows say
|
|
307
|
+
something. A mesh row's `name` is its named ancestors, then its own name, or its geometry's name or
|
|
308
|
+
type when it has none (`CharacterHands/left/BoxGeometry`).
|
|
285
309
|
|
|
286
310
|
`geometryCensus` lists a `BatchedMesh` one geometry at a time: visible instances × triangles each,
|
|
287
311
|
which is where a batch's budget actually goes. Rows are `batch#id` unless `label(batch, geometryId)`
|
|
@@ -294,29 +318,32 @@ treadmill_belt 570 513 292,410 0.306 true
|
|
|
294
318
|
treadmill_coupler 570 297 169,290 0.177 false
|
|
295
319
|
```
|
|
296
320
|
|
|
297
|
-
`countDraws` counts the draw calls one main pass makes: one per visible mesh, line
|
|
298
|
-
one per material group when a mesh has several. It doesn't cull and doesn't count shadows;
|
|
299
|
-
ledger counts the real thing.
|
|
321
|
+
`countDraws` counts the draw calls one main pass makes: one per visible mesh, line, point cloud or
|
|
322
|
+
sprite, one per material group when a mesh has several. It doesn't cull and doesn't count shadows;
|
|
323
|
+
the draw ledger counts the real thing.
|
|
300
324
|
|
|
301
|
-
### `findEmptyMeshes(root)`
|
|
325
|
+
### `findEmptyMeshes(root, options?)`
|
|
302
326
|
|
|
303
327
|
Meshes with no vertices, such as what merging nothing leaves. Each one still costs a draw call and
|
|
304
328
|
a geometry to dispose. A zero draw range doesn't count, since that's usually a pool waiting to fill.
|
|
329
|
+
Hidden meshes are included; it takes `findBadGeometry`'s options.
|
|
305
330
|
|
|
306
|
-
### `measureBounds(root, target)` / `measureClearance(root, a, b)`
|
|
331
|
+
### `measureBounds(root, target, options?)` / `measureClearance(root, a, b, options?)`
|
|
307
332
|
|
|
308
333
|
An object's world envelope, and the smallest gap between two objects, from their vertices as drawn:
|
|
309
334
|
instances and batches included, rotations exact. The gap is measured between triangles' bounding
|
|
310
335
|
boxes, which is exact for faces square to the axes and slightly under for sloped ones. So a tower
|
|
311
336
|
merged into one mesh still measures to its crossbar, not to the box around the whole tower. `axis`
|
|
312
|
-
says which way the gap runs when it's straight along one axis. Draw ranges
|
|
337
|
+
says which way the gap runs when it's straight along one axis (`null` when diagonal). Draw ranges
|
|
338
|
+
are respected. A target matching nothing throws; one that draws nothing gives `null`. Options:
|
|
339
|
+
`tagKey`, `bakes`.
|
|
313
340
|
|
|
314
341
|
### `recordDrawLedger(renderer)` / `beginDrawLedger(renderer)`
|
|
315
342
|
|
|
316
343
|
Wraps `WebGLRenderer.renderBufferDirect` and counts every draw by object name, by group (the name up
|
|
317
|
-
to its first `:` or
|
|
318
|
-
`render 1`, `render 2`… An unnamed object goes by its named ancestors, type and
|
|
319
|
-
(`CharacterHands/Mesh [skin]`).
|
|
344
|
+
to its first `:` or `#`, or your `groupOf(name)`), and by pass: `shadow`, then each `render()` call
|
|
345
|
+
by scene name, or `render 1`, `render 2`… An unnamed object goes by its named ancestors, type and
|
|
346
|
+
material (`CharacterHands/Mesh [skin]`).
|
|
320
347
|
|
|
321
348
|
Each row (object or group) carries its draws per pass (`passes`) and its triangles, in all
|
|
322
349
|
(`triangles`) and per pass (`passTriangles`): what a frame really draws of each part, so the city
|
|
@@ -325,7 +352,8 @@ pass, after culling them one by one.
|
|
|
325
352
|
|
|
326
353
|
`recordDrawLedger` records exactly one animation frame, and fails after `timeout` (2 s) when no
|
|
327
354
|
frame comes. `{ render }` records one call of your own render instead, frames or not.
|
|
328
|
-
`beginDrawLedger` returns `{ end() }` for any span. `printDrawLedger` prints the tables
|
|
355
|
+
`beginDrawLedger` returns `{ end() }` for any span. `printDrawLedger` prints the tables;
|
|
356
|
+
`ledgerTable(rows, passes)` is one of them as rows for `console.table`.
|
|
329
357
|
|
|
330
358
|
`passStats` sizes each pass: draws, distinct objects (for `shadow`, the casters), triangles, and
|
|
331
359
|
the targets drawn to in device pixels (`screen 2880×1800` for the canvas). A pass is `fullscreen`
|
|
@@ -336,14 +364,17 @@ pass. `fullscreenPasses` and `fullscreenPixels` total them: 8 fullscreen passes
|
|
|
336
364
|
### `watchBlackFrames(renderer)`
|
|
337
365
|
|
|
338
366
|
After each render to the screen, reads a 3 × 3 grid of pixels and counts frames where three or more
|
|
339
|
-
are pure black
|
|
340
|
-
stalls the GPU, so watch, read `
|
|
367
|
+
are pure black (options `grid` and `threshold` set the two numbers). Use a visible browser:
|
|
368
|
+
headless ones haven't reproduced black frames. Each read stalls the GPU, so watch, read `frames`
|
|
369
|
+
and `blackFrames` (which frames, 1-based), `stop()`; `reset()` starts the count over.
|
|
341
370
|
|
|
342
371
|
### `auditScene(root, options?)`
|
|
343
372
|
|
|
344
|
-
`{ triangles, zFighting, badGeometry, meshes }` in one call
|
|
345
|
-
|
|
346
|
-
|
|
373
|
+
`{ triangles, draws, zFighting, badGeometry, emptyMeshes, meshes }` in one call, `meshes` the
|
|
374
|
+
costliest `top` (default 10). `tag` narrows it to one tagged object. Rows carry the live mesh as a
|
|
375
|
+
non-enumerable property (`row.mesh` on a mesh row, `row.meshes` and `row.pairs` on a z-fighting
|
|
376
|
+
row, `row.object` on a bad or empty one), so they print and serialise without dragging the scene
|
|
377
|
+
along.
|
|
347
378
|
|
|
348
379
|
## Baked groups
|
|
349
380
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as findBadGeometry, g as onlyTag, i as countTriangles, l as findEmptyMeshes, n as findZFighting, o as listMeshes, r as countDraws, u as asObject3D } from "./budgets-BtXN-bkX.js";
|
|
2
2
|
// src/audit-scene.ts
|
|
3
3
|
/** The skip every check runs with: the caller's, narrowed to `tag` when given. */
|
|
4
4
|
const scopeOf = (root, options) => options.tag === void 0 ? options.skip : onlyTag(asObject3D(root), options.tag, options.tagKey ?? "studioObject", options.skip);
|
|
@@ -66,4 +66,4 @@ const summarizeScene = (root, options = {}) => {
|
|
|
66
66
|
};
|
|
67
67
|
export { summarizeScene as n, auditScene as t };
|
|
68
68
|
|
|
69
|
-
//# sourceMappingURL=audit-scene-
|
|
69
|
+
//# sourceMappingURL=audit-scene-BjB8raDp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-scene-
|
|
1
|
+
{"version":3,"file":"audit-scene-BjB8raDp.js","names":[],"sources":["../src/audit-scene.ts"],"sourcesContent":["import {\n type BadGeometryRow,\n type EmptyMeshRow,\n findBadGeometry,\n findEmptyMeshes,\n} from \"./bad-geometry.ts\";\nimport { type AnyObject3D, asObject3D, onlyTag, type Skip } from \"./scene.ts\";\nimport { countDraws, countTriangles, listMeshes, type MeshRow } from \"./triangles.ts\";\nimport { findZFighting, type ZFightingOptions, type ZFightingRow } from \"./z-fighting.ts\";\n\nexport type AuditSceneOptions = Pick<ZFightingOptions, \"bakes\" | \"gap\" | \"self\" | \"tagged\"> & {\n skip?: Skip | undefined;\n tagKey?: string;\n /** Only the object(s) tagged this (`userData[tagKey]`): one studio object out of a scene. */\n tag?: string | undefined;\n /** How many of the costliest meshes to list. */\n top?: number;\n};\n\nexport type AuditReport = {\n triangles: number;\n /** Draw calls one main pass makes, before culling and shadows: see `countDraws`. */\n draws: number;\n zFighting: ZFightingRow[];\n badGeometry: BadGeometryRow[];\n emptyMeshes: EmptyMeshRow[];\n /** The costliest meshes, most triangles first. */\n meshes: MeshRow[];\n};\n\n/** The skip every check runs with: the caller's, narrowed to `tag` when given. */\nconst scopeOf = (\n root: AnyObject3D,\n options: { skip?: Skip | undefined; tag?: string | undefined; tagKey?: string },\n) =>\n options.tag === undefined\n ? options.skip\n : onlyTag(asObject3D(root), options.tag, options.tagKey ?? \"studioObject\", options.skip);\n\n/**\n * Every check at once, JSON-safe: what a test asserts on or a headless browser prints. A clean\n * scene has `zFighting`, `badGeometry` and `emptyMeshes` all `[]`.\n */\nexport const auditScene = (root: AnyObject3D, options: AuditSceneOptions = {}): AuditReport => {\n const { tagKey, bakes } = options;\n const skip = scopeOf(root, options);\n const keyed = { skip, bakes, ...(tagKey === undefined ? {} : { tagKey }) };\n\n return {\n triangles: countTriangles(root, { skip }),\n draws: countDraws(root, { skip }),\n zFighting: findZFighting(root, {\n ...options,\n skip,\n ...(options.tag === undefined ? {} : { tagged: true }),\n }),\n badGeometry: findBadGeometry(root, keyed),\n emptyMeshes: findEmptyMeshes(root, keyed),\n meshes: listMeshes(root, { skip }).slice(0, options.top ?? 10),\n };\n};\n\nexport type AuditSummaryOptions = Pick<\n AuditSceneOptions,\n \"bakes\" | \"gap\" | \"skip\" | \"tag\" | \"tagKey\"\n> & {\n /** Run the z-fighting check, the slow one. Default true; false gives `null` counts. */\n zFighting?: boolean;\n};\n\nexport type AuditSummary = {\n triangles: number;\n draws: number;\n meshes: number;\n /** Pairs of meshes that z-fight, identical pairs counted each. */\n zFighting: number | null;\n /** Meshes that z-fight with themselves: merged geometry whose parts overlap. */\n selfZFighting: number | null;\n badGeometry: number;\n emptyMeshes: number;\n};\n\n/**\n * Every check as one count each: the single call to run after every edit. `0` everywhere (and\n * `triangles`, `draws` where you expect them) is a pass. Use `auditScene` to see what failed.\n */\nexport const summarizeScene = (\n root: AnyObject3D,\n options: AuditSummaryOptions = {},\n): AuditSummary => {\n const { tagKey, bakes } = options;\n const skip = scopeOf(root, options);\n const keyed = { skip, bakes, ...(tagKey === undefined ? {} : { tagKey }) };\n let zFighting: number | null = null;\n let selfZFighting: number | null = null;\n\n if (options.zFighting !== false) {\n zFighting = 0;\n selfZFighting = 0;\n\n for (const row of findZFighting(root, {\n ...(options.gap === undefined ? {} : { gap: options.gap }),\n ...(tagKey === undefined ? {} : { tagKey }),\n ...(options.tag === undefined ? {} : { tagged: true }),\n skip,\n bakes,\n self: true,\n })) {\n if (row.meshes[0] === row.meshes[1]) {\n selfZFighting += row.count;\n } else {\n zFighting += row.count;\n }\n }\n }\n\n return {\n triangles: countTriangles(root, { skip }),\n draws: countDraws(root, { skip }),\n meshes: listMeshes(root, { skip }).length,\n zFighting,\n selfZFighting,\n badGeometry: findBadGeometry(root, keyed).length,\n emptyMeshes: findEmptyMeshes(root, keyed).length,\n };\n};\n"],"mappings":";;;AA+BA,MAAM,WACJ,MACA,YAEA,QAAQ,QAAQ,KAAA,IACZ,QAAQ,OACR,QAAQ,WAAW,IAAI,GAAG,QAAQ,KAAK,QAAQ,UAAU,gBAAgB,QAAQ,IAAI;;;;;AAM3F,MAAa,cAAc,MAAmB,UAA6B,CAAC,MAAmB;CAC7F,MAAM,EAAE,QAAQ,UAAU;CAC1B,MAAM,OAAO,QAAQ,MAAM,OAAO;CAClC,MAAM,QAAQ;EAAE;EAAM;EAAO,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;CAAG;CAEzE,OAAO;EACL,WAAW,eAAe,MAAM,EAAE,KAAK,CAAC;EACxC,OAAO,WAAW,MAAM,EAAE,KAAK,CAAC;EAChC,WAAW,cAAc,MAAM;GAC7B,GAAG;GACH;GACA,GAAI,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,KAAK;EACtD,CAAC;EACD,aAAa,gBAAgB,MAAM,KAAK;EACxC,aAAa,gBAAgB,MAAM,KAAK;EACxC,QAAQ,WAAW,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,QAAQ,OAAO,EAAE;CAC/D;AACF;;;;;AA0BA,MAAa,kBACX,MACA,UAA+B,CAAC,MACf;CACjB,MAAM,EAAE,QAAQ,UAAU;CAC1B,MAAM,OAAO,QAAQ,MAAM,OAAO;CAClC,MAAM,QAAQ;EAAE;EAAM;EAAO,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;CAAG;CACzE,IAAI,YAA2B;CAC/B,IAAI,gBAA+B;CAEnC,IAAI,QAAQ,cAAc,OAAO;EAC/B,YAAY;EACZ,gBAAgB;EAEhB,KAAK,MAAM,OAAO,cAAc,MAAM;GACpC,GAAI,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,QAAQ,IAAI;GACxD,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;GACzC,GAAI,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,KAAK;GACpD;GACA;GACA,MAAM;EACR,CAAC,GACC,IAAI,IAAI,OAAO,OAAO,IAAI,OAAO,IAC/B,iBAAiB,IAAI;OAErB,aAAa,IAAI;CAGvB;CAEA,OAAO;EACL,WAAW,eAAe,MAAM,EAAE,KAAK,CAAC;EACxC,OAAO,WAAW,MAAM,EAAE,KAAK,CAAC;EAChC,QAAQ,WAAW,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;EACnC;EACA;EACA,aAAa,gBAAgB,MAAM,KAAK,CAAC,CAAC;EAC1C,aAAa,gBAAgB,MAAM,KAAK,CAAC,CAAC;CAC5C;AACF"}
|
|
@@ -676,57 +676,6 @@ const findZFighting = (scene, options = {}) => {
|
|
|
676
676
|
return [...rows.values()];
|
|
677
677
|
};
|
|
678
678
|
// src/budgets.ts
|
|
679
|
-
const FRAME_KEYS = [
|
|
680
|
-
"fps",
|
|
681
|
-
"calls",
|
|
682
|
-
"triangles",
|
|
683
|
-
"textureMB"
|
|
684
|
-
];
|
|
685
|
-
const PART_KEYS = [
|
|
686
|
-
"triangles",
|
|
687
|
-
"draws",
|
|
688
|
-
"meshes"
|
|
689
|
-
];
|
|
690
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
691
|
-
/** A copy of `value` with only `keys`, each a number ≥ 0, frozen. Throws naming `where`. */
|
|
692
|
-
const limits = (value, keys, where) => {
|
|
693
|
-
if (!isRecord(value)) throw new TypeError(`three-audit: budgets ${where} must be an object`);
|
|
694
|
-
const out = {};
|
|
695
|
-
for (const [key, limit] of Object.entries(value)) {
|
|
696
|
-
if (!keys.includes(key)) throw new TypeError(`three-audit: budgets ${where} has "${key}"; expected ${keys.map((name) => `"${name}"`).join(", ")}`);
|
|
697
|
-
if (limit === void 0) continue;
|
|
698
|
-
if (typeof limit !== "number" || !(limit >= 0) || !Number.isFinite(limit)) throw new TypeError(`three-audit: budgets ${where}.${key} must be a number ≥ 0`);
|
|
699
|
-
out[key] = limit;
|
|
700
|
-
}
|
|
701
|
-
return Object.freeze(out);
|
|
702
|
-
};
|
|
703
|
-
/**
|
|
704
|
-
* Checks budgets read from anywhere (a JSON file, a module) and returns them frozen. Throws a
|
|
705
|
-
* `TypeError` naming the first bad key.
|
|
706
|
-
*/
|
|
707
|
-
const parseBudgets = (value) => {
|
|
708
|
-
if (!isRecord(value)) throw new TypeError("three-audit: budgets must be an object with `frame` and `parts`");
|
|
709
|
-
const parts = {};
|
|
710
|
-
if (value.parts !== void 0) {
|
|
711
|
-
if (!isRecord(value.parts)) throw new TypeError("three-audit: budgets parts must be an object of part budgets");
|
|
712
|
-
for (const [name, part] of Object.entries(value.parts)) parts[name] = limits(part, PART_KEYS, `parts.${name}`);
|
|
713
|
-
}
|
|
714
|
-
return Object.freeze({
|
|
715
|
-
frame: limits(value.frame ?? {}, FRAME_KEYS, "frame"),
|
|
716
|
-
parts: Object.freeze(parts)
|
|
717
|
-
});
|
|
718
|
-
};
|
|
719
|
-
/**
|
|
720
|
-
* The app's budgets, typed and frozen: one file every reader imports.
|
|
721
|
-
*
|
|
722
|
-
* export default defineBudgets({
|
|
723
|
-
* frame: { fps: 60, calls: 150, triangles: 250_000 },
|
|
724
|
-
* parts: { kart: { triangles: 7_500, draws: 12 } },
|
|
725
|
-
* });
|
|
726
|
-
*
|
|
727
|
-
* `budgets.parts.kart` is typed, and a part not listed is a type error.
|
|
728
|
-
*/
|
|
729
|
-
const defineBudgets = (budgets) => parseBudgets(budgets);
|
|
730
679
|
/**
|
|
731
680
|
* An object measured against a part budget (or the frame's `triangles` and `calls`), one row per
|
|
732
681
|
* limit the budget sets. Nothing over is a pass.
|
|
@@ -751,6 +700,6 @@ const checkBudget = (root, budget, options = {}) => {
|
|
|
751
700
|
add("meshes", part.meshes, () => listMeshes(root, options).length);
|
|
752
701
|
return rows;
|
|
753
702
|
};
|
|
754
|
-
export {
|
|
703
|
+
export { pathOf as _, geometryCensus as a, findBadGeometry as c, batchedTriangles as d, isBatched as f, onlyTag as g, meshLabel as h, countTriangles as i, findEmptyMeshes as l, isMesh as m, findZFighting as n, listMeshes as o, isInstanced as p, countDraws as r, triangles as s, checkBudget as t, asObject3D as u, triangleCount as v, walk as y };
|
|
755
704
|
|
|
756
|
-
//# sourceMappingURL=budgets-
|
|
705
|
+
//# sourceMappingURL=budgets-BtXN-bkX.js.map
|