@zkmake/three-audit 0.2.2 → 0.3.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/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli/main.ts","../src/cli.ts"],"sourcesContent":["/**\n * `three-audit model.glb [more.gltf …]`: load each model in Node and check it for z-fighting and\n * NaN geometry. Exits 1 when a check fails, so it can gate CI; `--json` for machines.\n */\n/* oxlint-disable no-console -- a CLI's output is the console */\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { type AuditReport, auditScene } from \"../audit-scene.ts\";\nimport { loadModel } from \"../node/load.ts\";\n\nconst HELP = `Usage: three-audit <model.glb|model.gltf>… [options]\n\nChecks glTF models for z-fighting (faces in one plane, facing the same way, overlapping) and\ngeometry that renders NaN (non-finite positions or normals, zero-length normals). Exits 1 when\nany model fails a check, 2 when a model can't be read.\n\nOptions:\n --gap <m> How close two planes count as one, in metres (default 0.004)\n --self Also check for z-fighting within each mesh (merged geometry)\n --skip <regex> Leave out objects whose name matches, and everything under them (decals)\n --top <n> List the n costliest meshes (default 5, 0 for none)\n --json Print the reports as JSON\n --no-fail Exit 0 even when a check fails\n -h, --help Show this help\n -v, --version Show the version\n\nDraco and Meshopt models need draco3dgltf / meshoptimizer installed alongside.`;\n\n/** This package's version: its package.json is the nearest one up from here (`src/cli` or `dist`). */\nconst version = () => {\n for (let up = \"../\"; up.length <= 12; up += \"../\") {\n try {\n const pkg = JSON.parse(\n readFileSync(new URL(`${up}package.json`, import.meta.url), \"utf8\"),\n ) as {\n name?: string;\n version: string;\n };\n\n if (pkg.name === \"@zkmake/three-audit\") {\n return pkg.version;\n }\n } catch {\n // Not here; one more up.\n }\n }\n\n return \"unknown\";\n};\n\nconst count = (value: number) => value.toLocaleString(\"en-US\");\n\nconst printReport = (file: string, report: AuditReport, top: number) => {\n const lines = [`${basename(file)} ${count(report.triangles)} triangles`];\n\n if (report.zFighting.length === 0) {\n lines.push(\" ✓ no z-fighting\");\n } else {\n lines.push(\n ` ✗ ${report.zFighting.length} z-fighting pair${report.zFighting.length === 1 ? \"\" : \"s\"}`,\n );\n\n for (const row of report.zFighting) {\n const more = row.planes.length > 1 ? ` (+${row.planes.length - 1} more)` : \"\";\n const copies = row.count > 1 ? `, ×${row.count} identical pairs` : \"\";\n\n lines.push(\n ` ${row.a}`,\n ` ↔ ${row.b}`,\n ` ${row.triangles} triangles in ${row.planes[0]}${more}; overlap ${row.overlap.area} m² at (${row.overlap.centre.join(\", \")})${copies}`,\n );\n }\n }\n\n if (report.badGeometry.length === 0) {\n lines.push(\" ✓ no NaN geometry\");\n } else {\n lines.push(\n ` ✗ ${report.badGeometry.length} mesh${report.badGeometry.length === 1 ? \"\" : \"es\"} with NaN geometry`,\n );\n\n for (const row of report.badGeometry) {\n lines.push(\n ` ${row.mesh}${row.under ? ` (${row.under})` : \"\"}: ${row.nonFinite} non-finite, ${row.zeroNormals} zero normals`,\n );\n }\n }\n\n if (top > 0 && report.meshes.length > 0) {\n lines.push(\" most triangles:\");\n\n for (const row of report.meshes) {\n lines.push(\n ` ${count(row.total).padStart(9)} ${row.name}${row.instances > 1 ? ` ×${row.instances}` : \"\"}`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n};\n\nexport const main = async (argv: string[]): Promise<number> => {\n let parsed;\n\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n gap: { type: \"string\" },\n self: { type: \"boolean\", default: false },\n skip: { type: \"string\" },\n top: { type: \"string\" },\n json: { type: \"boolean\", default: false },\n \"no-fail\": { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n } catch (error) {\n console.error(`three-audit: ${(error as Error).message}\\n\\n${HELP}`);\n\n return 2;\n }\n\n const { values, positionals } = parsed;\n\n if (values.help) {\n console.log(HELP);\n\n return 0;\n }\n\n if (values.version) {\n console.log(version());\n\n return 0;\n }\n\n if (positionals.length === 0) {\n console.error(HELP);\n\n return 2;\n }\n\n const gap = values.gap === undefined ? undefined : Number(values.gap);\n const top = values.top === undefined ? 5 : Number(values.top);\n\n if ((gap !== undefined && !(gap > 0)) || !(top >= 0)) {\n console.error(\"three-audit: --gap must be a positive number and --top zero or more\");\n\n return 2;\n }\n\n const skipPattern = values.skip === undefined ? undefined : new RegExp(values.skip);\n const results: { file: string; report?: AuditReport; error?: string }[] = [];\n\n for (const file of positionals) {\n try {\n const scene = await loadModel(file);\n const report = auditScene(scene, {\n top,\n ...(gap === undefined ? {} : { gap }),\n ...(values.self ? { self: true } : {}),\n ...(skipPattern ? { skip: (object) => skipPattern.test(object.name) } : {}),\n });\n\n results.push({ file, report });\n } catch (error) {\n results.push({ file, error: (error as Error).message });\n }\n }\n\n if (values.json) {\n console.log(JSON.stringify(results, null, 2));\n } else {\n console.log(\n results\n .map(({ file, report, error }) =>\n report\n ? printReport(file, report, top)\n : `${basename(file)}\\n ✗ couldn't read: ${error}`,\n )\n .join(\"\\n\\n\"),\n );\n }\n\n if (results.some((result) => result.error !== undefined)) {\n return 2;\n }\n\n const failed = results.some(\n ({ report }) => report!.zFighting.length > 0 || report!.badGeometry.length > 0,\n );\n\n return failed && !values[\"no-fail\"] ? 1 : 0;\n};\n","#!/usr/bin/env node\n/** The `three-audit` bin. The work is in `cli/main.ts`, which the tests import. */\nimport { main } from \"./cli/main.ts\";\n\nprocess.exitCode = await main(process.argv.slice(2));\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli/main.ts","../src/cli.ts"],"sourcesContent":["/**\n * `three-audit model.glb [more.gltf …]`: load each model in Node and check it for z-fighting and\n * NaN geometry. Exits 1 when a check fails, so it can gate CI; `--json` for machines.\n */\n/* oxlint-disable no-console -- a CLI's output is the console */\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { type AuditReport, auditScene } from \"../audit-scene.ts\";\nimport { loadModel } from \"../node/load.ts\";\n\nconst HELP = `Usage: three-audit <model.glb|model.gltf>… [options]\n\nChecks glTF models for z-fighting (faces in one plane, facing the same way, overlapping) and\ngeometry that renders NaN (non-finite positions or normals, zero-length normals). Exits 1 when\nany model fails a check, 2 when a model can't be read.\n\nOptions:\n --gap <m> How close two planes count as one, in metres (default 0.004)\n --self Also check for z-fighting within each mesh (merged geometry)\n --skip <regex> Leave out objects whose name matches, and everything under them (decals)\n --top <n> List the n costliest meshes (default 5, 0 for none)\n --json Print the reports as JSON\n --no-fail Exit 0 even when a check fails\n -h, --help Show this help\n -v, --version Show the version\n\nDraco and Meshopt models need draco3dgltf / meshoptimizer installed alongside.`;\n\n/** This package's version: its package.json is the nearest one up from here (`src/cli` or `dist`). */\nconst version = () => {\n for (let up = \"../\"; up.length <= 12; up += \"../\") {\n try {\n const pkg = JSON.parse(\n readFileSync(new URL(`${up}package.json`, import.meta.url), \"utf8\"),\n ) as {\n name?: string;\n version: string;\n };\n\n if (pkg.name === \"@zkmake/three-audit\") {\n return pkg.version;\n }\n } catch {\n // Not here; one more up.\n }\n }\n\n return \"unknown\";\n};\n\nconst count = (value: number) => value.toLocaleString(\"en-US\");\n\nconst printReport = (file: string, report: AuditReport, top: number) => {\n const lines = [\n `${basename(file)} ${count(report.triangles)} triangles, ${count(report.draws)} draws`,\n ];\n\n if (report.zFighting.length === 0) {\n lines.push(\" ✓ no z-fighting\");\n } else {\n lines.push(\n ` ✗ ${report.zFighting.length} z-fighting pair${report.zFighting.length === 1 ? \"\" : \"s\"}`,\n );\n\n for (const row of report.zFighting) {\n const more = row.planes.length > 1 ? ` (+${row.planes.length - 1} more)` : \"\";\n const copies = row.count > 1 ? `, ×${row.count} identical pairs` : \"\";\n\n lines.push(\n ` ${row.a}`,\n ` ↔ ${row.b}`,\n ` ${row.triangles} triangles in ${row.planes[0]}${more}; overlap ${row.overlap.area} m² at (${row.overlap.centre.join(\", \")})${copies}`,\n );\n }\n }\n\n if (report.badGeometry.length === 0) {\n lines.push(\" ✓ no NaN geometry\");\n } else {\n lines.push(\n ` ✗ ${report.badGeometry.length} mesh${report.badGeometry.length === 1 ? \"\" : \"es\"} with NaN geometry`,\n );\n\n for (const row of report.badGeometry) {\n lines.push(\n ` ${row.mesh}${row.under ? ` (${row.under})` : \"\"}: ${row.nonFinite} non-finite, ${row.zeroNormals} zero normals`,\n );\n }\n }\n\n if (report.emptyMeshes.length > 0) {\n lines.push(\n ` ! ${report.emptyMeshes.length} empty mesh${report.emptyMeshes.length === 1 ? \"\" : \"es\"} (no vertices)`,\n );\n\n for (const row of report.emptyMeshes) {\n lines.push(` ${row.mesh}`);\n }\n }\n\n if (top > 0 && report.meshes.length > 0) {\n lines.push(\" most triangles:\");\n\n for (const row of report.meshes) {\n lines.push(\n ` ${count(row.total).padStart(9)} ${row.name}${row.instances > 1 ? ` ×${row.instances}` : \"\"}`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n};\n\nexport const main = async (argv: string[]): Promise<number> => {\n let parsed;\n\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n gap: { type: \"string\" },\n self: { type: \"boolean\", default: false },\n skip: { type: \"string\" },\n top: { type: \"string\" },\n json: { type: \"boolean\", default: false },\n \"no-fail\": { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n } catch (error) {\n console.error(`three-audit: ${(error as Error).message}\\n\\n${HELP}`);\n\n return 2;\n }\n\n const { values, positionals } = parsed;\n\n if (values.help) {\n console.log(HELP);\n\n return 0;\n }\n\n if (values.version) {\n console.log(version());\n\n return 0;\n }\n\n if (positionals.length === 0) {\n console.error(HELP);\n\n return 2;\n }\n\n const gap = values.gap === undefined ? undefined : Number(values.gap);\n const top = values.top === undefined ? 5 : Number(values.top);\n\n if ((gap !== undefined && !(gap > 0)) || !(top >= 0)) {\n console.error(\"three-audit: --gap must be a positive number and --top zero or more\");\n\n return 2;\n }\n\n const skipPattern = values.skip === undefined ? undefined : new RegExp(values.skip);\n const results: { file: string; report?: AuditReport; error?: string }[] = [];\n\n for (const file of positionals) {\n try {\n const scene = await loadModel(file);\n const report = auditScene(scene, {\n top,\n ...(gap === undefined ? {} : { gap }),\n ...(values.self ? { self: true } : {}),\n ...(skipPattern ? { skip: (object) => skipPattern.test(object.name) } : {}),\n });\n\n results.push({ file, report });\n } catch (error) {\n results.push({ file, error: (error as Error).message });\n }\n }\n\n if (values.json) {\n console.log(JSON.stringify(results, null, 2));\n } else {\n console.log(\n results\n .map(({ file, report, error }) =>\n report\n ? printReport(file, report, top)\n : `${basename(file)}\\n ✗ couldn't read: ${error}`,\n )\n .join(\"\\n\\n\"),\n );\n }\n\n if (results.some((result) => result.error !== undefined)) {\n return 2;\n }\n\n const failed = results.some(\n ({ report }) => report!.zFighting.length > 0 || report!.badGeometry.length > 0,\n );\n\n return failed && !values[\"no-fail\"] ? 1 : 0;\n};\n","#!/usr/bin/env node\n/** The `three-audit` bin. The work is in `cli/main.ts`, which the tests import. */\nimport { main } from \"./cli/main.ts\";\n\nprocess.exitCode = await main(process.argv.slice(2));\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BufferGeometry, Mesh, Object3D } from "three";
1
+ import { BatchedMesh, BufferGeometry, Mesh, Object3D } from "three";
2
2
  //#region src/scene.d.ts
3
3
  /**
4
4
  * Any three.js object, from whichever copy of three's types the app has. The checks take this, so
@@ -10,6 +10,12 @@ type AnyObject3D = {
10
10
  };
11
11
  /** Leave an object and everything under it out of a check. */
12
12
  type Skip = (object: Object3D) => boolean;
13
+ /**
14
+ * What a check sees of a bake (`@zkmake/three-batch`'s `bake` and `<Baked>`), which hides its
15
+ * parts and draws one merged mesh per material instead. `sources`: the parts, as if shown, and not
16
+ * the merge, so rows name real parts. `merged`: what the renderer draws.
17
+ */
18
+ type Bakes = "sources" | "merged";
13
19
  /** Triangles one draw of this geometry makes: indexed or not, within its draw range. */
14
20
  export declare const triangleCount: (geometry: BufferGeometry) => number;
15
21
  //#endregion
@@ -18,6 +24,8 @@ type BadGeometryOptions = {
18
24
  skip?: Skip | undefined;
19
25
  /** `userData` key naming the part a mesh belongs to, for the `under` column. */
20
26
  tagKey?: string;
27
+ /** A bake's parts (default), or its merged meshes: see `Bakes`. Not both, or each counts twice. */
28
+ bakes?: Bakes | undefined;
21
29
  };
22
30
  type BadGeometryRow = {
23
31
  mesh: string;
@@ -32,6 +40,18 @@ type BadGeometryRow = {
32
40
  };
33
41
  /** Meshes with geometry that renders NaN, hidden ones included. `[]` is a pass. */
34
42
  export declare const findBadGeometry: (root: AnyObject3D, options?: BadGeometryOptions) => BadGeometryRow[];
43
+ type EmptyMeshRow = {
44
+ /** `path/to/mesh [material]`. */
45
+ mesh: string;
46
+ /** Not enumerable: kept out of JSON and `console.table`. */
47
+ readonly object: Mesh;
48
+ };
49
+ /**
50
+ * Meshes with no vertices, hidden ones included: what a merge of nothing leaves behind. They draw
51
+ * nothing, but each one is still a draw call and a leak to dispose. A draw range of zero doesn't
52
+ * count: that's usually a pool waiting to fill.
53
+ */
54
+ export declare const findEmptyMeshes: (root: AnyObject3D, options?: BadGeometryOptions) => EmptyMeshRow[];
35
55
  //#endregion
36
56
  //#region src/triangles.d.ts
37
57
  type TriangleOptions = {
@@ -41,7 +61,7 @@ type MeshRow = {
41
61
  /** `path/to/mesh`: its named ancestors, then its name (or its geometry's name or type). */
42
62
  name: string;
43
63
  material: string;
44
- /** Triangles in one copy (a batch's average). */
64
+ /** Triangles in one copy. A batch's is an average: `geometryCensus` breaks it down. */
45
65
  triangles: number;
46
66
  instances: number;
47
67
  total: number;
@@ -49,12 +69,28 @@ type MeshRow = {
49
69
  readonly mesh: Mesh;
50
70
  };
51
71
  type GeometryRow = {
52
- /** `geometry.name`, or its type and the start of its uuid. */
72
+ /**
73
+ * `geometry.name`, or its type and the start of its uuid. For a geometry in a batch, the batch's
74
+ * `label` for it, or `batch#id`.
75
+ */
53
76
  geometry: string;
54
77
  /** Copies drawn: every mesh using it, instances counted. */
55
78
  uses: number;
56
79
  triangles: number;
57
80
  total: number;
81
+ /** Its part of the census's total, 0–1. */
82
+ share: number;
83
+ /** With `budget`: whether `share` is over it. */
84
+ overBudget?: boolean;
85
+ };
86
+ type CensusOptions = TriangleOptions & {
87
+ /**
88
+ * Names a geometry inside a batch, e.g. from the app's own table of what it added. Default
89
+ * `batch#id`, the batch named by its path.
90
+ */
91
+ label?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;
92
+ /** Flag rows whose share of the total is over this, 0–1: `0.2` for anything over 20%. */
93
+ budget?: number | undefined;
58
94
  };
59
95
  /**
60
96
  * Triangles one pass draws: visible meshes, instances counted, no frustum culling. A shadow pass
@@ -65,10 +101,16 @@ export declare const countTriangles: (root: AnyObject3D, options?: TriangleOptio
65
101
  export declare const listMeshes: (scene: AnyObject3D, options?: TriangleOptions) => MeshRow[];
66
102
  /**
67
103
  * Geometries by triangles drawn, hidden meshes included, most first. Name geometries
68
- * (`geometry.name`) to make this readable. Batches are left out: their shared buffer is not one
69
- * geometry; `listMeshes` counts them.
104
+ * (`geometry.name`) to make this readable. A batch counts each geometry in it separately, by its
105
+ * visible instances: the row a budget is really spent on.
106
+ */
107
+ export declare const geometryCensus: (root: AnyObject3D, options?: CensusOptions) => GeometryRow[];
108
+ /**
109
+ * Draw calls one main pass makes: one per visible mesh, line or point cloud, or one per material
110
+ * group for a mesh with several materials. Before frustum culling; each shadow-casting light adds
111
+ * one per caster. `recordDrawLedger` counts the real thing.
70
112
  */
71
- export declare const geometryCensus: (root: AnyObject3D, options?: TriangleOptions) => GeometryRow[];
113
+ export declare const countDraws: (root: AnyObject3D, options?: TriangleOptions) => number;
72
114
  //#endregion
73
115
  //#region src/z-fighting.d.ts
74
116
  type ZFightingOptions = {
@@ -86,6 +128,11 @@ type ZFightingOptions = {
86
128
  tagged?: boolean;
87
129
  tagKey?: string;
88
130
  skip?: Skip | undefined;
131
+ /**
132
+ * A bake's parts (default), or its merged meshes: see `Bakes`. The parts name what to fix; the
133
+ * merge is the same faces under one name.
134
+ */
135
+ bakes?: Bakes | undefined;
89
136
  };
90
137
  type ZFightingRow = {
91
138
  /** `path/to/mesh [material] @ (x, y, z)`: named ancestors, and the centre in world space. */
@@ -114,24 +161,49 @@ type ZFightingRow = {
114
161
  export declare const findZFighting: (scene: AnyObject3D, options?: ZFightingOptions) => ZFightingRow[];
115
162
  //#endregion
116
163
  //#region src/audit-scene.d.ts
117
- type AuditSceneOptions = Pick<ZFightingOptions, "gap" | "self" | "tagged"> & {
164
+ type AuditSceneOptions = Pick<ZFightingOptions, "bakes" | "gap" | "self" | "tagged"> & {
118
165
  skip?: Skip | undefined;
119
166
  tagKey?: string;
167
+ /** Only the object(s) tagged this (`userData[tagKey]`): one studio object out of a scene. */
168
+ tag?: string | undefined;
120
169
  /** How many of the costliest meshes to list. */
121
170
  top?: number;
122
171
  };
123
172
  type AuditReport = {
124
173
  triangles: number;
174
+ /** Draw calls one main pass makes, before culling and shadows: see `countDraws`. */
175
+ draws: number;
125
176
  zFighting: ZFightingRow[];
126
177
  badGeometry: BadGeometryRow[];
178
+ emptyMeshes: EmptyMeshRow[];
127
179
  /** The costliest meshes, most triangles first. */
128
180
  meshes: MeshRow[];
129
181
  };
130
182
  /**
131
183
  * Every check at once, JSON-safe: what a test asserts on or a headless browser prints. A clean
132
- * scene has `zFighting` and `badGeometry` both `[]`.
184
+ * scene has `zFighting`, `badGeometry` and `emptyMeshes` all `[]`.
133
185
  */
134
186
  export declare const auditScene: (root: AnyObject3D, options?: AuditSceneOptions) => AuditReport;
187
+ type AuditSummaryOptions = Pick<AuditSceneOptions, "bakes" | "gap" | "skip" | "tag" | "tagKey"> & {
188
+ /** Run the z-fighting check, the slow one. Default true; false gives `null` counts. */
189
+ zFighting?: boolean;
190
+ };
191
+ type AuditSummary = {
192
+ triangles: number;
193
+ draws: number;
194
+ meshes: number;
195
+ /** Pairs of meshes that z-fight, identical pairs counted each. */
196
+ zFighting: number | null;
197
+ /** Meshes that z-fight with themselves: merged geometry whose parts overlap. */
198
+ selfZFighting: number | null;
199
+ badGeometry: number;
200
+ emptyMeshes: number;
201
+ };
202
+ /**
203
+ * Every check as one count each: the single call to run after every edit. `0` everywhere (and
204
+ * `triangles`, `draws` where you expect them) is a pass. Use `auditScene` to see what failed.
205
+ */
206
+ export declare const summarizeScene: (root: AnyObject3D, options?: AuditSummaryOptions) => AuditSummary;
135
207
  //#endregion
136
208
  //#region src/black-frames.d.ts
137
209
  /** The `WebGLRenderer` methods the probe uses, typed loosely to fit any copy of three's types. */
@@ -170,6 +242,10 @@ export declare const watchBlackFrames: (renderer: ProbeRenderer, options?: Black
170
242
  type LedgerRenderer = {
171
243
  render(scene: AnyObject3D, camera: object): void;
172
244
  renderBufferDirect(camera: object, scene: object | null, geometry: object, material: object, object: AnyObject3D, group: unknown): void;
245
+ /** For pass sizes: the target being drawn to, `null` for the screen. */
246
+ getRenderTarget?(): unknown;
247
+ /** For pass sizes: the screen's drawing buffer, device pixels. */
248
+ getContext?(): unknown;
173
249
  };
174
250
  type DrawLedgerOptions = {
175
251
  /**
@@ -193,6 +269,18 @@ type LedgerRow = {
193
269
  /** Draws per pass name. */
194
270
  passes: Record<string, number>;
195
271
  };
272
+ type PassRow = {
273
+ name: string;
274
+ draws: number;
275
+ /** Distinct objects drawn: for `shadow`, the casters. */
276
+ objects: number;
277
+ /** Triangles drawn, instances counted. */
278
+ triangles: number;
279
+ /** Sizes drawn to, in device pixels: `2880×1800`, or `screen 2880×1800`. */
280
+ targets: string[];
281
+ /** Every call was one small mesh through an orthographic camera: a post-processing pass. */
282
+ fullscreen: boolean;
283
+ };
196
284
  type DrawLedger = {
197
285
  /**
198
286
  * Pass names in the order they first drew: `shadow`, then each `render()` call as its scene's
@@ -204,6 +292,11 @@ type DrawLedger = {
204
292
  /** By object name, most draws first. */
205
293
  objects: LedgerRow[];
206
294
  draws: number;
295
+ /** Each pass's draws, triangles, casters and target sizes, in `passes` order. */
296
+ passStats: PassRow[];
297
+ /** `render()` calls that drew one fullscreen pass, and the pixels they shaded between them. */
298
+ fullscreenPasses: number;
299
+ fullscreenPixels: number;
207
300
  };
208
301
  type DrawLedgerRecording = {
209
302
  /** Restore the renderer and return what was drawn since `beginDrawLedger`. */
@@ -234,6 +327,10 @@ type AuditHelpersOptions = {
234
327
  renderer?: (LedgerRenderer & ProbeRenderer) | undefined;
235
328
  skip?: Skip | undefined;
236
329
  tagKey?: string;
330
+ /** What checks see of a bake: its parts (default) or its merged meshes. See `Bakes`. */
331
+ bakes?: Bakes | undefined;
332
+ /** Names geometries inside a batch in `census()`: see `geometryCensus`'s `label`. */
333
+ batchLabel?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;
237
334
  /**
238
335
  * Also put each helper on `globalThis` under its short name (`tris`, `report`…). Default true;
239
336
  * false leaves only `threeAudit`, for pages where short names collide with other tooling.
@@ -250,5 +347,40 @@ type AuditHelpersOptions = {
250
347
  */
251
348
  export declare const installAuditHelpers: (root: AnyObject3D, options?: AuditHelpersOptions) => () => void;
252
349
  //#endregion
253
- export type { AuditHelpersOptions, AuditReport, AuditSceneOptions, BadGeometryOptions, BadGeometryRow, BlackFrameOptions, BlackFrameWatch, DrawLedger, DrawLedgerOptions, DrawLedgerRecording, GeometryRow, LedgerRenderer, LedgerRow, MeshRow, ProbeRenderer, Skip, TriangleOptions, ZFightingOptions, ZFightingRow };
350
+ //#region src/measure.d.ts
351
+ type MeasureOptions = {
352
+ /** `userData` key a string target is looked up by. Default `studioObject`. */
353
+ tagKey?: string;
354
+ /** A bake's parts (default), or its merged meshes: see `Bakes`. The same space either way. */
355
+ bakes?: Bakes | undefined;
356
+ };
357
+ /** What to measure: an object, or a tag (`userData[tagKey]`), or failing that an object name. */
358
+ type Target = string | AnyObject3D;
359
+ type Bounds = {
360
+ min: [number, number, number];
361
+ max: [number, number, number];
362
+ size: [number, number, number];
363
+ centre: [number, number, number];
364
+ };
365
+ type Clearance = {
366
+ /**
367
+ * The smallest gap between the two, in world units. Measured between triangles' bounding boxes:
368
+ * exact for faces square to the axes, a slight underestimate for sloped ones. 0 when they touch
369
+ * or intersect.
370
+ */
371
+ gap: number;
372
+ /** The axis the gap is measured along, when it's straight along one; `null` when diagonal. */
373
+ axis: "x" | "y" | "z" | null;
374
+ /** The closest meshes, one from each side. */
375
+ between: [string, string];
376
+ };
377
+ /** A target's world envelope, from its visible meshes' vertices; `null` when it draws nothing. */
378
+ export declare const measureBounds: (root: AnyObject3D, target: Target, options?: MeasureOptions) => Bounds | null;
379
+ /**
380
+ * The gap between two targets, and which of their meshes come closest. Throws when a target
381
+ * matches nothing; `null` when either draws nothing. Meshes in both targets are left out of `a`.
382
+ */
383
+ export declare const measureClearance: (root: AnyObject3D, a: Target, b: Target, options?: MeasureOptions) => Clearance | null;
384
+ //#endregion
385
+ export type { AnyObject3D, AuditHelpersOptions, AuditReport, AuditSceneOptions, AuditSummary, AuditSummaryOptions, BadGeometryOptions, BadGeometryRow, Bakes, BlackFrameOptions, BlackFrameWatch, Bounds, CensusOptions, Clearance, DrawLedger, DrawLedgerOptions, DrawLedgerRecording, EmptyMeshRow, GeometryRow, LedgerRenderer, LedgerRow, MeasureOptions, MeshRow, PassRow, ProbeRenderer, RecordDrawLedgerOptions, Skip, Target, TriangleOptions, ZFightingOptions, ZFightingRow };
254
386
  //# sourceMappingURL=index.d.ts.map