@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/README.md +72 -7
- package/dist/{audit-scene-BYsJcco9.js → audit-scene-Da-8SA1x.js} +180 -26
- package/dist/audit-scene-Da-8SA1x.js.map +1 -0
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +141 -9
- package/dist/index.js +381 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/audit-scene-BYsJcco9.js.map +0 -1
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
|
|
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
|
-
/**
|
|
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.
|
|
69
|
-
*
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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
|