@zkmake/three-audit 0.3.0 → 0.3.2

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 CHANGED
@@ -159,7 +159,7 @@ into one row with a `count`.
159
159
 
160
160
  Left out because they can't fight:
161
161
 
162
- - hidden objects
162
+ - hidden objects, except a bake's parts (see "Baked groups" below)
163
163
  - instanced and batched meshes, and `InstancedBufferGeometry`, which a shader positions. Check
164
164
  their source parts as plain meshes.
165
165
  - pairs where neither material writes depth, or one ignores depth entirely. A decal that doesn't
@@ -243,6 +243,18 @@ stalls the GPU, so watch, read `blackFrames`, `stop()`.
243
243
  non-enumerable property (`row.mesh`, `row.meshes`), so they print and serialise without dragging the
244
244
  scene along.
245
245
 
246
+ ## Baked groups
247
+
248
+ [`@zkmake/three-batch`](../three-batch)'s `bake` and `<Baked>` hide the parts and draw one merged
249
+ mesh per material. For z-fighting, NaN geometry, empty meshes, `bbox` and `clearance`,
250
+ three-audit checks the parts instead of the merge by default (`bakes: "sources"`). A row then
251
+ names the deck and the rail rather than `bridge:steel`, and a part is never compared with its own
252
+ merged copy. Parts are found by three-batch's `userData.bakeSource` flag. For bakes from before
253
+ that flag, they're the hidden objects inside anything holding a `userData.bakedResult` merge.
254
+ `bakes: "merged"` checks what's drawn instead.
255
+
256
+ Costs (`tris`, `meshes`, `census`, draws) always count what's drawn: the merge.
257
+
246
258
  ## Types
247
259
 
248
260
  The checks take any object with three's `isObject3D` flag, and the renderer as only the methods
@@ -1,12 +1,22 @@
1
1
  //#region src/scene.ts
2
2
  /** The object as three's own type, for the walk. */
3
3
  const asObject3D = (object) => object;
4
+ /**
5
+ * A part a bake hid in favour of its merge: flagged `userData.bakeSource` by three-batch, or (for
6
+ * bakes from before that flag) inside an object that holds a `userData.bakedResult` merge.
7
+ */
8
+ const isBakeSource = (object) => {
9
+ if (object.userData.bakeSource === true) return true;
10
+ for (let at = object.parent; at; at = at.parent) if (at.children.some((child) => child.userData.bakedResult === true)) return true;
11
+ return false;
12
+ };
4
13
  /** Depth-first, in child order. A skipped (or, with `visibleOnly`, hidden) object prunes its subtree. */
5
14
  const walk = (root, options, visit) => {
6
15
  const stack = [root];
16
+ const sources = options.bakes === "sources";
7
17
  while (stack.length > 0) {
8
18
  const object = stack.pop();
9
- if (options.visibleOnly === true && !object.visible || options.skip?.(object) === true) continue;
19
+ if (sources && object.userData.bakedResult === true || options.visibleOnly === true && !object.visible && !(sources && isBakeSource(object)) || options.skip?.(object) === true) continue;
10
20
  visit(object);
11
21
  for (let i = object.children.length - 1; i >= 0; i -= 1) stack.push(object.children[i]);
12
22
  }
@@ -173,7 +183,10 @@ const check = (geometry) => {
173
183
  const findBadGeometry = (root, options = {}) => {
174
184
  const tagKey = options.tagKey ?? "studioObject";
175
185
  const rows = [];
176
- walk(asObject3D(root), { skip: options.skip }, (object) => {
186
+ walk(asObject3D(root), {
187
+ skip: options.skip,
188
+ bakes: options.bakes ?? "sources"
189
+ }, (object) => {
177
190
  if (!isMesh(object) || !object.geometry.attributes.position) return;
178
191
  const { nonFinite, zeroNormals } = check(object.geometry);
179
192
  if (nonFinite > 0 || zeroNormals > 0) {
@@ -196,7 +209,10 @@ const findBadGeometry = (root, options = {}) => {
196
209
  const findEmptyMeshes = (root, options = {}) => {
197
210
  const scene = asObject3D(root);
198
211
  const rows = [];
199
- walk(scene, { skip: options.skip }, (object) => {
212
+ walk(scene, {
213
+ skip: options.skip,
214
+ bakes: options.bakes ?? "sources"
215
+ }, (object) => {
200
216
  if (isMesh(object) && (object.geometry.attributes.position?.count ?? 0) === 0) rows.push(withHidden({ mesh: meshLabel(object, scene, options.tagKey ?? "studioObject") }, "object", object));
201
217
  });
202
218
  return rows;
@@ -350,7 +366,8 @@ const findZFighting = (scene, options = {}) => {
350
366
  const keyOf = (x, y, z, d) => `${x},${y},${z},${d}`;
351
367
  walk(root, {
352
368
  skip: options.skip,
353
- visibleOnly: true
369
+ visibleOnly: true,
370
+ bakes: options.bakes ?? "sources"
354
371
  }, (object) => {
355
372
  if (!isMesh(object) || isInstanced(object) || isBatched(object) || object.geometry.isInstancedBufferGeometry === true || !object.geometry.attributes.position) return;
356
373
  if (tagged && tagOf(object, tagKey) === null) return;
@@ -665,11 +682,12 @@ const scopeOf = (root, options) => options.tag === void 0 ? options.skip : onlyT
665
682
  * scene has `zFighting`, `badGeometry` and `emptyMeshes` all `[]`.
666
683
  */
667
684
  const auditScene = (root, options = {}) => {
668
- const { tagKey } = options;
685
+ const { tagKey, bakes } = options;
669
686
  const skip = scopeOf(root, options);
670
- const keyed = tagKey === void 0 ? { skip } : {
687
+ const keyed = {
671
688
  skip,
672
- tagKey
689
+ bakes,
690
+ ...tagKey === void 0 ? {} : { tagKey }
673
691
  };
674
692
  return {
675
693
  triangles: countTriangles(root, { skip }),
@@ -689,11 +707,12 @@ const auditScene = (root, options = {}) => {
689
707
  * `triangles`, `draws` where you expect them) is a pass. Use `auditScene` to see what failed.
690
708
  */
691
709
  const summarizeScene = (root, options = {}) => {
692
- const { tagKey } = options;
710
+ const { tagKey, bakes } = options;
693
711
  const skip = scopeOf(root, options);
694
- const keyed = tagKey === void 0 ? { skip } : {
712
+ const keyed = {
695
713
  skip,
696
- tagKey
714
+ bakes,
715
+ ...tagKey === void 0 ? {} : { tagKey }
697
716
  };
698
717
  let zFighting = null;
699
718
  let selfZFighting = null;
@@ -705,6 +724,7 @@ const summarizeScene = (root, options = {}) => {
705
724
  ...tagKey === void 0 ? {} : { tagKey },
706
725
  ...options.tag === void 0 ? {} : { tagged: true },
707
726
  skip,
727
+ bakes,
708
728
  self: true
709
729
  })) if (row.meshes[0] === row.meshes[1]) selfZFighting += row.count;
710
730
  else zFighting += row.count;
@@ -722,4 +742,4 @@ const summarizeScene = (root, options = {}) => {
722
742
  //#endregion
723
743
  export { triangleCount as _, countTriangles as a, findBadGeometry as c, batchedTriangles as d, isBatched as f, pathOf as g, meshLabel as h, countDraws as i, findEmptyMeshes as l, isMesh as m, summarizeScene as n, geometryCensus as o, isInstanced as p, findZFighting as r, listMeshes as s, auditScene as t, asObject3D as u, walk as v };
724
744
 
725
- //# sourceMappingURL=audit-scene-CJG1DgRF.js.map
745
+ //# sourceMappingURL=audit-scene-Da-8SA1x.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-scene-Da-8SA1x.js","names":[],"sources":["../src/scene.ts","../src/bad-geometry.ts","../src/triangles.ts","../src/z-fighting.ts","../src/audit-scene.ts"],"sourcesContent":["/**\n * Scene-graph plumbing every check shares: the walk, mesh kinds, triangle counts, tags and names.\n *\n * Nothing here imports three at runtime; objects are recognised by three's own `is*` flags, so the\n * checks work on whatever copy of three the app bundles.\n */\nimport type { BatchedMesh, BufferGeometry, InstancedMesh, Material, Mesh, Object3D } from \"three\";\n\n/**\n * Any three.js object, from whichever copy of three's types the app has. The checks take this, so\n * an app on a newer `@types/three` than this package was built against passes its scene as is.\n */\nexport type AnyObject3D = { readonly isObject3D: true; readonly children: readonly object[] };\n\n/** The object as three's own type, for the walk. */\nexport const asObject3D = (object: AnyObject3D) => object as unknown as Object3D;\n\n/** Leave an object and everything under it out of a check. */\nexport type Skip = (object: Object3D) => boolean;\n\n/**\n * What a check sees of a bake (`@zkmake/three-batch`'s `bake` and `<Baked>`), which hides its\n * parts and draws one merged mesh per material instead. `sources`: the parts, as if shown, and not\n * the merge, so rows name real parts. `merged`: what the renderer draws.\n */\nexport type Bakes = \"sources\" | \"merged\";\n\nexport type WalkOptions = {\n skip?: Skip | undefined;\n /** Prune hidden objects and their subtrees, as the renderer does. */\n visibleOnly?: boolean;\n /** With `sources`: prune bake results, and walk the parts a bake hid as though visible. */\n bakes?: Bakes | undefined;\n};\n\n/**\n * A part a bake hid in favour of its merge: flagged `userData.bakeSource` by three-batch, or (for\n * bakes from before that flag) inside an object that holds a `userData.bakedResult` merge.\n */\nexport const isBakeSource = (object: Object3D) => {\n if (object.userData.bakeSource === true) {\n return true;\n }\n\n for (let at = object.parent; at; at = at.parent) {\n if (at.children.some((child) => child.userData.bakedResult === true)) {\n return true;\n }\n }\n\n return false;\n};\n\n/** Depth-first, in child order. A skipped (or, with `visibleOnly`, hidden) object prunes its subtree. */\nexport const walk = (root: Object3D, options: WalkOptions, visit: (object: Object3D) => void) => {\n const stack = [root];\n const sources = options.bakes === \"sources\";\n\n while (stack.length > 0) {\n const object = stack.pop()!;\n\n if (\n (sources && object.userData.bakedResult === true) ||\n (options.visibleOnly === true && !object.visible && !(sources && isBakeSource(object))) ||\n options.skip?.(object) === true\n ) {\n continue;\n }\n\n visit(object);\n\n for (let i = object.children.length - 1; i >= 0; i -= 1) {\n stack.push(object.children[i]!);\n }\n }\n};\n\nexport const isMesh = (object: Object3D): object is Mesh => (object as Mesh).isMesh === true;\n\n/** Anything the renderer draws: a mesh, line, point cloud or sprite. */\nexport const isDrawable = (object: Object3D) => {\n const flags = object as Partial<Record<\"isMesh\" | \"isLine\" | \"isPoints\" | \"isSprite\", boolean>>;\n\n return (\n flags.isMesh === true ||\n flags.isLine === true ||\n flags.isPoints === true ||\n flags.isSprite === true\n );\n};\n\nexport const isInstanced = (mesh: Mesh): mesh is InstancedMesh =>\n (mesh as InstancedMesh).isInstancedMesh === true;\n\nexport const isBatched = (mesh: Mesh): mesh is BatchedMesh =>\n (mesh as unknown as BatchedMesh).isBatchedMesh === true;\n\n/** Triangles one draw of this geometry makes: indexed or not, within its draw range. */\nexport const triangleCount = (geometry: BufferGeometry) => {\n const elements = geometry.index?.count ?? geometry.attributes.position?.count ?? 0;\n const { start, count } = geometry.drawRange;\n const drawn = Math.min(elements, start + count) - start;\n\n return Math.max(0, Math.floor(drawn / 3));\n};\n\n/**\n * A batch's visible instances by the geometry each draws: how many, and that geometry's triangles\n * (its range in the shared buffer). In first-drawn order.\n */\nexport const batchedGeometries = (batch: BatchedMesh) => {\n const geometries = new Map<number, { instances: number; triangles: number }>();\n\n for (let id = 0; id < batch.maxInstanceCount; id += 1) {\n let geometryId: number;\n\n // Deleted and never-used ids throw.\n try {\n if (!batch.getVisibleAt(id)) {\n continue;\n }\n\n geometryId = batch.getGeometryIdAt(id);\n } catch {\n continue;\n }\n\n const known = geometries.get(geometryId);\n\n if (known) {\n known.instances += 1;\n continue;\n }\n\n const range = batch.getGeometryRangeAt(geometryId) as {\n count?: number;\n indexCount: number;\n vertexCount: number;\n };\n const elements = range.count ?? (batch.geometry.index ? range.indexCount : range.vertexCount);\n\n geometries.set(geometryId, { instances: 1, triangles: Math.floor(elements / 3) });\n }\n\n return geometries;\n};\n\n/** Triangles a batch draws across its visible instances, and how many instances that is. */\nexport const batchedTriangles = (batch: BatchedMesh) => {\n let triangles = 0;\n let instances = 0;\n\n for (const geometry of batchedGeometries(batch).values()) {\n triangles += geometry.triangles * geometry.instances;\n instances += geometry.instances;\n }\n\n return { instances, triangles };\n};\n\n/** The tag of the nearest tagged ancestor (the object itself included), or `null`. */\nexport const tagOf = (object: Object3D, tagKey: string) => {\n for (let at: Object3D | null = object; at; at = at.parent) {\n const tag: unknown = at.userData[tagKey];\n\n if (tag !== undefined && tag !== null) {\n return String(tag);\n }\n }\n\n return null;\n};\n\n/**\n * A skip that keeps only what's tagged `tag`: prunes objects tagged otherwise, and meshes outside\n * any object tagged `tag`. Throws when nothing under `root` carries the tag.\n */\nexport const onlyTag = (root: Object3D, tag: string, tagKey: string, skip?: Skip): Skip => {\n let found = false;\n\n walk(root, { skip }, (object) => {\n const own: unknown = object.userData[tagKey];\n\n found ||= own !== undefined && own !== null && String(own) === tag;\n });\n\n if (!found) {\n throw new Error(`three-audit: nothing is tagged userData.${tagKey} = \"${tag}\"`);\n }\n\n return (object) => {\n if (skip?.(object) === true) {\n return true;\n }\n\n const own: unknown = object.userData[tagKey];\n\n if (own !== undefined && own !== null && String(own) !== tag) {\n return true;\n }\n\n return isDrawable(object) && tagOf(object, tagKey) !== tag;\n };\n};\n\nexport const hasTag = (root: Object3D, tagKey: string, skip?: Skip) => {\n let found = false;\n\n walk(root, { skip }, (object) => {\n found ||= object.userData[tagKey] !== undefined && object.userData[tagKey] !== null;\n });\n\n return found;\n};\n\nexport const meshName = (mesh: Mesh) => mesh.name || mesh.geometry.name || mesh.geometry.type;\n\n/**\n * The named ancestors above an object, outermost first, from `root` (included when named) down:\n * at most the nearest `depth`, with `…` for the rest. An unnamed ancestor carrying `tagKey` goes\n * by its tag.\n */\nexport const pathOf = (object: Object3D, root: Object3D | null, tagKey?: string, depth = 3) => {\n const names: string[] = [];\n\n for (let at = object.parent; at; at = at === root ? null : at.parent) {\n const tag: unknown = tagKey === undefined ? undefined : at.userData[tagKey];\n const name = at.name || (tag === undefined || tag === null ? \"\" : String(tag));\n\n if (name) {\n names.unshift(name);\n }\n }\n\n return names.length > depth ? [\"…\", ...names.slice(-depth)] : names;\n};\n\n/** `path/to/mesh [material]`: enough to find a mesh in the scene without walking it by hand. */\nexport const meshLabel = (mesh: Mesh, root: Object3D | null, tagKey?: string) =>\n `${[...pathOf(mesh, root, tagKey), meshName(mesh)].join(\"/\")} [${materialName(mesh)}]`;\n\nexport const materialName = (mesh: Mesh) => {\n const material = (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material) as\n | Material\n | undefined;\n\n return material ? material.name || material.type : \"none\";\n};\n\n/** Adds a property JSON and `console.table` leave out: a live object that would serialise the scene. */\nexport const withHidden = <Row extends object, Key extends string, Value>(\n row: Row,\n key: Key,\n value: Value,\n) => Object.defineProperty(row, key, { value, enumerable: false }) as Row & Record<Key, Value>;\n","/**\n * Geometry that lights a pixel NaN: a non-finite position or normal, or a zero-length normal on a\n * triangle with area. Bloom and other blurs spread one NaN pixel into a black block.\n */\nimport type { BufferGeometry, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n type Bakes,\n isMesh,\n meshLabel,\n meshName,\n type Skip,\n tagOf,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type BadGeometryOptions = {\n skip?: Skip | undefined;\n /** `userData` key naming the part a mesh belongs to, for the `under` column. */\n tagKey?: string;\n /** A bake's parts (default), or its merged meshes: see `Bakes`. Not both, or each counts twice. */\n bakes?: Bakes | undefined;\n};\n\nexport type BadGeometryRow = {\n mesh: string;\n /** The tagged part it sits in, or its parent's name. */\n under: string;\n /** Vertices with a NaN or infinite position or normal component. */\n nonFinite: number;\n /** Vertices of triangles with area whose normal has no length. */\n zeroNormals: number;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly object: Mesh;\n};\n\nconst corner = (geometry: BufferGeometry, k: number) => geometry.index?.getX(k) ?? k;\n\nconst check = (geometry: BufferGeometry) => {\n const position = geometry.attributes.position!;\n const normal = geometry.attributes.normal;\n const flagged = new Uint8Array(position.count);\n let nonFinite = 0;\n let zeroNormals = 0;\n\n for (let v = 0; v < position.count; v += 1) {\n const finite =\n Number.isFinite(position.getX(v)) &&\n Number.isFinite(position.getY(v)) &&\n Number.isFinite(position.getZ(v)) &&\n (!normal ||\n (Number.isFinite(normal.getX(v)) &&\n Number.isFinite(normal.getY(v)) &&\n Number.isFinite(normal.getZ(v))));\n\n if (!finite) {\n flagged[v] = 1;\n nonFinite += 1;\n }\n }\n\n if (!normal) {\n return { nonFinite, zeroNormals };\n }\n\n const corners = geometry.index?.count ?? position.count;\n\n for (let k = 0; k + 2 < corners; k += 3) {\n const a = corner(geometry, k);\n const b = corner(geometry, k + 1);\n const c = corner(geometry, k + 2);\n const ux = position.getX(b) - position.getX(a);\n const uy = position.getY(b) - position.getY(a);\n const uz = position.getZ(b) - position.getZ(a);\n const vx = position.getX(c) - position.getX(a);\n const vy = position.getY(c) - position.getY(a);\n const vz = position.getZ(c) - position.getZ(a);\n const area = Math.hypot(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx);\n\n if (!(area > 1e-8)) {\n continue;\n }\n\n for (const v of [a, b, c]) {\n if (flagged[v] === 0 && Math.hypot(normal.getX(v), normal.getY(v), normal.getZ(v)) < 1e-4) {\n flagged[v] = 1;\n zeroNormals += 1;\n }\n }\n }\n\n return { nonFinite, zeroNormals };\n};\n\n/** Meshes with geometry that renders NaN, hidden ones included. `[]` is a pass. */\nexport const findBadGeometry = (\n root: AnyObject3D,\n options: BadGeometryOptions = {},\n): BadGeometryRow[] => {\n const tagKey = options.tagKey ?? \"studioObject\";\n const rows: BadGeometryRow[] = [];\n\n walk(asObject3D(root), { skip: options.skip, bakes: options.bakes ?? \"sources\" }, (object) => {\n if (!isMesh(object) || !object.geometry.attributes.position) {\n return;\n }\n\n const { nonFinite, zeroNormals } = check(object.geometry);\n\n if (nonFinite > 0 || zeroNormals > 0) {\n const under = tagOf(object, tagKey) ?? object.parent?.name ?? \"\";\n\n rows.push(\n withHidden({ mesh: meshName(object), under, nonFinite, zeroNormals }, \"object\", object),\n );\n }\n });\n\n return rows;\n};\n\nexport type EmptyMeshRow = {\n /** `path/to/mesh [material]`. */\n mesh: string;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly object: Mesh;\n};\n\n/**\n * Meshes with no vertices, hidden ones included: what a merge of nothing leaves behind. They draw\n * nothing, but each one is still a draw call and a leak to dispose. A draw range of zero doesn't\n * count: that's usually a pool waiting to fill.\n */\nexport const findEmptyMeshes = (\n root: AnyObject3D,\n options: BadGeometryOptions = {},\n): EmptyMeshRow[] => {\n const scene = asObject3D(root);\n const rows: EmptyMeshRow[] = [];\n\n walk(scene, { skip: options.skip, bakes: options.bakes ?? \"sources\" }, (object) => {\n if (isMesh(object) && (object.geometry.attributes.position?.count ?? 0) === 0) {\n rows.push(\n withHidden(\n { mesh: meshLabel(object, scene, options.tagKey ?? \"studioObject\") },\n \"object\",\n object,\n ),\n );\n }\n });\n\n return rows;\n};\n","/**\n * Where a triangle budget goes: the total one pass draws, the meshes drawing it, and the\n * geometries behind them.\n */\nimport type { BatchedMesh, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n batchedGeometries,\n batchedTriangles,\n isBatched,\n isDrawable,\n isInstanced,\n isMesh,\n materialName,\n meshName,\n pathOf,\n type Skip,\n triangleCount,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type TriangleOptions = { skip?: Skip | undefined };\n\nexport type MeshRow = {\n /** `path/to/mesh`: its named ancestors, then its name (or its geometry's name or type). */\n name: string;\n material: string;\n /** Triangles in one copy. A batch's is an average: `geometryCensus` breaks it down. */\n triangles: number;\n instances: number;\n total: number;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly mesh: Mesh;\n};\n\nexport type GeometryRow = {\n /**\n * `geometry.name`, or its type and the start of its uuid. For a geometry in a batch, the batch's\n * `label` for it, or `batch#id`.\n */\n geometry: string;\n /** Copies drawn: every mesh using it, instances counted. */\n uses: number;\n triangles: number;\n total: number;\n /** Its part of the census's total, 0–1. */\n share: number;\n /** With `budget`: whether `share` is over it. */\n overBudget?: boolean;\n};\n\nexport type CensusOptions = TriangleOptions & {\n /**\n * Names a geometry inside a batch, e.g. from the app's own table of what it added. Default\n * `batch#id`, the batch named by its path.\n */\n label?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;\n /** Flag rows whose share of the total is over this, 0–1: `0.2` for anything over 20%. */\n budget?: number | undefined;\n};\n\nconst cost = (mesh: Mesh) => {\n if (isBatched(mesh)) {\n return batchedTriangles(mesh);\n }\n\n const instances = isInstanced(mesh) ? mesh.count : 1;\n\n return { instances, triangles: triangleCount(mesh.geometry) * instances };\n};\n\n/**\n * Triangles one pass draws: visible meshes, instances counted, no frustum culling. A shadow pass\n * adds roughly as much again for each shadow-casting light.\n */\nexport const countTriangles = (root: AnyObject3D, options: TriangleOptions = {}) => {\n let total = 0;\n\n walk(asObject3D(root), { skip: options.skip, visibleOnly: true }, (object) => {\n if (isMesh(object)) {\n total += cost(object).triangles;\n }\n });\n\n return total;\n};\n\n/** Every visible mesh, most triangles drawn first. */\nexport const listMeshes = (scene: AnyObject3D, options: TriangleOptions = {}): MeshRow[] => {\n const root = asObject3D(scene);\n const rows: MeshRow[] = [];\n\n walk(root, { skip: options.skip, visibleOnly: true }, (object) => {\n if (!isMesh(object)) {\n return;\n }\n\n const { instances, triangles } = cost(object);\n const row = {\n name: [...pathOf(object, root), meshName(object)].join(\"/\"),\n material: materialName(object),\n triangles: instances > 0 ? Math.round(triangles / instances) : 0,\n instances,\n total: triangles,\n };\n\n rows.push(withHidden(row, \"mesh\", object));\n });\n\n return rows.sort((a, b) => b.total - a.total);\n};\n\n/**\n * Geometries by triangles drawn, hidden meshes included, most first. Name geometries\n * (`geometry.name`) to make this readable. A batch counts each geometry in it separately, by its\n * visible instances: the row a budget is really spent on.\n */\nexport const geometryCensus = (root: AnyObject3D, options: CensusOptions = {}): GeometryRow[] => {\n const scene = asObject3D(root);\n const rows = new Map<string, GeometryRow>();\n const add = (key: string, triangles: number, uses: number) => {\n const row = rows.get(key) ?? { geometry: key, uses: 0, triangles, total: 0, share: 0 };\n\n row.uses += uses;\n row.total += triangles * uses;\n rows.set(key, row);\n };\n\n walk(scene, { skip: options.skip }, (object) => {\n if (!isMesh(object) || !object.geometry.attributes.position) {\n return;\n }\n\n if (isBatched(object)) {\n const name = [...pathOf(object, scene), meshName(object)].join(\"/\");\n\n for (const [id, { instances, triangles }] of batchedGeometries(object)) {\n add(options.label?.(object, id) ?? `${name}#${id}`, triangles, instances);\n }\n\n return;\n }\n\n const geometry = object.geometry;\n\n add(\n geometry.name || `${geometry.type}:${geometry.uuid.slice(0, 6)}`,\n triangleCount(geometry),\n isInstanced(object) ? object.count : 1,\n );\n });\n\n const all = [...rows.values()];\n const sum = all.reduce((total, row) => total + row.total, 0);\n\n for (const row of all) {\n row.share = sum > 0 ? Math.round((row.total / sum) * 1000) / 1000 : 0;\n\n if (options.budget !== undefined) {\n row.overBudget = row.total / Math.max(sum, 1) > options.budget;\n }\n }\n\n return all.sort((a, b) => b.total - a.total);\n};\n\n/**\n * Draw calls one main pass makes: one per visible mesh, line or point cloud, or one per material\n * group for a mesh with several materials. Before frustum culling; each shadow-casting light adds\n * one per caster. `recordDrawLedger` counts the real thing.\n */\nexport const countDraws = (root: AnyObject3D, options: TriangleOptions = {}) => {\n let draws = 0;\n\n walk(asObject3D(root), { skip: options.skip, visibleOnly: true }, (object) => {\n if (!isDrawable(object)) {\n return;\n }\n\n const drawable = object as Partial<Mesh>;\n\n const material = drawable.material;\n\n if (Array.isArray(material)) {\n const groups = drawable.geometry?.groups ?? [];\n\n draws += groups.filter(\n (group) => material[group.materialIndex ?? 0]?.visible !== false,\n ).length;\n } else if (material?.visible !== false) {\n draws += 1;\n }\n });\n\n return draws;\n};\n","/**\n * Z-fighting found by geometry instead of by eye: pairs of meshes with triangles in (nearly) one\n * plane, facing the same way, overlapping. Those flicker wherever depth precision runs out.\n *\n * Triangles are bucketed by their quantised plane (normal and offset), so only triangles in\n * neighbouring buckets are compared. A pair counts only when each triangle's corners lie within\n * `gap` of the other's plane, measured at the triangles: the offsets are measured from the world\n * origin, and far from it two faces a degree or two apart can share one while metres apart where\n * they stand. Overlap in the shared plane is a separating-axis test, then clipped for its area.\n *\n * A bake's hidden parts are checked in place of its merge (`bakes`). Left out, as they can't\n * fight: other hidden objects, instanced and batched meshes (and geometry a\n * shader positions, `InstancedBufferGeometry`), pairs where neither side writes depth or one\n * doesn't test it, and pairs where either material has a polygon offset (the usual decal fix).\n */\nimport type { Material, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n type Bakes,\n hasTag,\n isBatched,\n isInstanced,\n isMesh,\n meshLabel,\n type Skip,\n tagOf,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type ZFightingOptions = {\n /**\n * How close two planes count as one, in world units. 0.004 (4 mm at 1 unit = 1 m) is about what\n * depth precision loses with a camera ~50 m out. Scale it with your units.\n */\n gap?: number;\n /** Also compare triangles within one mesh: for merged geometry whose parts share a buffer. */\n self?: boolean;\n /**\n * Only meshes inside a tagged object (`userData[tagKey]`), so a studio's ground and props stay\n * out. Defaults to true when anything under the root is tagged, false otherwise.\n */\n tagged?: boolean;\n tagKey?: string;\n skip?: Skip | undefined;\n /**\n * A bake's parts (default), or its merged meshes: see `Bakes`. The parts name what to fix; the\n * merge is the same faces under one name.\n */\n bakes?: Bakes | undefined;\n};\n\nexport type ZFightingRow = {\n /** `path/to/mesh [material] @ (x, y, z)`: named ancestors, and the centre in world space. */\n a: string;\n b: string;\n /** Overlapping triangle pairs between the two. */\n triangles: number;\n /**\n * The planes they share, most overlap first: `z = -3.000, facing +z` when axis-aligned, else\n * `(nx, ny, nz) · p = d`. Both faces point along the normal: opposite-facing faces never pair.\n */\n planes: string[];\n /** Where they overlap: the area in world units², and its centre in world space. */\n overlap: { area: number; centre: [number, number, number] };\n /** Identical pairs (same labels, triangles and planes) folded into this row. */\n count: number;\n /** Not enumerable: kept out of JSON and `console.table`. The first pair folded in. */\n readonly meshes: readonly [Mesh, Mesh];\n /** Not enumerable: every pair folded in. */\n readonly pairs: readonly (readonly [Mesh, Mesh])[];\n};\n\n/** Normals quantised to 1/40: neighbouring buckets are searched, so this only sets bucket size. */\nconst QUANT = 40;\n/** Normals this aligned count as parallel (about 2.6°). */\nconst PARALLEL = 0.999;\n\ntype Part = {\n mesh: Mesh;\n label: string;\n min: number[];\n max: number[];\n /** Some material writes depth / tests it / offsets it. */\n writes: boolean;\n tests: boolean;\n offset: boolean;\n};\n\nconst round = (value: number, places = 2) => {\n const scale = 10 ** places;\n const rounded = Math.round(value * scale) / scale;\n\n return rounded === 0 ? 0 : rounded;\n};\n\nconst labelOf = (part: Part) => {\n const centre = part.min.map((low, axis) => round((low + part.max[axis]!) / 2));\n\n return `${part.label} @ (${centre.join(\", \")})`;\n};\n\nconst fixed = (value: number) => round(value, 3).toFixed(3);\n\n/** `z = -3.000, facing +z` for an axis-aligned plane, `(nx, ny, nz) · p = d` for any other. */\nconst planeLabel = (nx: number, ny: number, nz: number, d: number) => {\n for (const [axis, value] of [\n [\"x\", nx],\n [\"y\", ny],\n [\"z\", nz],\n ] as const) {\n if (Math.abs(value) > 0.9999) {\n return `${axis} = ${fixed(d / value)}, facing ${value > 0 ? \"+\" : \"-\"}${axis}`;\n }\n }\n\n return `(${fixed(nx)}, ${fixed(ny)}, ${fixed(nz)}) · p = ${fixed(d)}`;\n};\n\nconst materialsOf = (mesh: Mesh) =>\n (Array.isArray(mesh.material) ? mesh.material : [mesh.material]).filter(\n (material): material is Material => material !== undefined && material !== null,\n );\n\n/** Whether two parts can fight: one writes depth the other tests, and neither is offset. */\nconst canFight = (a: Part, b: Part) =>\n !a.offset && !b.offset && ((a.writes && b.tests) || (b.writes && a.tests));\n\n/** Pairs of meshes that z-fight, most overlapping triangles first. `[]` is a pass. */\nexport const findZFighting = (\n scene: AnyObject3D,\n options: ZFightingOptions = {},\n): ZFightingRow[] => {\n const root = asObject3D(scene);\n const gap = options.gap ?? 0.004;\n const self = options.self ?? false;\n const tagKey = options.tagKey ?? \"studioObject\";\n const tagged = options.tagged ?? hasTag(root, tagKey, options.skip);\n\n root.updateWorldMatrix(true, true);\n\n const parts: Part[] = [];\n // Per triangle: its part, plane (nx, ny, nz, d) and world corners (9 numbers).\n const owner: number[] = [];\n const planes: number[] = [];\n const corners: number[] = [];\n const keys: [number, number, number, number][] = [];\n const buckets = new Map<string, number[]>();\n const keyOf = (x: number, y: number, z: number, d: number) => `${x},${y},${z},${d}`;\n\n walk(\n root,\n { skip: options.skip, visibleOnly: true, bakes: options.bakes ?? \"sources\" },\n (object) => {\n if (\n !isMesh(object) ||\n isInstanced(object) ||\n isBatched(object) ||\n (object.geometry as { isInstancedBufferGeometry?: boolean }).isInstancedBufferGeometry ===\n true ||\n !object.geometry.attributes.position\n ) {\n return;\n }\n\n if (tagged && tagOf(object, tagKey) === null) {\n return;\n }\n\n const materials = materialsOf(object).filter((material) => material.visible !== false);\n\n if (materials.length === 0) {\n return;\n }\n\n const part = parts.length;\n const { index, attributes } = object.geometry;\n const position = attributes.position!;\n const e = object.matrixWorld.elements;\n const min = [Infinity, Infinity, Infinity];\n const max = [-Infinity, -Infinity, -Infinity];\n const count = index?.count ?? position.count;\n const world = [0, 0, 0, 0, 0, 0, 0, 0, 0];\n\n parts.push({\n mesh: object,\n label: meshLabel(object, root, tagKey),\n min,\n max,\n writes: materials.some((material) => material.depthWrite !== false),\n tests: materials.some((material) => material.depthTest !== false),\n offset: materials.some(\n (material) =>\n material.polygonOffset === true &&\n (material.polygonOffsetFactor !== 0 || material.polygonOffsetUnits !== 0),\n ),\n });\n\n for (let k = 0; k + 2 < count; k += 3) {\n for (let c = 0; c < 3; c += 1) {\n const v = index?.getX(k + c) ?? k + c;\n const x = position.getX(v);\n const y = position.getY(v);\n const z = position.getZ(v);\n\n for (let axis = 0; axis < 3; axis += 1) {\n const value = e[axis]! * x + e[axis + 4]! * y + e[axis + 8]! * z + e[axis + 12]!;\n\n world[c * 3 + axis] = value;\n min[axis] = Math.min(min[axis]!, value);\n max[axis] = Math.max(max[axis]!, value);\n }\n }\n\n const [ax, ay, az, bx, by, bz, cx, cy, cz] = world as [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n ];\n const ux = bx - ax;\n const uy = by - ay;\n const uz = bz - az;\n const vx = cx - ax;\n const vy = cy - ay;\n const vz = cz - az;\n let nx = uy * vz - uz * vy;\n let ny = uz * vx - ux * vz;\n let nz = ux * vy - uy * vx;\n const length = Math.hypot(nx, ny, nz);\n\n // Slivers under ~1e-5 m² show nothing.\n if (!(length / 2 >= 1e-5)) {\n continue;\n }\n\n nx /= length;\n ny /= length;\n nz /= length;\n\n const d = nx * ax + ny * ay + nz * az;\n const id = owner.length;\n const key: [number, number, number, number] = [\n Math.round(nx * QUANT),\n Math.round(ny * QUANT),\n Math.round(nz * QUANT),\n Math.round(d / gap),\n ];\n const bucket = buckets.get(keyOf(...key));\n\n owner.push(part);\n planes.push(nx, ny, nz, d);\n corners.push(...world);\n keys.push(key);\n\n if (bucket) {\n bucket.push(id);\n } else {\n buckets.set(keyOf(...key), [id]);\n }\n }\n },\n );\n\n // Overlap in s's plane, with a margin so triangles merely sharing an edge don't count.\n const margin = gap / 2;\n const flat = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n // s's plane axes from the last `overlaps`: u, then v.\n const frame = [0, 0, 0, 0, 0, 0];\n\n const overlaps = (s: number, t: number) => {\n const S = s * 9;\n const T = t * 9;\n let ux = corners[S + 3]! - corners[S]!;\n let uy = corners[S + 4]! - corners[S + 1]!;\n let uz = corners[S + 5]! - corners[S + 2]!;\n const length = Math.hypot(ux, uy, uz) || 1;\n\n ux /= length;\n uy /= length;\n uz /= length;\n\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const vx = ny * uz - nz * uy;\n const vy = nz * ux - nx * uz;\n const vz = nx * uy - ny * ux;\n\n frame[0] = ux;\n frame[1] = uy;\n frame[2] = uz;\n frame[3] = vx;\n frame[4] = vy;\n frame[5] = vz;\n\n // Corners 0–2 are s, 3–5 are t, as (x, y) in the plane.\n for (let i = 0; i < 6; i += 1) {\n const at = i < 3 ? S + i * 3 : T + (i - 3) * 3;\n const px = corners[at]!;\n const py = corners[at + 1]!;\n const pz = corners[at + 2]!;\n\n flat[i * 2] = px * ux + py * uy + pz * uz;\n flat[i * 2 + 1] = px * vx + py * vy + pz * vz;\n }\n\n for (let edge = 0; edge < 6; edge += 1) {\n const from = edge;\n const to = edge < 3 ? (edge + 1) % 3 : 3 + ((edge + 1) % 3);\n const ex = flat[to * 2]! - flat[from * 2]!;\n const ey = flat[to * 2 + 1]! - flat[from * 2 + 1]!;\n const edgeLength = Math.hypot(ex, ey) || 1;\n const axisX = -ey / edgeLength;\n const axisY = ex / edgeLength;\n let p0 = Infinity;\n let p1 = -Infinity;\n let q0 = Infinity;\n let q1 = -Infinity;\n\n for (let i = 0; i < 6; i += 1) {\n const value = flat[i * 2]! * axisX + flat[i * 2 + 1]! * axisY;\n\n if (i < 3) {\n p0 = Math.min(p0, value);\n p1 = Math.max(p1, value);\n } else {\n q0 = Math.min(q0, value);\n q1 = Math.max(q1, value);\n }\n }\n\n if (Math.min(p1, q1) - Math.max(p0, q0) < margin) {\n return false;\n }\n }\n\n return true;\n };\n\n /**\n * The overlap `overlaps` just found, as area and centroid in s's plane: t's triangle clipped to\n * s's (Sutherland–Hodgman), then the shoelace formula.\n */\n const clipped = () => {\n const turn =\n (flat[2]! - flat[0]!) * (flat[5]! - flat[1]!) - (flat[4]! - flat[0]!) * (flat[3]! - flat[1]!);\n // s's corners counter-clockwise.\n const order = turn >= 0 ? [0, 1, 2] : [0, 2, 1];\n let polygon = flat.slice(6, 12);\n\n for (let edge = 0; edge < 3 && polygon.length >= 6; edge += 1) {\n const ax = flat[order[edge]! * 2]!;\n const ay = flat[order[edge]! * 2 + 1]!;\n const ex = flat[order[(edge + 1) % 3]! * 2]! - ax;\n const ey = flat[order[(edge + 1) % 3]! * 2 + 1]! - ay;\n const next: number[] = [];\n const n = polygon.length / 2;\n\n for (let i = 0; i < n; i += 1) {\n const px = polygon[i * 2]!;\n const py = polygon[i * 2 + 1]!;\n const qx = polygon[((i + 1) % n) * 2]!;\n const qy = polygon[((i + 1) % n) * 2 + 1]!;\n const dp = ex * (py - ay) - ey * (px - ax);\n const dq = ex * (qy - ay) - ey * (qx - ax);\n\n if (dp >= 0) {\n next.push(px, py);\n }\n\n if (dp >= 0 !== dq >= 0) {\n const k = dp / (dp - dq);\n\n next.push(px + (qx - px) * k, py + (qy - py) * k);\n }\n }\n\n polygon = next;\n }\n\n let twice = 0;\n let cx = 0;\n let cy = 0;\n const n = polygon.length / 2;\n\n for (let i = 0; i < n; i += 1) {\n const x0 = polygon[i * 2]!;\n const y0 = polygon[i * 2 + 1]!;\n const x1 = polygon[((i + 1) % n) * 2]!;\n const y1 = polygon[((i + 1) % n) * 2 + 1]!;\n const cross = x0 * y1 - x1 * y0;\n\n twice += cross;\n cx += (x0 + x1) * cross;\n cy += (y0 + y1) * cross;\n }\n\n return Math.abs(twice) < 1e-12\n ? { area: 0, x: 0, y: 0 }\n : { area: Math.abs(twice) / 2, x: cx / (3 * twice), y: cy / (3 * twice) };\n };\n\n // Every corner of t within `gap` of s's plane, measured where t stands.\n const onPlane = (s: number, t: number) => {\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const d = planes[s * 4 + 3]!;\n\n for (let c = 0; c < 3; c += 1) {\n const at = t * 9 + c * 3;\n\n if (Math.abs(nx * corners[at]! + ny * corners[at + 1]! + nz * corners[at + 2]! - d) > gap) {\n return false;\n }\n }\n\n return true;\n };\n\n type Pair = {\n a: number;\n b: number;\n triangles: number;\n area: number;\n /** Area-weighted sums of the overlap's centre. */\n centre: [number, number, number];\n planes: Map<string, number>;\n };\n\n const pairs = new Map<string, Pair>();\n\n // Every neighbouring bucket, so near-equal planes that round apart still meet.\n for (let s = 0; s < owner.length; s += 1) {\n const [kx, ky, kz, kd] = keys[s]!;\n\n for (let i = -1; i <= 1; i += 1) {\n for (let j = -1; j <= 1; j += 1) {\n for (let k = -1; k <= 1; k += 1) {\n for (let l = -1; l <= 1; l += 1) {\n for (const t of buckets.get(keyOf(kx + i, ky + j, kz + k, kd + l)) ?? []) {\n const a = owner[s]!;\n const b = owner[t]!;\n\n if (\n t <= s ||\n (a === b && !self) ||\n !canFight(parts[a]!, parts[b]!) ||\n planes[s * 4]! * planes[t * 4]! +\n planes[s * 4 + 1]! * planes[t * 4 + 1]! +\n planes[s * 4 + 2]! * planes[t * 4 + 2]! <\n PARALLEL ||\n !onPlane(s, t) ||\n !onPlane(t, s) ||\n !overlaps(s, t)\n ) {\n continue;\n }\n\n const key = a < b ? `${a}:${b}` : `${b}:${a}`;\n const pair = pairs.get(key) ?? {\n a: Math.min(a, b),\n b: Math.max(a, b),\n triangles: 0,\n area: 0,\n centre: [0, 0, 0],\n planes: new Map<string, number>(),\n };\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const d = planes[s * 4 + 3]!;\n const { area, x, y } = clipped();\n const plane = planeLabel(nx, ny, nz, d);\n\n pair.triangles += 1;\n pair.area += area;\n pair.centre[0] += area * (x * frame[0]! + y * frame[3]! + d * nx);\n pair.centre[1] += area * (x * frame[1]! + y * frame[4]! + d * ny);\n pair.centre[2] += area * (x * frame[2]! + y * frame[5]! + d * nz);\n pair.planes.set(plane, (pair.planes.get(plane) ?? 0) + area);\n pairs.set(key, pair);\n }\n }\n }\n }\n }\n }\n\n // Fold identical pairs (pooled copies of one prop, all at one spot) into one row each.\n const rows = new Map<string, ZFightingRow & { pairs: (readonly [Mesh, Mesh])[] }>();\n\n for (const pair of [...pairs.values()].sort((x, y) => y.triangles - x.triangles)) {\n const meshes = [parts[pair.a]!.mesh, parts[pair.b]!.mesh] as const;\n const a = labelOf(parts[pair.a]!);\n const b = labelOf(parts[pair.b]!);\n const planeLabels = [...pair.planes.entries()]\n .sort((x, y) => y[1] - x[1])\n .map(([plane]) => plane);\n const key = JSON.stringify([a, b, pair.triangles, planeLabels]);\n const folded = rows.get(key);\n\n if (folded) {\n folded.count += 1;\n folded.pairs.push(meshes);\n continue;\n }\n\n const weight = pair.area > 0 ? pair.area : 1;\n const row = withHidden(\n withHidden(\n {\n a,\n b,\n triangles: pair.triangles,\n planes: planeLabels,\n overlap: {\n area: round(pair.area, 4),\n centre: pair.centre.map((sum) => round(sum / weight)) as [number, number, number],\n },\n count: 1,\n },\n \"meshes\",\n meshes,\n ),\n \"pairs\",\n [meshes],\n );\n\n rows.set(key, row);\n }\n\n return [...rows.values()];\n};\n","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":"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"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as auditScene } from "./audit-scene-CJG1DgRF.js";
2
+ import { t as auditScene } from "./audit-scene-Da-8SA1x.js";
3
3
  import { t as loadModel } from "./load-nqfCU3-k.js";
4
4
  import { basename } from "node:path";
5
5
  import { readFileSync } from "node:fs";
package/dist/index.d.ts CHANGED
@@ -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;
@@ -43,7 +51,7 @@ type EmptyMeshRow = {
43
51
  * nothing, but each one is still a draw call and a leak to dispose. A draw range of zero doesn't
44
52
  * count: that's usually a pool waiting to fill.
45
53
  */
46
- export declare const findEmptyMeshes: (root: AnyObject3D, options?: Pick<BadGeometryOptions, "skip" | "tagKey">) => EmptyMeshRow[];
54
+ export declare const findEmptyMeshes: (root: AnyObject3D, options?: BadGeometryOptions) => EmptyMeshRow[];
47
55
  //#endregion
48
56
  //#region src/triangles.d.ts
49
57
  type TriangleOptions = {
@@ -120,6 +128,11 @@ type ZFightingOptions = {
120
128
  tagged?: boolean;
121
129
  tagKey?: string;
122
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;
123
136
  };
124
137
  type ZFightingRow = {
125
138
  /** `path/to/mesh [material] @ (x, y, z)`: named ancestors, and the centre in world space. */
@@ -148,7 +161,7 @@ type ZFightingRow = {
148
161
  export declare const findZFighting: (scene: AnyObject3D, options?: ZFightingOptions) => ZFightingRow[];
149
162
  //#endregion
150
163
  //#region src/audit-scene.d.ts
151
- type AuditSceneOptions = Pick<ZFightingOptions, "gap" | "self" | "tagged"> & {
164
+ type AuditSceneOptions = Pick<ZFightingOptions, "bakes" | "gap" | "self" | "tagged"> & {
152
165
  skip?: Skip | undefined;
153
166
  tagKey?: string;
154
167
  /** Only the object(s) tagged this (`userData[tagKey]`): one studio object out of a scene. */
@@ -171,7 +184,7 @@ type AuditReport = {
171
184
  * scene has `zFighting`, `badGeometry` and `emptyMeshes` all `[]`.
172
185
  */
173
186
  export declare const auditScene: (root: AnyObject3D, options?: AuditSceneOptions) => AuditReport;
174
- type AuditSummaryOptions = Pick<AuditSceneOptions, "gap" | "skip" | "tag" | "tagKey"> & {
187
+ type AuditSummaryOptions = Pick<AuditSceneOptions, "bakes" | "gap" | "skip" | "tag" | "tagKey"> & {
175
188
  /** Run the z-fighting check, the slow one. Default true; false gives `null` counts. */
176
189
  zFighting?: boolean;
177
190
  };
@@ -314,6 +327,8 @@ type AuditHelpersOptions = {
314
327
  renderer?: (LedgerRenderer & ProbeRenderer) | undefined;
315
328
  skip?: Skip | undefined;
316
329
  tagKey?: string;
330
+ /** What checks see of a bake: its parts (default) or its merged meshes. See `Bakes`. */
331
+ bakes?: Bakes | undefined;
317
332
  /** Names geometries inside a batch in `census()`: see `geometryCensus`'s `label`. */
318
333
  batchLabel?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;
319
334
  /**
@@ -336,6 +351,8 @@ export declare const installAuditHelpers: (root: AnyObject3D, options?: AuditHel
336
351
  type MeasureOptions = {
337
352
  /** `userData` key a string target is looked up by. Default `studioObject`. */
338
353
  tagKey?: string;
354
+ /** A bake's parts (default), or its merged meshes: see `Bakes`. The same space either way. */
355
+ bakes?: Bakes | undefined;
339
356
  };
340
357
  /** What to measure: an object, or a tag (`userData[tagKey]`), or failing that an object name. */
341
358
  type Target = string | AnyObject3D;
@@ -365,5 +382,5 @@ export declare const measureBounds: (root: AnyObject3D, target: Target, options?
365
382
  */
366
383
  export declare const measureClearance: (root: AnyObject3D, a: Target, b: Target, options?: MeasureOptions) => Clearance | null;
367
384
  //#endregion
368
- export type { AnyObject3D, AuditHelpersOptions, AuditReport, AuditSceneOptions, AuditSummary, AuditSummaryOptions, BadGeometryOptions, BadGeometryRow, BlackFrameOptions, BlackFrameWatch, Bounds, CensusOptions, Clearance, DrawLedger, DrawLedgerOptions, DrawLedgerRecording, EmptyMeshRow, GeometryRow, LedgerRenderer, LedgerRow, MeasureOptions, MeshRow, PassRow, ProbeRenderer, RecordDrawLedgerOptions, Skip, Target, TriangleOptions, ZFightingOptions, ZFightingRow };
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 };
369
386
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as triangleCount, a as countTriangles, c as findBadGeometry, d as batchedTriangles, f as isBatched, g as pathOf, h as meshLabel, i as countDraws, l as findEmptyMeshes, m as isMesh, n as summarizeScene, o as geometryCensus, p as isInstanced, r as findZFighting, s as listMeshes, t as auditScene, u as asObject3D, v as walk } from "./audit-scene-CJG1DgRF.js";
1
+ import { _ as triangleCount, a as countTriangles, c as findBadGeometry, d as batchedTriangles, f as isBatched, g as pathOf, h as meshLabel, i as countDraws, l as findEmptyMeshes, m as isMesh, n as summarizeScene, o as geometryCensus, p as isInstanced, r as findZFighting, s as listMeshes, t as auditScene, u as asObject3D, v as walk } from "./audit-scene-Da-8SA1x.js";
2
2
  //#region src/patch.ts
3
3
  /** Swap `key` on `target` for `value`; the returned function puts back exactly what was there. */
4
4
  const patch = (target, key, value) => {
@@ -406,7 +406,7 @@ const round = (value) => {
406
406
  return rounded === 0 ? 0 : rounded;
407
407
  };
408
408
  /** The visible meshes a target stands for. */
409
- const meshesOf = (root, target, tagKey, role) => {
409
+ const meshesOf = (root, target, { tagKey = "studioObject", bakes = "sources" }, role) => {
410
410
  let objects;
411
411
  if (typeof target === "string") {
412
412
  const tagged = [];
@@ -420,7 +420,10 @@ const meshesOf = (root, target, tagKey, role) => {
420
420
  if (objects.length === 0) throw new Error(`three-audit: ${role}: nothing is tagged userData.${tagKey} = "${target}" or named "${target}"`);
421
421
  } else objects = [asObject3D(target)];
422
422
  const meshes = /* @__PURE__ */ new Set();
423
- for (const object of objects) walk(object, { visibleOnly: true }, (child) => {
423
+ for (const object of objects) walk(object, {
424
+ visibleOnly: true,
425
+ bakes
426
+ }, (child) => {
424
427
  if (isMesh(child)) meshes.add(child);
425
428
  });
426
429
  return meshes;
@@ -430,7 +433,7 @@ const measureBounds = (root, target, options = {}) => {
430
433
  const scene = asObject3D(root);
431
434
  scene.updateWorldMatrix(true, true);
432
435
  const box = emptyBox();
433
- for (const mesh of meshesOf(scene, target, options.tagKey ?? "studioObject", "bounds")) eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {
436
+ for (const mesh of meshesOf(scene, target, options, "bounds")) eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {
434
437
  grow(box, ax, ay, az);
435
438
  grow(box, bx, by, bz);
436
439
  grow(box, cx, cy, cz);
@@ -469,8 +472,8 @@ const measureClearance = (root, a, b, options = {}) => {
469
472
  const scene = asObject3D(root);
470
473
  const tagKey = options.tagKey ?? "studioObject";
471
474
  scene.updateWorldMatrix(true, true);
472
- const inB = meshesOf(scene, b, tagKey, "clearance b");
473
- const inA = [...meshesOf(scene, a, tagKey, "clearance a")].filter((mesh) => !inB.has(mesh));
475
+ const inB = meshesOf(scene, b, options, "clearance b");
476
+ const inA = [...meshesOf(scene, a, options, "clearance a")].filter((mesh) => !inB.has(mesh));
474
477
  const measure = (mesh) => {
475
478
  const box = emptyBox();
476
479
  eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {
@@ -584,13 +587,14 @@ const needsRenderer = (name) => () => {
584
587
  * useLayoutEffect(() => installAuditHelpers(scene, { renderer: gl }), [scene, gl]);
585
588
  */
586
589
  const installAuditHelpers = (root, options = {}) => {
587
- const { renderer, skip, batchLabel } = options;
590
+ const { renderer, skip, batchLabel, bakes } = options;
588
591
  const tagKey = options.tagKey ?? "studioObject";
589
592
  const helpers = {
590
593
  auditRoot: root,
591
594
  summary: (tag, summaryOptions = {}) => summarizeScene(root, {
592
595
  skip,
593
596
  tagKey,
597
+ bakes,
594
598
  ...summaryOptions,
595
599
  ...tag === void 0 ? {} : { tag }
596
600
  }),
@@ -603,21 +607,30 @@ const installAuditHelpers = (root, options = {}) => {
603
607
  }),
604
608
  audit: () => findBadGeometry(root, {
605
609
  skip,
606
- tagKey
610
+ tagKey,
611
+ bakes
607
612
  }),
608
613
  zfight: (gap, self) => findZFighting(root, {
609
614
  skip,
610
615
  tagKey,
616
+ bakes,
611
617
  ...gap === void 0 ? {} : { gap },
612
618
  ...self === void 0 ? {} : { self }
613
619
  }),
614
620
  report: (tag) => auditScene(root, {
615
621
  skip,
616
622
  tagKey,
623
+ bakes,
617
624
  ...tag === void 0 ? {} : { tag }
618
625
  }),
619
- bbox: (target) => measureBounds(root, target, { tagKey }),
620
- clearance: (a, b) => measureClearance(root, a, b, { tagKey }),
626
+ bbox: (target) => measureBounds(root, target, {
627
+ tagKey,
628
+ bakes
629
+ }),
630
+ clearance: (a, b) => measureClearance(root, a, b, {
631
+ tagKey,
632
+ bakes
633
+ }),
621
634
  ledger: renderer ? async (ledgerOptions) => {
622
635
  const ledger = await recordDrawLedger(renderer, ledgerOptions);
623
636
  printDrawLedger(ledger);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/patch.ts","../src/black-frames.ts","../src/frames.ts","../src/draw-ledger.ts","../src/measure.ts","../src/console-helpers.ts"],"sourcesContent":["/** Swap `key` on `target` for `value`; the returned function puts back exactly what was there. */\nexport const patch = <Target extends object, Key extends keyof Target>(\n target: Target,\n key: Key,\n value: Target[Key],\n) => {\n const own = Object.hasOwn(target, key);\n const original = target[key];\n\n target[key] = value;\n\n return () => {\n if (own) {\n target[key] = original;\n } else {\n delete target[key];\n }\n };\n};\n","/**\n * Black-frame probe: after each frame, samples a grid of the drawing buffer and counts frames\n * where several samples are pure black. Catches the flash a NaN pixel makes once bloom spreads it,\n * which lasts one frame in twenty and never shows in a screenshot.\n *\n * WebGL only (`readPixels`). Each sample stalls the GPU pipeline, so watch, read, stop.\n */\nimport { patch } from \"./patch.ts\";\nimport type { AnyObject3D } from \"./scene.ts\";\n\n/** The `WebGLRenderer` methods the probe uses, typed loosely to fit any copy of three's types. */\nexport type ProbeRenderer = {\n render(scene: AnyObject3D, camera: object): void;\n getContext(): unknown;\n getRenderTarget(): unknown;\n};\n\nexport type BlackFrameOptions = {\n /** Samples per side: 3 reads a 3 × 3 grid at the quarter lines. */\n grid?: number;\n /** Black samples that make a frame black. */\n threshold?: number;\n};\n\nexport type BlackFrameWatch = {\n /** Frames sampled since the start or the last `reset()`. */\n readonly frames: number;\n /** Which of those frames (1-based) were black. */\n readonly blackFrames: readonly number[];\n reset: () => void;\n /** Restore the renderer and stop sampling. */\n stop: () => void;\n};\n\ntype ReadPixels = Pick<\n WebGLRenderingContext,\n \"RGBA\" | \"UNSIGNED_BYTE\" | \"drawingBufferHeight\" | \"drawingBufferWidth\" | \"readPixels\"\n>;\n\n/**\n * Sample every frame the app draws to the screen. Several screen renders in one frame (an overlay\n * redrawn after the composer) count once, as the last of them: the image the display shows.\n * Browser only: frames are told apart by `requestAnimationFrame`.\n */\nexport const watchBlackFrames = (\n renderer: ProbeRenderer,\n options: BlackFrameOptions = {},\n): BlackFrameWatch => {\n const context = renderer.getContext() as Partial<ReadPixels> | null;\n\n if (typeof context?.readPixels !== \"function\") {\n throw new TypeError(\"three-audit: the black-frame probe needs a WebGLRenderer\");\n }\n\n if (typeof requestAnimationFrame !== \"function\") {\n throw new TypeError(\"three-audit: the black-frame probe needs requestAnimationFrame\");\n }\n\n const gl = context as ReadPixels;\n const grid = options.grid ?? 3;\n const threshold = options.threshold ?? 3;\n const pixel = new Uint8Array(4);\n const render = renderer.render;\n let frames = 0;\n let black: number[] = [];\n let pending: boolean | null = null;\n\n const isBlack = () => {\n let count = 0;\n\n for (let i = 1; i <= grid; i += 1) {\n for (let j = 1; j <= grid; j += 1) {\n const x = Math.floor((gl.drawingBufferWidth * i) / (grid + 1));\n const y = Math.floor((gl.drawingBufferHeight * j) / (grid + 1));\n\n gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);\n\n if (pixel[0]! < 4 && pixel[1]! < 4 && pixel[2]! < 4 && pixel[3]! > 250) {\n count += 1;\n }\n }\n }\n\n return count >= threshold;\n };\n\n // Commit the frame's last screen sample once per animation frame.\n let raf = 0;\n const commit = () => {\n if (pending !== null) {\n frames += 1;\n\n if (pending) {\n black.push(frames);\n }\n\n pending = null;\n }\n\n raf = requestAnimationFrame(commit);\n };\n\n raf = requestAnimationFrame(commit);\n\n const restore = patch(renderer, \"render\", function (this: unknown, scene, camera) {\n render.call(this, scene, camera);\n\n // Read before the browser presents (and, without preserveDrawingBuffer, clears) the buffer.\n if (renderer.getRenderTarget() === null) {\n pending = isBlack();\n }\n });\n\n return {\n get frames() {\n return frames;\n },\n get blackFrames() {\n return black;\n },\n reset: () => {\n frames = 0;\n black = [];\n pending = null;\n },\n stop: () => {\n cancelAnimationFrame(raf);\n restore();\n },\n };\n};\n","/**\n * Waiting on animation frames without hanging: a tab in the background (an agent's browser, an\n * occluded window) gets none, so every wait gives up after a timeout and says why.\n */\n\n/** Frames an agent-driven tab should see within this long, in ms. */\nexport const FRAME_TIMEOUT = 2000;\n\nconst noFrame = (caller: string, timeout: number, hint: string) => {\n const hidden = typeof document !== \"undefined\" && document.hidden;\n\n return new Error(\n `three-audit: ${caller} got no animation frame in ${timeout / 1000} s${\n hidden\n ? \" (document.hidden is true: browsers pause requestAnimationFrame in background tabs)\"\n : \"\"\n }. ${hint}`,\n );\n};\n\n/** The next animation frame, or an error naming the cause when none comes within `timeout`. */\nexport const nextFrame = (caller: string, timeout: number, hint: string) =>\n new Promise<void>((resolve, reject) => {\n if (typeof requestAnimationFrame !== \"function\") {\n reject(new Error(`three-audit: ${caller} needs requestAnimationFrame`));\n\n return;\n }\n\n let frame = 0;\n const timer = setTimeout(() => {\n cancelAnimationFrame(frame);\n reject(noFrame(caller, timeout, hint));\n }, timeout);\n\n frame = requestAnimationFrame(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n","/**\n * A ledger of every draw call over one frame: which object drew, in which pass. Exact counts from\n * the renderer itself, where `renderer.info` gives one total (and resets it every `render()`).\n *\n * WebGL only: it wraps `WebGLRenderer.renderBufferDirect`, which every draw goes through, and\n * `render()`, to tell passes apart. Draws with a depth or distance material are the shadow maps.\n */\nimport type { BufferGeometry, Material, Mesh, Object3D } from \"three\";\n\nimport { FRAME_TIMEOUT, nextFrame } from \"./frames.ts\";\nimport { patch } from \"./patch.ts\";\nimport {\n type AnyObject3D,\n batchedTriangles,\n isBatched,\n isInstanced,\n pathOf,\n triangleCount,\n} from \"./scene.ts\";\n\n/**\n * The two `WebGLRenderer` methods the ledger wraps. Typed loosely, so a renderer from any copy of\n * three's types fits.\n */\nexport type LedgerRenderer = {\n render(scene: AnyObject3D, camera: object): void;\n renderBufferDirect(\n camera: object,\n scene: object | null,\n geometry: object,\n material: object,\n object: AnyObject3D,\n group: unknown,\n ): void;\n /** For pass sizes: the target being drawn to, `null` for the screen. */\n getRenderTarget?(): unknown;\n /** For pass sizes: the screen's drawing buffer, device pixels. */\n getContext?(): unknown;\n};\n\nexport type DrawLedgerOptions = {\n /**\n * The group an object name files under. Default: up to the first `:` or `#`, so a bake's\n * `wagon:paint` and a strip's `grass#3` group as `wagon` and `grass`.\n */\n groupOf?: (name: string) => string;\n};\n\nexport type RecordDrawLedgerOptions = DrawLedgerOptions & {\n /**\n * Record one call of this (the app's own render, sync or async) instead of one animation frame.\n * For a tab in the background, where no frames come.\n */\n render?: () => unknown;\n /** How long to wait for each animation frame before failing, in ms. Default 2000. */\n timeout?: number;\n};\n\nexport type LedgerRow = {\n name: string;\n total: number;\n /** Draws per pass name. */\n passes: Record<string, number>;\n};\n\nexport type PassRow = {\n name: string;\n draws: number;\n /** Distinct objects drawn: for `shadow`, the casters. */\n objects: number;\n /** Triangles drawn, instances counted. */\n triangles: number;\n /** Sizes drawn to, in device pixels: `2880×1800`, or `screen 2880×1800`. */\n targets: string[];\n /** Every call was one small mesh through an orthographic camera: a post-processing pass. */\n fullscreen: boolean;\n};\n\nexport type DrawLedger = {\n /**\n * Pass names in the order they first drew: `shadow`, then each `render()` call as its scene's\n * name, or `render 1`, `render 2`… when unnamed (a post-processing chain is several).\n */\n passes: string[];\n /** By group, most draws first. */\n groups: LedgerRow[];\n /** By object name, most draws first. */\n objects: LedgerRow[];\n draws: number;\n /** Each pass's draws, triangles, casters and target sizes, in `passes` order. */\n passStats: PassRow[];\n /** `render()` calls that drew one fullscreen pass, and the pixels they shaded between them. */\n fullscreenPasses: number;\n fullscreenPixels: number;\n};\n\nexport type DrawLedgerRecording = {\n /** Restore the renderer and return what was drawn since `beginDrawLedger`. */\n end: () => DrawLedger;\n};\n\nconst defaultGroupOf = (name: string) => name.split(/[:#]/)[0] || name;\n\n/** The object's name, or for an unnamed one its named ancestors, type and material. */\nconst nameOf = (object: Object3D, material: Material) =>\n object.name ||\n `${[...pathOf(object, null, undefined, 2), object.type].join(\"/\")} [${material.name || material.type}]`;\n\nconst tally = (rows: Map<string, LedgerRow>, name: string, pass: string) => {\n const row = rows.get(name) ?? { name, total: 0, passes: {} };\n\n row.total += 1;\n row.passes[pass] = (row.passes[pass] ?? 0) + 1;\n rows.set(name, row);\n};\n\nconst sorted = (rows: Map<string, LedgerRow>) =>\n [...rows.values()].sort((a, b) => b.total - a.total);\n\n/** Triangles one draw makes: its material group's range, times its instances. */\nconst drawTriangles = (geometry: BufferGeometry, object: Object3D, group: unknown) => {\n const mesh = object as Mesh;\n\n if (mesh.isMesh !== true) {\n return 0;\n }\n\n if (isBatched(mesh)) {\n return batchedTriangles(mesh).triangles;\n }\n\n const range = group as { start?: number; count?: number } | null;\n const elements = geometry.index?.count ?? geometry.attributes.position?.count ?? 0;\n const own =\n range && typeof range.count === \"number\"\n ? Math.floor(Math.max(0, Math.min(range.count, elements - (range.start ?? 0))) / 3)\n : triangleCount(geometry);\n\n return own * (isInstanced(mesh) ? mesh.count : 1);\n};\n\ntype Size = { width: number; height: number; screen: boolean };\n\ntype PassTally = Omit<PassRow, \"objects\" | \"targets\" | \"fullscreen\"> & {\n objects: Set<Object3D>;\n targets: Set<string>;\n calls: number;\n fullscreenCalls: number;\n};\n\ntype Call = {\n pass: string;\n draws: number;\n orthographic: boolean;\n /** Vertices of the one draw so far. */\n vertices: number;\n size: Size | null;\n};\n\n/** Start counting every draw. Call `end()` after the frame to restore the renderer and read it. */\nexport const beginDrawLedger = (\n renderer: LedgerRenderer,\n options: DrawLedgerOptions = {},\n): DrawLedgerRecording => {\n if (typeof renderer.renderBufferDirect !== \"function\") {\n throw new TypeError(\"three-audit: the draw ledger needs a WebGLRenderer\");\n }\n\n const groupOf = options.groupOf ?? defaultGroupOf;\n const render = renderer.render;\n const renderBufferDirect = renderer.renderBufferDirect;\n const passes: string[] = [];\n const objects = new Map<string, LedgerRow>();\n const groups = new Map<string, LedgerRow>();\n const stats = new Map<string, PassTally>();\n const calls: Call[] = [];\n let renders = 0;\n let current = \"direct\";\n let draws = 0;\n let fullscreenPasses = 0;\n let fullscreenPixels = 0;\n\n const statOf = (pass: string) => {\n let stat = stats.get(pass);\n\n if (!stat) {\n stat = {\n name: pass,\n draws: 0,\n triangles: 0,\n objects: new Set(),\n targets: new Set(),\n calls: 0,\n fullscreenCalls: 0,\n };\n stats.set(pass, stat);\n }\n\n return stat;\n };\n\n const sizeNow = (): Size | null => {\n const target = renderer.getRenderTarget?.() as { width?: number; height?: number } | null;\n\n if (target && typeof target.width === \"number\" && typeof target.height === \"number\") {\n return { width: target.width, height: target.height, screen: false };\n }\n\n const gl = renderer.getContext?.() as\n | { drawingBufferWidth?: number; drawingBufferHeight?: number }\n | null\n | undefined;\n\n return target === null && typeof gl?.drawingBufferWidth === \"number\"\n ? { width: gl.drawingBufferWidth, height: gl.drawingBufferHeight ?? 0, screen: true }\n : null;\n };\n\n const restoreRender = patch(renderer, \"render\", function (this: unknown, scene, camera) {\n const outer = current;\n\n renders += 1;\n current = (scene as Partial<Object3D>).name || `render ${renders}`;\n\n const call: Call = {\n pass: current,\n draws: 0,\n orthographic: (camera as { isOrthographicCamera?: boolean }).isOrthographicCamera === true,\n vertices: 0,\n size: null,\n };\n\n calls.push(call);\n\n try {\n render.call(this, scene, camera);\n } finally {\n calls.pop();\n current = outer;\n\n const stat = stats.get(call.pass);\n\n if (stat) {\n stat.calls += 1;\n\n // One small mesh through an orthographic camera: a fullscreen quad or triangle.\n if (call.draws === 1 && call.orthographic && call.vertices <= 6) {\n stat.fullscreenCalls += 1;\n fullscreenPasses += 1;\n fullscreenPixels += call.size ? call.size.width * call.size.height : 0;\n }\n }\n }\n });\n\n const restoreDraw = patch(\n renderer,\n \"renderBufferDirect\",\n function (this: unknown, camera, scene, geometry, material, object, group) {\n const shadow =\n (material as { isMeshDepthMaterial?: boolean }).isMeshDepthMaterial === true ||\n (material as { isMeshDistanceMaterial?: boolean }).isMeshDistanceMaterial === true;\n const pass = shadow ? \"shadow\" : current;\n const name = nameOf(object as unknown as Object3D, material as Material);\n\n if (!passes.includes(pass)) {\n passes.push(pass);\n }\n\n const stat = statOf(pass);\n const size = sizeNow();\n const drawn = geometry as BufferGeometry;\n\n stat.draws += 1;\n stat.triangles += drawTriangles(drawn, object as unknown as Object3D, group);\n stat.objects.add(object as unknown as Object3D);\n\n if (size) {\n stat.targets.add(`${size.screen ? \"screen \" : \"\"}${size.width}×${size.height}`);\n }\n\n const call = calls.at(-1);\n\n if (call && !shadow) {\n call.draws += 1;\n call.vertices = drawn.attributes?.position?.count ?? Infinity;\n call.size = size;\n }\n\n draws += 1;\n tally(objects, name, pass);\n tally(groups, groupOf(name), pass);\n renderBufferDirect.call(this, camera, scene, geometry, material, object, group);\n },\n );\n\n return {\n end: () => {\n restoreDraw();\n restoreRender();\n\n return {\n passes,\n groups: sorted(groups),\n objects: sorted(objects),\n draws,\n passStats: passes.map((pass) => {\n const stat = stats.get(pass)!;\n\n return {\n name: pass,\n draws: stat.draws,\n objects: stat.objects.size,\n triangles: stat.triangles,\n targets: [...stat.targets],\n fullscreen: stat.calls > 0 && stat.fullscreenCalls === stat.calls,\n };\n }),\n fullscreenPasses,\n fullscreenPixels,\n };\n },\n };\n};\n\n/**\n * Record exactly one frame of an app that renders from `requestAnimationFrame` (three's\n * `setAnimationLoop`, React Three Fiber): from one animation frame to the next. A demand-driven\n * loop that renders nothing that frame gives an empty ledger. Browser only.\n *\n * With `render`, records one call of it instead, frames or not. Without, fails after `timeout`\n * when no frame comes (a background tab), rather than waiting for ever.\n */\nexport const recordDrawLedger = async (\n renderer: LedgerRenderer,\n options: RecordDrawLedgerOptions = {},\n): Promise<DrawLedger> => {\n const { render, timeout = FRAME_TIMEOUT, ...ledgerOptions } = options;\n\n if (render) {\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n try {\n await render();\n } catch (error) {\n recording.end();\n throw error;\n }\n\n return recording.end();\n }\n\n const hint = \"Bring the tab to the front, or pass { render } to record one call of your own.\";\n\n await nextFrame(\"recordDrawLedger\", timeout, hint);\n\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n try {\n await nextFrame(\"recordDrawLedger\", timeout, hint);\n } catch (error) {\n recording.end();\n throw error;\n }\n\n return recording.end();\n};\n\n/** Rows shaped for `console.table`: one column per pass, then the total. */\nexport const ledgerTable = (rows: LedgerRow[], passes: string[]) =>\n rows.map((row) => ({\n name: row.name,\n ...Object.fromEntries(passes.map((pass) => [pass, row.passes[pass] ?? 0])),\n total: row.total,\n }));\n\n/** The ledger as two console tables: by group, and every object collapsed beneath. */\nexport const printDrawLedger = (ledger: DrawLedger) => {\n /* oxlint-disable no-console -- printing is this function's job */\n console.group(`Draw ledger: ${ledger.draws} draws in one frame`);\n console.table(ledgerTable(ledger.groups, ledger.passes));\n console.table(ledger.passStats.map((pass) => ({ ...pass, targets: pass.targets.join(\", \") })));\n\n if (ledger.fullscreenPasses > 0) {\n console.log(\n `${ledger.fullscreenPasses} fullscreen passes: ${(ledger.fullscreenPixels / 1e6).toFixed(1)} Mpx shaded`,\n );\n }\n\n console.groupCollapsed(\"Every object\");\n console.table(ledgerTable(ledger.objects, ledger.passes));\n console.groupEnd();\n console.groupEnd();\n /* oxlint-enable no-console */\n};\n","/**\n * Measuring a layout: an object's world envelope, and the gap between two objects. The questions\n * a scene raises before anything renders wrong: does the train fit under the crossbar, does the\n * lid clear the hinge.\n *\n * Measured from the vertices as drawn (instances and batches included), not from bounding boxes\n * transformed after the fact, which grow with every rotation.\n */\nimport type { BatchedMesh, Mesh, Object3D } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n isBatched,\n isInstanced,\n isMesh,\n meshLabel,\n walk,\n} from \"./scene.ts\";\n\nexport type MeasureOptions = {\n /** `userData` key a string target is looked up by. Default `studioObject`. */\n tagKey?: string;\n};\n\n/** What to measure: an object, or a tag (`userData[tagKey]`), or failing that an object name. */\nexport type Target = string | AnyObject3D;\n\nexport type Bounds = {\n min: [number, number, number];\n max: [number, number, number];\n size: [number, number, number];\n centre: [number, number, number];\n};\n\nexport type Clearance = {\n /**\n * The smallest gap between the two, in world units. Measured between triangles' bounding boxes:\n * exact for faces square to the axes, a slight underestimate for sloped ones. 0 when they touch\n * or intersect.\n */\n gap: number;\n /** The axis the gap is measured along, when it's straight along one; `null` when diagonal. */\n axis: \"x\" | \"y\" | \"z\" | null;\n /** The closest meshes, one from each side. */\n between: [string, string];\n};\n\n/** A 4 × 4 column-major matrix, as three stores them. */\ntype Elements = ArrayLike<number>;\n\nconst multiply = (a: Elements, b: Elements, out: number[]) => {\n for (let row = 0; row < 4; row += 1) {\n for (let column = 0; column < 4; column += 1) {\n let sum = 0;\n\n for (let k = 0; k < 4; k += 1) {\n sum += a[k * 4 + row]! * b[column * 4 + k]!;\n }\n\n out[column * 4 + row] = sum;\n }\n }\n\n return out;\n};\n\ntype Triangle = (\n ax: number,\n ay: number,\n az: number,\n bx: number,\n by: number,\n bz: number,\n cx: number,\n cy: number,\n cz: number,\n) => void;\n\n/** Every triangle a mesh draws, in world space: each instance of an instanced or batched mesh. */\nconst eachTriangle = (mesh: Mesh, visit: Triangle) => {\n const { index, attributes } = mesh.geometry;\n const position = attributes.position;\n\n if (!position) {\n return;\n }\n\n const world = mesh.matrixWorld.elements;\n const corner = [0, 0, 0, 0, 0, 0, 0, 0, 0];\n const emit = (matrix: Elements, first: number, count: number, indexed: boolean) => {\n for (let k = first; k + 2 < first + count; k += 3) {\n for (let c = 0; c < 3; c += 1) {\n const v = indexed ? index!.getX(k + c) : k + c;\n const x = position.getX(v);\n const y = position.getY(v);\n const z = position.getZ(v);\n\n for (let axis = 0; axis < 3; axis += 1) {\n corner[c * 3 + axis] =\n matrix[axis]! * x + matrix[axis + 4]! * y + matrix[axis + 8]! * z + matrix[axis + 12]!;\n }\n }\n\n visit(\n ...(corner as [number, number, number, number, number, number, number, number, number]),\n );\n }\n };\n\n if (isBatched(mesh)) {\n const batch = mesh as BatchedMesh;\n const instance = Array.from({ length: 16 }, () => 0);\n const matrix = Array.from({ length: 16 }, () => 0);\n // `getMatrixAt` fills whatever has three's `fromArray`, so no Matrix4 is needed.\n const target = {\n fromArray(array: ArrayLike<number>, offset = 0) {\n for (let i = 0; i < 16; i += 1) {\n instance[i] = array[offset + i]!;\n }\n\n return target;\n },\n };\n\n for (let id = 0; id < batch.maxInstanceCount; id += 1) {\n let range: {\n vertexStart: number;\n vertexCount: number;\n indexStart: number;\n indexCount: number;\n };\n\n try {\n if (!batch.getVisibleAt(id)) {\n continue;\n }\n\n range = batch.getGeometryRangeAt(batch.getGeometryIdAt(id)) as typeof range;\n batch.getMatrixAt(id, target as never);\n } catch {\n continue;\n }\n\n multiply(world, instance, matrix);\n\n if (index) {\n emit(matrix, range.indexStart, range.indexCount, true);\n } else {\n emit(matrix, range.vertexStart, range.vertexCount, false);\n }\n }\n\n return;\n }\n\n const { start, count } = mesh.geometry.drawRange;\n const elements = index?.count ?? position.count;\n const first = Math.min(start, elements);\n const drawn = Math.min(elements, start + count) - first;\n\n if (isInstanced(mesh)) {\n const matrix = Array.from({ length: 16 }, () => 0);\n const instances = mesh.instanceMatrix.array;\n\n for (let i = 0; i < mesh.count; i += 1) {\n multiply(world, Array.prototype.slice.call(instances, i * 16, i * 16 + 16), matrix);\n emit(matrix, first, drawn, index !== null);\n }\n\n return;\n }\n\n emit(world, first, drawn, index !== null);\n};\n\n/** A box as [minX, minY, minZ, maxX, maxY, maxZ]. */\ntype Box = [number, number, number, number, number, number];\n\nconst emptyBox = (): Box => [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];\n\nconst grow = (box: Box, x: number, y: number, z: number) => {\n box[0] = Math.min(box[0], x);\n box[1] = Math.min(box[1], y);\n box[2] = Math.min(box[2], z);\n box[3] = Math.max(box[3], x);\n box[4] = Math.max(box[4], y);\n box[5] = Math.max(box[5], z);\n};\n\n/** Per axis, how far apart two boxes are (0 where they overlap). */\nconst apart = (\n a: ArrayLike<number>,\n ai: number,\n b: ArrayLike<number>,\n bi: number,\n out: number[],\n) => {\n for (let axis = 0; axis < 3; axis += 1) {\n out[axis] = Math.max(0, a[ai + axis]! - b[bi + axis + 3]!, b[bi + axis]! - a[ai + axis + 3]!);\n }\n\n return Math.hypot(out[0]!, out[1]!, out[2]!);\n};\n\nconst round = (value: number) => {\n const rounded = Math.round(value * 1000) / 1000;\n\n return rounded === 0 ? 0 : rounded;\n};\n\n/** The visible meshes a target stands for. */\nconst meshesOf = (root: Object3D, target: Target, tagKey: string, role: string) => {\n let objects: Object3D[];\n\n if (typeof target === \"string\") {\n const tagged: Object3D[] = [];\n const named: Object3D[] = [];\n\n walk(root, {}, (object) => {\n const tag: unknown = object.userData[tagKey];\n\n if (tag !== undefined && tag !== null && String(tag) === target) {\n tagged.push(object);\n } else if (object.name === target) {\n named.push(object);\n }\n });\n objects = tagged.length > 0 ? tagged : named;\n\n if (objects.length === 0) {\n throw new Error(\n `three-audit: ${role}: nothing is tagged userData.${tagKey} = \"${target}\" or named \"${target}\"`,\n );\n }\n } else {\n objects = [asObject3D(target)];\n }\n\n const meshes = new Set<Mesh>();\n\n for (const object of objects) {\n walk(object, { visibleOnly: true }, (child) => {\n if (isMesh(child)) {\n meshes.add(child);\n }\n });\n }\n\n return meshes;\n};\n\n/** A target's world envelope, from its visible meshes' vertices; `null` when it draws nothing. */\nexport const measureBounds = (\n root: AnyObject3D,\n target: Target,\n options: MeasureOptions = {},\n): Bounds | null => {\n const scene = asObject3D(root);\n\n scene.updateWorldMatrix(true, true);\n\n const box = emptyBox();\n\n for (const mesh of meshesOf(scene, target, options.tagKey ?? \"studioObject\", \"bounds\")) {\n eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n grow(box, ax, ay, az);\n grow(box, bx, by, bz);\n grow(box, cx, cy, cz);\n });\n }\n\n if (box[0] > box[3]) {\n return null;\n }\n\n const min = [box[0], box[1], box[2]].map(round) as Bounds[\"min\"];\n const max = [box[3], box[4], box[5]].map(round) as Bounds[\"max\"];\n\n return {\n min,\n max,\n size: [0, 1, 2].map((axis) => round(box[axis + 3]! - box[axis]!)) as Bounds[\"size\"],\n centre: [0, 1, 2].map((axis) => round((box[axis]! + box[axis + 3]!) / 2)) as Bounds[\"centre\"],\n };\n};\n\n/** Triangle boxes past this many pairs, a mesh pair is measured by its whole boxes instead. */\nconst PAIR_LIMIT = 4_000_000;\n\ntype Measured = { mesh: Mesh; box: Box; triangles: Float64Array | null };\n\n/**\n * The gap between two targets, and which of their meshes come closest. Throws when a target\n * matches nothing; `null` when either draws nothing. Meshes in both targets are left out of `a`.\n */\nexport const measureClearance = (\n root: AnyObject3D,\n a: Target,\n b: Target,\n options: MeasureOptions = {},\n): Clearance | null => {\n const scene = asObject3D(root);\n const tagKey = options.tagKey ?? \"studioObject\";\n\n scene.updateWorldMatrix(true, true);\n\n const inB = meshesOf(scene, b, tagKey, \"clearance b\");\n const inA = [...meshesOf(scene, a, tagKey, \"clearance a\")].filter((mesh) => !inB.has(mesh));\n const measure = (mesh: Mesh): Measured => {\n const box = emptyBox();\n\n eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n grow(box, ax, ay, az);\n grow(box, bx, by, bz);\n grow(box, cx, cy, cz);\n });\n\n return { mesh, box, triangles: null };\n };\n const sideA = inA.map(measure).filter(({ box }) => box[0] <= box[3]);\n const sideB = [...inB].map(measure).filter(({ box }) => box[0] <= box[3]);\n\n if (sideA.length === 0 || sideB.length === 0) {\n return null;\n }\n\n const trianglesOf = (side: Measured) => {\n if (!side.triangles) {\n const boxes: number[] = [];\n\n eachTriangle(side.mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n boxes.push(\n Math.min(ax, bx, cx),\n Math.min(ay, by, cy),\n Math.min(az, bz, cz),\n Math.max(ax, bx, cx),\n Math.max(ay, by, cy),\n Math.max(az, bz, cz),\n );\n });\n side.triangles = Float64Array.from(boxes);\n }\n\n return side.triangles;\n };\n\n const axes = [0, 0, 0];\n const pairs = sideA\n .flatMap((left) =>\n sideB.map((right) => ({ left, right, distance: apart(left.box, 0, right.box, 0, axes) })),\n )\n .sort((x, y) => x.distance - y.distance);\n let best = Infinity;\n let bestAxes = [0, 0, 0];\n let between: [Mesh, Mesh] = [pairs[0]!.left.mesh, pairs[0]!.right.mesh];\n\n for (const { left, right, distance } of pairs) {\n if (distance >= best) {\n break;\n }\n\n const leftTriangles = trianglesOf(left);\n const rightTriangles = trianglesOf(right);\n // Only triangles that could beat the best so far, measured against the other mesh's box.\n const near = (triangles: Float64Array, other: Box) => {\n const kept: number[] = [];\n\n for (let t = 0; t < triangles.length; t += 6) {\n if (apart(triangles, t, other, 0, axes) < best) {\n kept.push(t);\n }\n }\n\n return kept;\n };\n const nearLeft = near(leftTriangles, right.box);\n const nearRight = near(rightTriangles, left.box);\n\n if (nearLeft.length * nearRight.length > PAIR_LIMIT) {\n if (distance < best) {\n best = distance;\n apart(left.box, 0, right.box, 0, axes);\n bestAxes = [...axes];\n between = [left.mesh, right.mesh];\n }\n\n continue;\n }\n\n for (const i of nearLeft) {\n for (const j of nearRight) {\n const gap = apart(leftTriangles, i, rightTriangles, j, axes);\n\n if (gap < best) {\n best = gap;\n bestAxes = [...axes];\n between = [left.mesh, right.mesh];\n }\n }\n }\n }\n\n const separated = bestAxes.flatMap((value, axis) => (value > 0 ? [axis] : []));\n\n return {\n gap: round(best),\n axis: separated.length === 1 ? ([\"x\", \"y\", \"z\"] as const)[separated[0]!]! : null,\n between: [meshLabel(between[0], scene, tagKey), meshLabel(between[1], scene, tagKey)],\n };\n};\n","/**\n * The checks as console globals, for a dev page or a headless browser's `eval`: `summary()`,\n * `tris()`, `meshes()`, `census()`, `audit()`, `zfight()`, `report()`, `bbox()`, `clearance()`,\n * and with a renderer `ledger()`, `beginLedger()` and `blackFrames()`. All of them also sit on\n * `threeAudit`, which is the only global with `globals: false`.\n */\nimport type { BatchedMesh } from \"three\";\n\nimport { auditScene, type AuditSummaryOptions, summarizeScene } from \"./audit-scene.ts\";\nimport { findBadGeometry } from \"./bad-geometry.ts\";\nimport { type ProbeRenderer, watchBlackFrames } from \"./black-frames.ts\";\nimport {\n beginDrawLedger,\n type DrawLedgerOptions,\n type LedgerRenderer,\n printDrawLedger,\n recordDrawLedger,\n type RecordDrawLedgerOptions,\n} from \"./draw-ledger.ts\";\nimport { FRAME_TIMEOUT, nextFrame } from \"./frames.ts\";\nimport { measureBounds, measureClearance, type Target } from \"./measure.ts\";\nimport type { AnyObject3D, Skip } from \"./scene.ts\";\nimport { type CensusOptions, countTriangles, geometryCensus, listMeshes } from \"./triangles.ts\";\nimport { findZFighting } from \"./z-fighting.ts\";\n\nexport type AuditHelpersOptions = {\n /** A `WebGLRenderer`, for `ledger()`, `beginLedger()` and `blackFrames()`. */\n renderer?: (LedgerRenderer & ProbeRenderer) | undefined;\n skip?: Skip | undefined;\n tagKey?: string;\n /** Names geometries inside a batch in `census()`: see `geometryCensus`'s `label`. */\n batchLabel?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;\n /**\n * Also put each helper on `globalThis` under its short name (`tris`, `report`…). Default true;\n * false leaves only `threeAudit`, for pages where short names collide with other tooling.\n */\n globals?: boolean;\n};\n\nconst NAMESPACE = \"threeAudit\";\n\nconst NAMES = [\n \"auditRoot\",\n \"summary\",\n \"tris\",\n \"meshes\",\n \"census\",\n \"audit\",\n \"zfight\",\n \"report\",\n \"bbox\",\n \"clearance\",\n \"ledger\",\n \"beginLedger\",\n \"blackFrames\",\n] as const;\n\nconst needsRenderer = (name: string) => () => {\n throw new Error(`three-audit: ${name}() needs installAuditHelpers(scene, { renderer })`);\n};\n\n/**\n * Put the helpers on `globalThis`, and on `globalThis.threeAudit`. Returns a function that\n * removes them.\n *\n * vanilla: installAuditHelpers(scene, { renderer })\n * R3F: const { scene, gl } = useThree();\n * useLayoutEffect(() => installAuditHelpers(scene, { renderer: gl }), [scene, gl]);\n */\nexport const installAuditHelpers = (root: AnyObject3D, options: AuditHelpersOptions = {}) => {\n const { renderer, skip, batchLabel } = options;\n const tagKey = options.tagKey ?? \"studioObject\";\n\n const helpers: Record<(typeof NAMES)[number], unknown> = {\n auditRoot: root,\n summary: (tag?: string, summaryOptions: Pick<AuditSummaryOptions, \"gap\" | \"zFighting\"> = {}) =>\n summarizeScene(root, {\n skip,\n tagKey,\n ...summaryOptions,\n ...(tag === undefined ? {} : { tag }),\n }),\n tris: () => countTriangles(root, { skip }),\n meshes: () => listMeshes(root, { skip }),\n census: (censusOptions: Pick<CensusOptions, \"budget\" | \"label\"> = {}) =>\n geometryCensus(root, { skip, label: batchLabel, ...censusOptions }),\n audit: () => findBadGeometry(root, { skip, tagKey }),\n zfight: (gap?: number, self?: boolean) =>\n findZFighting(root, {\n skip,\n tagKey,\n ...(gap === undefined ? {} : { gap }),\n ...(self === undefined ? {} : { self }),\n }),\n report: (tag?: string) =>\n auditScene(root, { skip, tagKey, ...(tag === undefined ? {} : { tag }) }),\n bbox: (target: Target) => measureBounds(root, target, { tagKey }),\n clearance: (a: Target, b: Target) => measureClearance(root, a, b, { tagKey }),\n ledger: renderer\n ? async (ledgerOptions?: RecordDrawLedgerOptions) => {\n const ledger = await recordDrawLedger(renderer, ledgerOptions);\n\n printDrawLedger(ledger);\n\n return ledger;\n }\n : needsRenderer(\"ledger\"),\n beginLedger: renderer\n ? (ledgerOptions?: DrawLedgerOptions) => {\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n return {\n end: () => {\n const ledger = recording.end();\n\n printDrawLedger(ledger);\n\n return ledger;\n },\n };\n }\n : needsRenderer(\"beginLedger\"),\n blackFrames: renderer\n ? async (seconds = 10) => {\n await nextFrame(\n \"blackFrames()\",\n FRAME_TIMEOUT,\n \"Black frames only show in a visible tab: bring it to the front.\",\n );\n\n const watch = watchBlackFrames(renderer);\n\n return new Promise<{ frames: number; blackFrames: readonly number[] }>((resolve) => {\n setTimeout(() => {\n watch.stop();\n resolve({ frames: watch.frames, blackFrames: watch.blackFrames });\n }, seconds * 1000);\n });\n }\n : needsRenderer(\"blackFrames\"),\n };\n const namespace = { ...helpers };\n const scope = globalThis as Record<string, unknown>;\n\n if (options.globals !== false) {\n Object.assign(scope, helpers);\n }\n\n scope[NAMESPACE] = namespace;\n\n return () => {\n for (const name of NAMES) {\n if (scope[name] === helpers[name]) {\n delete scope[name];\n }\n }\n\n if (scope[NAMESPACE] === namespace) {\n delete scope[NAMESPACE];\n }\n };\n};\n"],"mappings":";;;AACA,MAAa,SACX,QACA,KACA,UACG;CACH,MAAM,MAAM,OAAO,OAAO,QAAQ,GAAG;CACrC,MAAM,WAAW,OAAO;CAExB,OAAO,OAAO;CAEd,aAAa;EACX,IAAI,KACF,OAAO,OAAO;OAEd,OAAO,OAAO;CAElB;AACF;;;;;;;;;;;;;;;AC0BA,MAAa,oBACX,UACA,UAA6B,CAAC,MACV;CACpB,MAAM,UAAU,SAAS,WAAW;CAEpC,IAAI,OAAO,SAAS,eAAe,YACjC,MAAM,IAAI,UAAU,0DAA0D;CAGhF,IAAI,OAAO,0BAA0B,YACnC,MAAM,IAAI,UAAU,gEAAgE;CAGtF,MAAM,KAAK;CACX,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,wBAAQ,IAAI,WAAW,CAAC;CAC9B,MAAM,SAAS,SAAS;CACxB,IAAI,SAAS;CACb,IAAI,QAAkB,CAAC;CACvB,IAAI,UAA0B;CAE9B,MAAM,gBAAgB;EACpB,IAAI,QAAQ;EAEZ,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,KAAK,GAC9B,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,KAAK,GAAG;GACjC,MAAM,IAAI,KAAK,MAAO,GAAG,qBAAqB,KAAM,OAAO,EAAE;GAC7D,MAAM,IAAI,KAAK,MAAO,GAAG,sBAAsB,KAAM,OAAO,EAAE;GAE9D,GAAG,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,eAAe,KAAK;GAE1D,IAAI,MAAM,KAAM,KAAK,MAAM,KAAM,KAAK,MAAM,KAAM,KAAK,MAAM,KAAM,KACjE,SAAS;EAEb;EAGF,OAAO,SAAS;CAClB;CAGA,IAAI,MAAM;CACV,MAAM,eAAe;EACnB,IAAI,YAAY,MAAM;GACpB,UAAU;GAEV,IAAI,SACF,MAAM,KAAK,MAAM;GAGnB,UAAU;EACZ;EAEA,MAAM,sBAAsB,MAAM;CACpC;CAEA,MAAM,sBAAsB,MAAM;CAElC,MAAM,UAAU,MAAM,UAAU,UAAU,SAAyB,OAAO,QAAQ;EAChF,OAAO,KAAK,MAAM,OAAO,MAAM;EAG/B,IAAI,SAAS,gBAAgB,MAAM,MACjC,UAAU,QAAQ;CAEtB,CAAC;CAED,OAAO;EACL,IAAI,SAAS;GACX,OAAO;EACT;EACA,IAAI,cAAc;GAChB,OAAO;EACT;EACA,aAAa;GACX,SAAS;GACT,QAAQ,CAAC;GACT,UAAU;EACZ;EACA,YAAY;GACV,qBAAqB,GAAG;GACxB,QAAQ;EACV;CACF;AACF;;;;;;;;AC5HA,MAAa,gBAAgB;AAE7B,MAAM,WAAW,QAAgB,SAAiB,SAAiB;CACjE,MAAM,SAAS,OAAO,aAAa,eAAe,SAAS;CAE3D,uBAAO,IAAI,MACT,gBAAgB,OAAO,6BAA6B,UAAU,IAAK,IACjE,SACI,wFACA,GACL,IAAI,MACP;AACF;;AAGA,MAAa,aAAa,QAAgB,SAAiB,SACzD,IAAI,SAAe,SAAS,WAAW;CACrC,IAAI,OAAO,0BAA0B,YAAY;EAC/C,uBAAO,IAAI,MAAM,gBAAgB,OAAO,6BAA6B,CAAC;EAEtE;CACF;CAEA,IAAI,QAAQ;CACZ,MAAM,QAAQ,iBAAiB;EAC7B,qBAAqB,KAAK;EAC1B,OAAO,QAAQ,QAAQ,SAAS,IAAI,CAAC;CACvC,GAAG,OAAO;CAEV,QAAQ,4BAA4B;EAClC,aAAa,KAAK;EAClB,QAAQ;CACV,CAAC;AACH,CAAC;;;AC8DH,MAAM,kBAAkB,SAAiB,KAAK,MAAM,MAAM,CAAC,CAAC,MAAM;;AAGlE,MAAM,UAAU,QAAkB,aAChC,OAAO,QACP,GAAG,CAAC,GAAG,OAAO,QAAQ,MAAM,KAAA,GAAW,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,GAAG,EAAE,IAAI,SAAS,QAAQ,SAAS,KAAK;AAEvG,MAAM,SAAS,MAA8B,MAAc,SAAiB;CAC1E,MAAM,MAAM,KAAK,IAAI,IAAI,KAAK;EAAE;EAAM,OAAO;EAAG,QAAQ,CAAC;CAAE;CAE3D,IAAI,SAAS;CACb,IAAI,OAAO,SAAS,IAAI,OAAO,SAAS,KAAK;CAC7C,KAAK,IAAI,MAAM,GAAG;AACpB;AAEA,MAAM,UAAU,SACd,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;;AAGrD,MAAM,iBAAiB,UAA0B,QAAkB,UAAmB;CACpF,MAAM,OAAO;CAEb,IAAI,KAAK,WAAW,MAClB,OAAO;CAGT,IAAI,UAAU,IAAI,GAChB,OAAO,iBAAiB,IAAI,CAAC,CAAC;CAGhC,MAAM,QAAQ;CACd,MAAM,WAAW,SAAS,OAAO,SAAS,SAAS,WAAW,UAAU,SAAS;CAMjF,QAJE,SAAS,OAAO,MAAM,UAAU,WAC5B,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,OAAO,YAAY,MAAM,SAAS,EAAE,CAAC,IAAI,CAAC,IAChF,cAAc,QAAQ,MAEd,YAAY,IAAI,IAAI,KAAK,QAAQ;AACjD;;AAqBA,MAAa,mBACX,UACA,UAA6B,CAAC,MACN;CACxB,IAAI,OAAO,SAAS,uBAAuB,YACzC,MAAM,IAAI,UAAU,oDAAoD;CAG1E,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,SAAS;CACxB,MAAM,qBAAqB,SAAS;CACpC,MAAM,SAAmB,CAAC;CAC1B,MAAM,0BAAU,IAAI,IAAuB;CAC3C,MAAM,yBAAS,IAAI,IAAuB;CAC1C,MAAM,wBAAQ,IAAI,IAAuB;CACzC,MAAM,QAAgB,CAAC;CACvB,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,QAAQ;CACZ,IAAI,mBAAmB;CACvB,IAAI,mBAAmB;CAEvB,MAAM,UAAU,SAAiB;EAC/B,IAAI,OAAO,MAAM,IAAI,IAAI;EAEzB,IAAI,CAAC,MAAM;GACT,OAAO;IACL,MAAM;IACN,OAAO;IACP,WAAW;IACX,yBAAS,IAAI,IAAI;IACjB,yBAAS,IAAI,IAAI;IACjB,OAAO;IACP,iBAAiB;GACnB;GACA,MAAM,IAAI,MAAM,IAAI;EACtB;EAEA,OAAO;CACT;CAEA,MAAM,gBAA6B;EACjC,MAAM,SAAS,SAAS,kBAAkB;EAE1C,IAAI,UAAU,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,WAAW,UACzE,OAAO;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;GAAQ,QAAQ;EAAM;EAGrE,MAAM,KAAK,SAAS,aAAa;EAKjC,OAAO,WAAW,QAAQ,OAAO,IAAI,uBAAuB,WACxD;GAAE,OAAO,GAAG;GAAoB,QAAQ,GAAG,uBAAuB;GAAG,QAAQ;EAAK,IAClF;CACN;CAEA,MAAM,gBAAgB,MAAM,UAAU,UAAU,SAAyB,OAAO,QAAQ;EACtF,MAAM,QAAQ;EAEd,WAAW;EACX,UAAW,MAA4B,QAAQ,UAAU;EAEzD,MAAM,OAAa;GACjB,MAAM;GACN,OAAO;GACP,cAAe,OAA8C,yBAAyB;GACtF,UAAU;GACV,MAAM;EACR;EAEA,MAAM,KAAK,IAAI;EAEf,IAAI;GACF,OAAO,KAAK,MAAM,OAAO,MAAM;EACjC,UAAU;GACR,MAAM,IAAI;GACV,UAAU;GAEV,MAAM,OAAO,MAAM,IAAI,KAAK,IAAI;GAEhC,IAAI,MAAM;IACR,KAAK,SAAS;IAGd,IAAI,KAAK,UAAU,KAAK,KAAK,gBAAgB,KAAK,YAAY,GAAG;KAC/D,KAAK,mBAAmB;KACxB,oBAAoB;KACpB,oBAAoB,KAAK,OAAO,KAAK,KAAK,QAAQ,KAAK,KAAK,SAAS;IACvE;GACF;EACF;CACF,CAAC;CAED,MAAM,cAAc,MAClB,UACA,sBACA,SAAyB,QAAQ,OAAO,UAAU,UAAU,QAAQ,OAAO;EACzE,MAAM,SACH,SAA+C,wBAAwB,QACvE,SAAkD,2BAA2B;EAChF,MAAM,OAAO,SAAS,WAAW;EACjC,MAAM,OAAO,OAAO,QAA+B,QAAoB;EAEvE,IAAI,CAAC,OAAO,SAAS,IAAI,GACvB,OAAO,KAAK,IAAI;EAGlB,MAAM,OAAO,OAAO,IAAI;EACxB,MAAM,OAAO,QAAQ;EACrB,MAAM,QAAQ;EAEd,KAAK,SAAS;EACd,KAAK,aAAa,cAAc,OAAO,QAA+B,KAAK;EAC3E,KAAK,QAAQ,IAAI,MAA6B;EAE9C,IAAI,MACF,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,YAAY,KAAK,KAAK,MAAM,GAAG,KAAK,QAAQ;EAGhF,MAAM,OAAO,MAAM,GAAG,EAAE;EAExB,IAAI,QAAQ,CAAC,QAAQ;GACnB,KAAK,SAAS;GACd,KAAK,WAAW,MAAM,YAAY,UAAU,SAAS;GACrD,KAAK,OAAO;EACd;EAEA,SAAS;EACT,MAAM,SAAS,MAAM,IAAI;EACzB,MAAM,QAAQ,QAAQ,IAAI,GAAG,IAAI;EACjC,mBAAmB,KAAK,MAAM,QAAQ,OAAO,UAAU,UAAU,QAAQ,KAAK;CAChF,CACF;CAEA,OAAO,EACL,WAAW;EACT,YAAY;EACZ,cAAc;EAEd,OAAO;GACL;GACA,QAAQ,OAAO,MAAM;GACrB,SAAS,OAAO,OAAO;GACvB;GACA,WAAW,OAAO,KAAK,SAAS;IAC9B,MAAM,OAAO,MAAM,IAAI,IAAI;IAE3B,OAAO;KACL,MAAM;KACN,OAAO,KAAK;KACZ,SAAS,KAAK,QAAQ;KACtB,WAAW,KAAK;KAChB,SAAS,CAAC,GAAG,KAAK,OAAO;KACzB,YAAY,KAAK,QAAQ,KAAK,KAAK,oBAAoB,KAAK;IAC9D;GACF,CAAC;GACD;GACA;EACF;CACF,EACF;AACF;;;;;;;;;AAUA,MAAa,mBAAmB,OAC9B,UACA,UAAmC,CAAC,MACZ;CACxB,MAAM,EAAE,QAAQ,UAAU,eAAe,GAAG,kBAAkB;CAE9D,IAAI,QAAQ;EACV,MAAM,YAAY,gBAAgB,UAAU,aAAa;EAEzD,IAAI;GACF,MAAM,OAAO;EACf,SAAS,OAAO;GACd,UAAU,IAAI;GACd,MAAM;EACR;EAEA,OAAO,UAAU,IAAI;CACvB;CAEA,MAAM,OAAO;CAEb,MAAM,UAAU,oBAAoB,SAAS,IAAI;CAEjD,MAAM,YAAY,gBAAgB,UAAU,aAAa;CAEzD,IAAI;EACF,MAAM,UAAU,oBAAoB,SAAS,IAAI;CACnD,SAAS,OAAO;EACd,UAAU,IAAI;EACd,MAAM;CACR;CAEA,OAAO,UAAU,IAAI;AACvB;;AAGA,MAAa,eAAe,MAAmB,WAC7C,KAAK,KAAK,SAAS;CACjB,MAAM,IAAI;CACV,GAAG,OAAO,YAAY,OAAO,KAAK,SAAS,CAAC,MAAM,IAAI,OAAO,SAAS,CAAC,CAAC,CAAC;CACzE,OAAO,IAAI;AACb,EAAE;;AAGJ,MAAa,mBAAmB,WAAuB;CAErD,QAAQ,MAAM,gBAAgB,OAAO,MAAM,oBAAoB;CAC/D,QAAQ,MAAM,YAAY,OAAO,QAAQ,OAAO,MAAM,CAAC;CACvD,QAAQ,MAAM,OAAO,UAAU,KAAK,UAAU;EAAE,GAAG;EAAM,SAAS,KAAK,QAAQ,KAAK,IAAI;CAAE,EAAE,CAAC;CAE7F,IAAI,OAAO,mBAAmB,GAC5B,QAAQ,IACN,GAAG,OAAO,iBAAiB,uBAAuB,OAAO,mBAAmB,IAAA,CAAK,QAAQ,CAAC,EAAE,YAC9F;CAGF,QAAQ,eAAe,cAAc;CACrC,QAAQ,MAAM,YAAY,OAAO,SAAS,OAAO,MAAM,CAAC;CACxD,QAAQ,SAAS;CACjB,QAAQ,SAAS;AAEnB;;;ACvVA,MAAM,YAAY,GAAa,GAAa,QAAkB;CAC5D,KAAK,IAAI,MAAM,GAAG,MAAM,GAAG,OAAO,GAChC,KAAK,IAAI,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG;EAC5C,IAAI,MAAM;EAEV,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,GAC1B,OAAO,EAAE,IAAI,IAAI,OAAQ,EAAE,SAAS,IAAI;EAG1C,IAAI,SAAS,IAAI,OAAO;CAC1B;CAGF,OAAO;AACT;;AAeA,MAAM,gBAAgB,MAAY,UAAoB;CACpD,MAAM,EAAE,OAAO,eAAe,KAAK;CACnC,MAAM,WAAW,WAAW;CAE5B,IAAI,CAAC,UACH;CAGF,MAAM,QAAQ,KAAK,YAAY;CAC/B,MAAM,SAAS;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC;CACzC,MAAM,QAAQ,QAAkB,OAAe,OAAe,YAAqB;EACjF,KAAK,IAAI,IAAI,OAAO,IAAI,IAAI,QAAQ,OAAO,KAAK,GAAG;GACjD,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;IAC7B,MAAM,IAAI,UAAU,MAAO,KAAK,IAAI,CAAC,IAAI,IAAI;IAC7C,MAAM,IAAI,SAAS,KAAK,CAAC;IACzB,MAAM,IAAI,SAAS,KAAK,CAAC;IACzB,MAAM,IAAI,SAAS,KAAK,CAAC;IAEzB,KAAK,IAAI,OAAO,GAAG,OAAO,GAAG,QAAQ,GACnC,OAAO,IAAI,IAAI,QACb,OAAO,QAAS,IAAI,OAAO,OAAO,KAAM,IAAI,OAAO,OAAO,KAAM,IAAI,OAAO,OAAO;GAExF;GAEA,MACE,GAAI,MACN;EACF;CACF;CAEA,IAAI,UAAU,IAAI,GAAG;EACnB,MAAM,QAAQ;EACd,MAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;EACnD,MAAM,SAAS,MAAM,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;EAEjD,MAAM,SAAS,EACb,UAAU,OAA0B,SAAS,GAAG;GAC9C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,GAC3B,SAAS,KAAK,MAAM,SAAS;GAG/B,OAAO;EACT,EACF;EAEA,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,kBAAkB,MAAM,GAAG;GACrD,IAAI;GAOJ,IAAI;IACF,IAAI,CAAC,MAAM,aAAa,EAAE,GACxB;IAGF,QAAQ,MAAM,mBAAmB,MAAM,gBAAgB,EAAE,CAAC;IAC1D,MAAM,YAAY,IAAI,MAAe;GACvC,QAAQ;IACN;GACF;GAEA,SAAS,OAAO,UAAU,MAAM;GAEhC,IAAI,OACF,KAAK,QAAQ,MAAM,YAAY,MAAM,YAAY,IAAI;QAErD,KAAK,QAAQ,MAAM,aAAa,MAAM,aAAa,KAAK;EAE5D;EAEA;CACF;CAEA,MAAM,EAAE,OAAO,UAAU,KAAK,SAAS;CACvC,MAAM,WAAW,OAAO,SAAS,SAAS;CAC1C,MAAM,QAAQ,KAAK,IAAI,OAAO,QAAQ;CACtC,MAAM,QAAQ,KAAK,IAAI,UAAU,QAAQ,KAAK,IAAI;CAElD,IAAI,YAAY,IAAI,GAAG;EACrB,MAAM,SAAS,MAAM,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;EACjD,MAAM,YAAY,KAAK,eAAe;EAEtC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,KAAK,GAAG;GACtC,SAAS,OAAO,MAAM,UAAU,MAAM,KAAK,WAAW,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,MAAM;GAClF,KAAK,QAAQ,OAAO,OAAO,UAAU,IAAI;EAC3C;EAEA;CACF;CAEA,KAAK,OAAO,OAAO,OAAO,UAAU,IAAI;AAC1C;AAKA,MAAM,iBAAsB;CAAC;CAAU;CAAU;CAAU;CAAW;CAAW;AAAS;AAE1F,MAAM,QAAQ,KAAU,GAAW,GAAW,MAAc;CAC1D,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;CAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;CAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;CAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;CAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;CAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC;AAC7B;;AAGA,MAAM,SACJ,GACA,IACA,GACA,IACA,QACG;CACH,KAAK,IAAI,OAAO,GAAG,OAAO,GAAG,QAAQ,GACnC,IAAI,QAAQ,KAAK,IAAI,GAAG,EAAE,KAAK,QAAS,EAAE,KAAK,OAAO,IAAK,EAAE,KAAK,QAAS,EAAE,KAAK,OAAO,EAAG;CAG9F,OAAO,KAAK,MAAM,IAAI,IAAK,IAAI,IAAK,IAAI,EAAG;AAC7C;AAEA,MAAM,SAAS,UAAkB;CAC/B,MAAM,UAAU,KAAK,MAAM,QAAQ,GAAI,IAAI;CAE3C,OAAO,YAAY,IAAI,IAAI;AAC7B;;AAGA,MAAM,YAAY,MAAgB,QAAgB,QAAgB,SAAiB;CACjF,IAAI;CAEJ,IAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,SAAqB,CAAC;EAC5B,MAAM,QAAoB,CAAC;EAE3B,KAAK,MAAM,CAAC,IAAI,WAAW;GACzB,MAAM,MAAe,OAAO,SAAS;GAErC,IAAI,QAAQ,KAAA,KAAa,QAAQ,QAAQ,OAAO,GAAG,MAAM,QACvD,OAAO,KAAK,MAAM;QACb,IAAI,OAAO,SAAS,QACzB,MAAM,KAAK,MAAM;EAErB,CAAC;EACD,UAAU,OAAO,SAAS,IAAI,SAAS;EAEvC,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MACR,gBAAgB,KAAK,+BAA+B,OAAO,MAAM,OAAO,cAAc,OAAO,EAC/F;CAEJ,OACE,UAAU,CAAC,WAAW,MAAM,CAAC;CAG/B,MAAM,yBAAS,IAAI,IAAU;CAE7B,KAAK,MAAM,UAAU,SACnB,KAAK,QAAQ,EAAE,aAAa,KAAK,IAAI,UAAU;EAC7C,IAAI,OAAO,KAAK,GACd,OAAO,IAAI,KAAK;CAEpB,CAAC;CAGH,OAAO;AACT;;AAGA,MAAa,iBACX,MACA,QACA,UAA0B,CAAC,MACT;CAClB,MAAM,QAAQ,WAAW,IAAI;CAE7B,MAAM,kBAAkB,MAAM,IAAI;CAElC,MAAM,MAAM,SAAS;CAErB,KAAK,MAAM,QAAQ,SAAS,OAAO,QAAQ,QAAQ,UAAU,gBAAgB,QAAQ,GACnF,aAAa,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO;EACzD,KAAK,KAAK,IAAI,IAAI,EAAE;EACpB,KAAK,KAAK,IAAI,IAAI,EAAE;EACpB,KAAK,KAAK,IAAI,IAAI,EAAE;CACtB,CAAC;CAGH,IAAI,IAAI,KAAK,IAAI,IACf,OAAO;CAMT,OAAO;EACL,KAJU;GAAC,IAAI;GAAI,IAAI;GAAI,IAAI;EAAE,CAAC,CAAC,IAAI,KAIrC;EACF,KAJU;GAAC,IAAI;GAAI,IAAI;GAAI,IAAI;EAAE,CAAC,CAAC,IAAI,KAIrC;EACF,MAAM;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC,KAAK,SAAS,MAAM,IAAI,OAAO,KAAM,IAAI,KAAM,CAAC;EAChE,QAAQ;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC,KAAK,SAAS,OAAO,IAAI,QAAS,IAAI,OAAO,MAAO,CAAC,CAAC;CAC1E;AACF;;AAGA,MAAM,aAAa;;;;;AAQnB,MAAa,oBACX,MACA,GACA,GACA,UAA0B,CAAC,MACN;CACrB,MAAM,QAAQ,WAAW,IAAI;CAC7B,MAAM,SAAS,QAAQ,UAAU;CAEjC,MAAM,kBAAkB,MAAM,IAAI;CAElC,MAAM,MAAM,SAAS,OAAO,GAAG,QAAQ,aAAa;CACpD,MAAM,MAAM,CAAC,GAAG,SAAS,OAAO,GAAG,QAAQ,aAAa,CAAC,CAAC,CAAC,QAAQ,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC;CAC1F,MAAM,WAAW,SAAyB;EACxC,MAAM,MAAM,SAAS;EAErB,aAAa,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO;GACzD,KAAK,KAAK,IAAI,IAAI,EAAE;GACpB,KAAK,KAAK,IAAI,IAAI,EAAE;GACpB,KAAK,KAAK,IAAI,IAAI,EAAE;EACtB,CAAC;EAED,OAAO;GAAE;GAAM;GAAK,WAAW;EAAK;CACtC;CACA,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,UAAU,IAAI,MAAM,IAAI,EAAE;CACnE,MAAM,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,UAAU,IAAI,MAAM,IAAI,EAAE;CAExE,IAAI,MAAM,WAAW,KAAK,MAAM,WAAW,GACzC,OAAO;CAGT,MAAM,eAAe,SAAmB;EACtC,IAAI,CAAC,KAAK,WAAW;GACnB,MAAM,QAAkB,CAAC;GAEzB,aAAa,KAAK,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO;IAC9D,MAAM,KACJ,KAAK,IAAI,IAAI,IAAI,EAAE,GACnB,KAAK,IAAI,IAAI,IAAI,EAAE,GACnB,KAAK,IAAI,IAAI,IAAI,EAAE,GACnB,KAAK,IAAI,IAAI,IAAI,EAAE,GACnB,KAAK,IAAI,IAAI,IAAI,EAAE,GACnB,KAAK,IAAI,IAAI,IAAI,EAAE,CACrB;GACF,CAAC;GACD,KAAK,YAAY,aAAa,KAAK,KAAK;EAC1C;EAEA,OAAO,KAAK;CACd;CAEA,MAAM,OAAO;EAAC;EAAG;EAAG;CAAC;CACrB,MAAM,QAAQ,MACX,SAAS,SACR,MAAM,KAAK,WAAW;EAAE;EAAM;EAAO,UAAU,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK,GAAG,IAAI;CAAE,EAAE,CAC1F,CAAC,CACA,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CACzC,IAAI,OAAO;CACX,IAAI,WAAW;EAAC;EAAG;EAAG;CAAC;CACvB,IAAI,UAAwB,CAAC,MAAM,EAAE,CAAE,KAAK,MAAM,MAAM,EAAE,CAAE,MAAM,IAAI;CAEtE,KAAK,MAAM,EAAE,MAAM,OAAO,cAAc,OAAO;EAC7C,IAAI,YAAY,MACd;EAGF,MAAM,gBAAgB,YAAY,IAAI;EACtC,MAAM,iBAAiB,YAAY,KAAK;EAExC,MAAM,QAAQ,WAAyB,UAAe;GACpD,MAAM,OAAiB,CAAC;GAExB,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,GACzC,IAAI,MAAM,WAAW,GAAG,OAAO,GAAG,IAAI,IAAI,MACxC,KAAK,KAAK,CAAC;GAIf,OAAO;EACT;EACA,MAAM,WAAW,KAAK,eAAe,MAAM,GAAG;EAC9C,MAAM,YAAY,KAAK,gBAAgB,KAAK,GAAG;EAE/C,IAAI,SAAS,SAAS,UAAU,SAAS,YAAY;GACnD,IAAI,WAAW,MAAM;IACnB,OAAO;IACP,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK,GAAG,IAAI;IACrC,WAAW,CAAC,GAAG,IAAI;IACnB,UAAU,CAAC,KAAK,MAAM,MAAM,IAAI;GAClC;GAEA;EACF;EAEA,KAAK,MAAM,KAAK,UACd,KAAK,MAAM,KAAK,WAAW;GACzB,MAAM,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,IAAI;GAE3D,IAAI,MAAM,MAAM;IACd,OAAO;IACP,WAAW,CAAC,GAAG,IAAI;IACnB,UAAU,CAAC,KAAK,MAAM,MAAM,IAAI;GAClC;EACF;CAEJ;CAEA,MAAM,YAAY,SAAS,SAAS,OAAO,SAAU,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,CAAE;CAE7E,OAAO;EACL,KAAK,MAAM,IAAI;EACf,MAAM,UAAU,WAAW,IAAK;GAAC;GAAK;GAAK;EAAG,CAAC,CAAW,UAAU,MAAQ;EAC5E,SAAS,CAAC,UAAU,QAAQ,IAAI,OAAO,MAAM,GAAG,UAAU,QAAQ,IAAI,OAAO,MAAM,CAAC;CACtF;AACF;;;ACnXA,MAAM,YAAY;AAElB,MAAM,QAAQ;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,iBAAiB,eAAuB;CAC5C,MAAM,IAAI,MAAM,gBAAgB,KAAK,kDAAkD;AACzF;;;;;;;;;AAUA,MAAa,uBAAuB,MAAmB,UAA+B,CAAC,MAAM;CAC3F,MAAM,EAAE,UAAU,MAAM,eAAe;CACvC,MAAM,SAAS,QAAQ,UAAU;CAEjC,MAAM,UAAmD;EACvD,WAAW;EACX,UAAU,KAAc,iBAAiE,CAAC,MACxF,eAAe,MAAM;GACnB;GACA;GACA,GAAG;GACH,GAAI,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,IAAI;EACrC,CAAC;EACH,YAAY,eAAe,MAAM,EAAE,KAAK,CAAC;EACzC,cAAc,WAAW,MAAM,EAAE,KAAK,CAAC;EACvC,SAAS,gBAAyD,CAAC,MACjE,eAAe,MAAM;GAAE;GAAM,OAAO;GAAY,GAAG;EAAc,CAAC;EACpE,aAAa,gBAAgB,MAAM;GAAE;GAAM;EAAO,CAAC;EACnD,SAAS,KAAc,SACrB,cAAc,MAAM;GAClB;GACA;GACA,GAAI,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,IAAI;GACnC,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;EACvC,CAAC;EACH,SAAS,QACP,WAAW,MAAM;GAAE;GAAM;GAAQ,GAAI,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,IAAI;EAAG,CAAC;EAC1E,OAAO,WAAmB,cAAc,MAAM,QAAQ,EAAE,OAAO,CAAC;EAChE,YAAY,GAAW,MAAc,iBAAiB,MAAM,GAAG,GAAG,EAAE,OAAO,CAAC;EAC5E,QAAQ,WACJ,OAAO,kBAA4C;GACjD,MAAM,SAAS,MAAM,iBAAiB,UAAU,aAAa;GAE7D,gBAAgB,MAAM;GAEtB,OAAO;EACT,IACA,cAAc,QAAQ;EAC1B,aAAa,YACR,kBAAsC;GACrC,MAAM,YAAY,gBAAgB,UAAU,aAAa;GAEzD,OAAO,EACL,WAAW;IACT,MAAM,SAAS,UAAU,IAAI;IAE7B,gBAAgB,MAAM;IAEtB,OAAO;GACT,EACF;EACF,IACA,cAAc,aAAa;EAC/B,aAAa,WACT,OAAO,UAAU,OAAO;GACtB,MAAM,UACJ,iBACA,eACA,iEACF;GAEA,MAAM,QAAQ,iBAAiB,QAAQ;GAEvC,OAAO,IAAI,SAA6D,YAAY;IAClF,iBAAiB;KACf,MAAM,KAAK;KACX,QAAQ;MAAE,QAAQ,MAAM;MAAQ,aAAa,MAAM;KAAY,CAAC;IAClE,GAAG,UAAU,GAAI;GACnB,CAAC;EACH,IACA,cAAc,aAAa;CACjC;CACA,MAAM,YAAY,EAAE,GAAG,QAAQ;CAC/B,MAAM,QAAQ;CAEd,IAAI,QAAQ,YAAY,OACtB,OAAO,OAAO,OAAO,OAAO;CAG9B,MAAM,aAAa;CAEnB,aAAa;EACX,KAAK,MAAM,QAAQ,OACjB,IAAI,MAAM,UAAU,QAAQ,OAC1B,OAAO,MAAM;EAIjB,IAAI,MAAM,eAAe,WACvB,OAAO,MAAM;CAEjB;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/patch.ts","../src/black-frames.ts","../src/frames.ts","../src/draw-ledger.ts","../src/measure.ts","../src/console-helpers.ts"],"sourcesContent":["/** Swap `key` on `target` for `value`; the returned function puts back exactly what was there. */\nexport const patch = <Target extends object, Key extends keyof Target>(\n target: Target,\n key: Key,\n value: Target[Key],\n) => {\n const own = Object.hasOwn(target, key);\n const original = target[key];\n\n target[key] = value;\n\n return () => {\n if (own) {\n target[key] = original;\n } else {\n delete target[key];\n }\n };\n};\n","/**\n * Black-frame probe: after each frame, samples a grid of the drawing buffer and counts frames\n * where several samples are pure black. Catches the flash a NaN pixel makes once bloom spreads it,\n * which lasts one frame in twenty and never shows in a screenshot.\n *\n * WebGL only (`readPixels`). Each sample stalls the GPU pipeline, so watch, read, stop.\n */\nimport { patch } from \"./patch.ts\";\nimport type { AnyObject3D } from \"./scene.ts\";\n\n/** The `WebGLRenderer` methods the probe uses, typed loosely to fit any copy of three's types. */\nexport type ProbeRenderer = {\n render(scene: AnyObject3D, camera: object): void;\n getContext(): unknown;\n getRenderTarget(): unknown;\n};\n\nexport type BlackFrameOptions = {\n /** Samples per side: 3 reads a 3 × 3 grid at the quarter lines. */\n grid?: number;\n /** Black samples that make a frame black. */\n threshold?: number;\n};\n\nexport type BlackFrameWatch = {\n /** Frames sampled since the start or the last `reset()`. */\n readonly frames: number;\n /** Which of those frames (1-based) were black. */\n readonly blackFrames: readonly number[];\n reset: () => void;\n /** Restore the renderer and stop sampling. */\n stop: () => void;\n};\n\ntype ReadPixels = Pick<\n WebGLRenderingContext,\n \"RGBA\" | \"UNSIGNED_BYTE\" | \"drawingBufferHeight\" | \"drawingBufferWidth\" | \"readPixels\"\n>;\n\n/**\n * Sample every frame the app draws to the screen. Several screen renders in one frame (an overlay\n * redrawn after the composer) count once, as the last of them: the image the display shows.\n * Browser only: frames are told apart by `requestAnimationFrame`.\n */\nexport const watchBlackFrames = (\n renderer: ProbeRenderer,\n options: BlackFrameOptions = {},\n): BlackFrameWatch => {\n const context = renderer.getContext() as Partial<ReadPixels> | null;\n\n if (typeof context?.readPixels !== \"function\") {\n throw new TypeError(\"three-audit: the black-frame probe needs a WebGLRenderer\");\n }\n\n if (typeof requestAnimationFrame !== \"function\") {\n throw new TypeError(\"three-audit: the black-frame probe needs requestAnimationFrame\");\n }\n\n const gl = context as ReadPixels;\n const grid = options.grid ?? 3;\n const threshold = options.threshold ?? 3;\n const pixel = new Uint8Array(4);\n const render = renderer.render;\n let frames = 0;\n let black: number[] = [];\n let pending: boolean | null = null;\n\n const isBlack = () => {\n let count = 0;\n\n for (let i = 1; i <= grid; i += 1) {\n for (let j = 1; j <= grid; j += 1) {\n const x = Math.floor((gl.drawingBufferWidth * i) / (grid + 1));\n const y = Math.floor((gl.drawingBufferHeight * j) / (grid + 1));\n\n gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);\n\n if (pixel[0]! < 4 && pixel[1]! < 4 && pixel[2]! < 4 && pixel[3]! > 250) {\n count += 1;\n }\n }\n }\n\n return count >= threshold;\n };\n\n // Commit the frame's last screen sample once per animation frame.\n let raf = 0;\n const commit = () => {\n if (pending !== null) {\n frames += 1;\n\n if (pending) {\n black.push(frames);\n }\n\n pending = null;\n }\n\n raf = requestAnimationFrame(commit);\n };\n\n raf = requestAnimationFrame(commit);\n\n const restore = patch(renderer, \"render\", function (this: unknown, scene, camera) {\n render.call(this, scene, camera);\n\n // Read before the browser presents (and, without preserveDrawingBuffer, clears) the buffer.\n if (renderer.getRenderTarget() === null) {\n pending = isBlack();\n }\n });\n\n return {\n get frames() {\n return frames;\n },\n get blackFrames() {\n return black;\n },\n reset: () => {\n frames = 0;\n black = [];\n pending = null;\n },\n stop: () => {\n cancelAnimationFrame(raf);\n restore();\n },\n };\n};\n","/**\n * Waiting on animation frames without hanging: a tab in the background (an agent's browser, an\n * occluded window) gets none, so every wait gives up after a timeout and says why.\n */\n\n/** Frames an agent-driven tab should see within this long, in ms. */\nexport const FRAME_TIMEOUT = 2000;\n\nconst noFrame = (caller: string, timeout: number, hint: string) => {\n const hidden = typeof document !== \"undefined\" && document.hidden;\n\n return new Error(\n `three-audit: ${caller} got no animation frame in ${timeout / 1000} s${\n hidden\n ? \" (document.hidden is true: browsers pause requestAnimationFrame in background tabs)\"\n : \"\"\n }. ${hint}`,\n );\n};\n\n/** The next animation frame, or an error naming the cause when none comes within `timeout`. */\nexport const nextFrame = (caller: string, timeout: number, hint: string) =>\n new Promise<void>((resolve, reject) => {\n if (typeof requestAnimationFrame !== \"function\") {\n reject(new Error(`three-audit: ${caller} needs requestAnimationFrame`));\n\n return;\n }\n\n let frame = 0;\n const timer = setTimeout(() => {\n cancelAnimationFrame(frame);\n reject(noFrame(caller, timeout, hint));\n }, timeout);\n\n frame = requestAnimationFrame(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n","/**\n * A ledger of every draw call over one frame: which object drew, in which pass. Exact counts from\n * the renderer itself, where `renderer.info` gives one total (and resets it every `render()`).\n *\n * WebGL only: it wraps `WebGLRenderer.renderBufferDirect`, which every draw goes through, and\n * `render()`, to tell passes apart. Draws with a depth or distance material are the shadow maps.\n */\nimport type { BufferGeometry, Material, Mesh, Object3D } from \"three\";\n\nimport { FRAME_TIMEOUT, nextFrame } from \"./frames.ts\";\nimport { patch } from \"./patch.ts\";\nimport {\n type AnyObject3D,\n batchedTriangles,\n isBatched,\n isInstanced,\n pathOf,\n triangleCount,\n} from \"./scene.ts\";\n\n/**\n * The two `WebGLRenderer` methods the ledger wraps. Typed loosely, so a renderer from any copy of\n * three's types fits.\n */\nexport type LedgerRenderer = {\n render(scene: AnyObject3D, camera: object): void;\n renderBufferDirect(\n camera: object,\n scene: object | null,\n geometry: object,\n material: object,\n object: AnyObject3D,\n group: unknown,\n ): void;\n /** For pass sizes: the target being drawn to, `null` for the screen. */\n getRenderTarget?(): unknown;\n /** For pass sizes: the screen's drawing buffer, device pixels. */\n getContext?(): unknown;\n};\n\nexport type DrawLedgerOptions = {\n /**\n * The group an object name files under. Default: up to the first `:` or `#`, so a bake's\n * `wagon:paint` and a strip's `grass#3` group as `wagon` and `grass`.\n */\n groupOf?: (name: string) => string;\n};\n\nexport type RecordDrawLedgerOptions = DrawLedgerOptions & {\n /**\n * Record one call of this (the app's own render, sync or async) instead of one animation frame.\n * For a tab in the background, where no frames come.\n */\n render?: () => unknown;\n /** How long to wait for each animation frame before failing, in ms. Default 2000. */\n timeout?: number;\n};\n\nexport type LedgerRow = {\n name: string;\n total: number;\n /** Draws per pass name. */\n passes: Record<string, number>;\n};\n\nexport type PassRow = {\n name: string;\n draws: number;\n /** Distinct objects drawn: for `shadow`, the casters. */\n objects: number;\n /** Triangles drawn, instances counted. */\n triangles: number;\n /** Sizes drawn to, in device pixels: `2880×1800`, or `screen 2880×1800`. */\n targets: string[];\n /** Every call was one small mesh through an orthographic camera: a post-processing pass. */\n fullscreen: boolean;\n};\n\nexport type DrawLedger = {\n /**\n * Pass names in the order they first drew: `shadow`, then each `render()` call as its scene's\n * name, or `render 1`, `render 2`… when unnamed (a post-processing chain is several).\n */\n passes: string[];\n /** By group, most draws first. */\n groups: LedgerRow[];\n /** By object name, most draws first. */\n objects: LedgerRow[];\n draws: number;\n /** Each pass's draws, triangles, casters and target sizes, in `passes` order. */\n passStats: PassRow[];\n /** `render()` calls that drew one fullscreen pass, and the pixels they shaded between them. */\n fullscreenPasses: number;\n fullscreenPixels: number;\n};\n\nexport type DrawLedgerRecording = {\n /** Restore the renderer and return what was drawn since `beginDrawLedger`. */\n end: () => DrawLedger;\n};\n\nconst defaultGroupOf = (name: string) => name.split(/[:#]/)[0] || name;\n\n/** The object's name, or for an unnamed one its named ancestors, type and material. */\nconst nameOf = (object: Object3D, material: Material) =>\n object.name ||\n `${[...pathOf(object, null, undefined, 2), object.type].join(\"/\")} [${material.name || material.type}]`;\n\nconst tally = (rows: Map<string, LedgerRow>, name: string, pass: string) => {\n const row = rows.get(name) ?? { name, total: 0, passes: {} };\n\n row.total += 1;\n row.passes[pass] = (row.passes[pass] ?? 0) + 1;\n rows.set(name, row);\n};\n\nconst sorted = (rows: Map<string, LedgerRow>) =>\n [...rows.values()].sort((a, b) => b.total - a.total);\n\n/** Triangles one draw makes: its material group's range, times its instances. */\nconst drawTriangles = (geometry: BufferGeometry, object: Object3D, group: unknown) => {\n const mesh = object as Mesh;\n\n if (mesh.isMesh !== true) {\n return 0;\n }\n\n if (isBatched(mesh)) {\n return batchedTriangles(mesh).triangles;\n }\n\n const range = group as { start?: number; count?: number } | null;\n const elements = geometry.index?.count ?? geometry.attributes.position?.count ?? 0;\n const own =\n range && typeof range.count === \"number\"\n ? Math.floor(Math.max(0, Math.min(range.count, elements - (range.start ?? 0))) / 3)\n : triangleCount(geometry);\n\n return own * (isInstanced(mesh) ? mesh.count : 1);\n};\n\ntype Size = { width: number; height: number; screen: boolean };\n\ntype PassTally = Omit<PassRow, \"objects\" | \"targets\" | \"fullscreen\"> & {\n objects: Set<Object3D>;\n targets: Set<string>;\n calls: number;\n fullscreenCalls: number;\n};\n\ntype Call = {\n pass: string;\n draws: number;\n orthographic: boolean;\n /** Vertices of the one draw so far. */\n vertices: number;\n size: Size | null;\n};\n\n/** Start counting every draw. Call `end()` after the frame to restore the renderer and read it. */\nexport const beginDrawLedger = (\n renderer: LedgerRenderer,\n options: DrawLedgerOptions = {},\n): DrawLedgerRecording => {\n if (typeof renderer.renderBufferDirect !== \"function\") {\n throw new TypeError(\"three-audit: the draw ledger needs a WebGLRenderer\");\n }\n\n const groupOf = options.groupOf ?? defaultGroupOf;\n const render = renderer.render;\n const renderBufferDirect = renderer.renderBufferDirect;\n const passes: string[] = [];\n const objects = new Map<string, LedgerRow>();\n const groups = new Map<string, LedgerRow>();\n const stats = new Map<string, PassTally>();\n const calls: Call[] = [];\n let renders = 0;\n let current = \"direct\";\n let draws = 0;\n let fullscreenPasses = 0;\n let fullscreenPixels = 0;\n\n const statOf = (pass: string) => {\n let stat = stats.get(pass);\n\n if (!stat) {\n stat = {\n name: pass,\n draws: 0,\n triangles: 0,\n objects: new Set(),\n targets: new Set(),\n calls: 0,\n fullscreenCalls: 0,\n };\n stats.set(pass, stat);\n }\n\n return stat;\n };\n\n const sizeNow = (): Size | null => {\n const target = renderer.getRenderTarget?.() as { width?: number; height?: number } | null;\n\n if (target && typeof target.width === \"number\" && typeof target.height === \"number\") {\n return { width: target.width, height: target.height, screen: false };\n }\n\n const gl = renderer.getContext?.() as\n | { drawingBufferWidth?: number; drawingBufferHeight?: number }\n | null\n | undefined;\n\n return target === null && typeof gl?.drawingBufferWidth === \"number\"\n ? { width: gl.drawingBufferWidth, height: gl.drawingBufferHeight ?? 0, screen: true }\n : null;\n };\n\n const restoreRender = patch(renderer, \"render\", function (this: unknown, scene, camera) {\n const outer = current;\n\n renders += 1;\n current = (scene as Partial<Object3D>).name || `render ${renders}`;\n\n const call: Call = {\n pass: current,\n draws: 0,\n orthographic: (camera as { isOrthographicCamera?: boolean }).isOrthographicCamera === true,\n vertices: 0,\n size: null,\n };\n\n calls.push(call);\n\n try {\n render.call(this, scene, camera);\n } finally {\n calls.pop();\n current = outer;\n\n const stat = stats.get(call.pass);\n\n if (stat) {\n stat.calls += 1;\n\n // One small mesh through an orthographic camera: a fullscreen quad or triangle.\n if (call.draws === 1 && call.orthographic && call.vertices <= 6) {\n stat.fullscreenCalls += 1;\n fullscreenPasses += 1;\n fullscreenPixels += call.size ? call.size.width * call.size.height : 0;\n }\n }\n }\n });\n\n const restoreDraw = patch(\n renderer,\n \"renderBufferDirect\",\n function (this: unknown, camera, scene, geometry, material, object, group) {\n const shadow =\n (material as { isMeshDepthMaterial?: boolean }).isMeshDepthMaterial === true ||\n (material as { isMeshDistanceMaterial?: boolean }).isMeshDistanceMaterial === true;\n const pass = shadow ? \"shadow\" : current;\n const name = nameOf(object as unknown as Object3D, material as Material);\n\n if (!passes.includes(pass)) {\n passes.push(pass);\n }\n\n const stat = statOf(pass);\n const size = sizeNow();\n const drawn = geometry as BufferGeometry;\n\n stat.draws += 1;\n stat.triangles += drawTriangles(drawn, object as unknown as Object3D, group);\n stat.objects.add(object as unknown as Object3D);\n\n if (size) {\n stat.targets.add(`${size.screen ? \"screen \" : \"\"}${size.width}×${size.height}`);\n }\n\n const call = calls.at(-1);\n\n if (call && !shadow) {\n call.draws += 1;\n call.vertices = drawn.attributes?.position?.count ?? Infinity;\n call.size = size;\n }\n\n draws += 1;\n tally(objects, name, pass);\n tally(groups, groupOf(name), pass);\n renderBufferDirect.call(this, camera, scene, geometry, material, object, group);\n },\n );\n\n return {\n end: () => {\n restoreDraw();\n restoreRender();\n\n return {\n passes,\n groups: sorted(groups),\n objects: sorted(objects),\n draws,\n passStats: passes.map((pass) => {\n const stat = stats.get(pass)!;\n\n return {\n name: pass,\n draws: stat.draws,\n objects: stat.objects.size,\n triangles: stat.triangles,\n targets: [...stat.targets],\n fullscreen: stat.calls > 0 && stat.fullscreenCalls === stat.calls,\n };\n }),\n fullscreenPasses,\n fullscreenPixels,\n };\n },\n };\n};\n\n/**\n * Record exactly one frame of an app that renders from `requestAnimationFrame` (three's\n * `setAnimationLoop`, React Three Fiber): from one animation frame to the next. A demand-driven\n * loop that renders nothing that frame gives an empty ledger. Browser only.\n *\n * With `render`, records one call of it instead, frames or not. Without, fails after `timeout`\n * when no frame comes (a background tab), rather than waiting for ever.\n */\nexport const recordDrawLedger = async (\n renderer: LedgerRenderer,\n options: RecordDrawLedgerOptions = {},\n): Promise<DrawLedger> => {\n const { render, timeout = FRAME_TIMEOUT, ...ledgerOptions } = options;\n\n if (render) {\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n try {\n await render();\n } catch (error) {\n recording.end();\n throw error;\n }\n\n return recording.end();\n }\n\n const hint = \"Bring the tab to the front, or pass { render } to record one call of your own.\";\n\n await nextFrame(\"recordDrawLedger\", timeout, hint);\n\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n try {\n await nextFrame(\"recordDrawLedger\", timeout, hint);\n } catch (error) {\n recording.end();\n throw error;\n }\n\n return recording.end();\n};\n\n/** Rows shaped for `console.table`: one column per pass, then the total. */\nexport const ledgerTable = (rows: LedgerRow[], passes: string[]) =>\n rows.map((row) => ({\n name: row.name,\n ...Object.fromEntries(passes.map((pass) => [pass, row.passes[pass] ?? 0])),\n total: row.total,\n }));\n\n/** The ledger as two console tables: by group, and every object collapsed beneath. */\nexport const printDrawLedger = (ledger: DrawLedger) => {\n /* oxlint-disable no-console -- printing is this function's job */\n console.group(`Draw ledger: ${ledger.draws} draws in one frame`);\n console.table(ledgerTable(ledger.groups, ledger.passes));\n console.table(ledger.passStats.map((pass) => ({ ...pass, targets: pass.targets.join(\", \") })));\n\n if (ledger.fullscreenPasses > 0) {\n console.log(\n `${ledger.fullscreenPasses} fullscreen passes: ${(ledger.fullscreenPixels / 1e6).toFixed(1)} Mpx shaded`,\n );\n }\n\n console.groupCollapsed(\"Every object\");\n console.table(ledgerTable(ledger.objects, ledger.passes));\n console.groupEnd();\n console.groupEnd();\n /* oxlint-enable no-console */\n};\n","/**\n * Measuring a layout: an object's world envelope, and the gap between two objects. The questions\n * a scene raises before anything renders wrong: does the train fit under the crossbar, does the\n * lid clear the hinge.\n *\n * Measured from the vertices as drawn (instances and batches included), not from bounding boxes\n * transformed after the fact, which grow with every rotation.\n */\nimport type { BatchedMesh, Mesh, Object3D } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n type Bakes,\n isBatched,\n isInstanced,\n isMesh,\n meshLabel,\n walk,\n} from \"./scene.ts\";\n\nexport type MeasureOptions = {\n /** `userData` key a string target is looked up by. Default `studioObject`. */\n tagKey?: string;\n /** A bake's parts (default), or its merged meshes: see `Bakes`. The same space either way. */\n bakes?: Bakes | undefined;\n};\n\n/** What to measure: an object, or a tag (`userData[tagKey]`), or failing that an object name. */\nexport type Target = string | AnyObject3D;\n\nexport type Bounds = {\n min: [number, number, number];\n max: [number, number, number];\n size: [number, number, number];\n centre: [number, number, number];\n};\n\nexport type Clearance = {\n /**\n * The smallest gap between the two, in world units. Measured between triangles' bounding boxes:\n * exact for faces square to the axes, a slight underestimate for sloped ones. 0 when they touch\n * or intersect.\n */\n gap: number;\n /** The axis the gap is measured along, when it's straight along one; `null` when diagonal. */\n axis: \"x\" | \"y\" | \"z\" | null;\n /** The closest meshes, one from each side. */\n between: [string, string];\n};\n\n/** A 4 × 4 column-major matrix, as three stores them. */\ntype Elements = ArrayLike<number>;\n\nconst multiply = (a: Elements, b: Elements, out: number[]) => {\n for (let row = 0; row < 4; row += 1) {\n for (let column = 0; column < 4; column += 1) {\n let sum = 0;\n\n for (let k = 0; k < 4; k += 1) {\n sum += a[k * 4 + row]! * b[column * 4 + k]!;\n }\n\n out[column * 4 + row] = sum;\n }\n }\n\n return out;\n};\n\ntype Triangle = (\n ax: number,\n ay: number,\n az: number,\n bx: number,\n by: number,\n bz: number,\n cx: number,\n cy: number,\n cz: number,\n) => void;\n\n/** Every triangle a mesh draws, in world space: each instance of an instanced or batched mesh. */\nconst eachTriangle = (mesh: Mesh, visit: Triangle) => {\n const { index, attributes } = mesh.geometry;\n const position = attributes.position;\n\n if (!position) {\n return;\n }\n\n const world = mesh.matrixWorld.elements;\n const corner = [0, 0, 0, 0, 0, 0, 0, 0, 0];\n const emit = (matrix: Elements, first: number, count: number, indexed: boolean) => {\n for (let k = first; k + 2 < first + count; k += 3) {\n for (let c = 0; c < 3; c += 1) {\n const v = indexed ? index!.getX(k + c) : k + c;\n const x = position.getX(v);\n const y = position.getY(v);\n const z = position.getZ(v);\n\n for (let axis = 0; axis < 3; axis += 1) {\n corner[c * 3 + axis] =\n matrix[axis]! * x + matrix[axis + 4]! * y + matrix[axis + 8]! * z + matrix[axis + 12]!;\n }\n }\n\n visit(\n ...(corner as [number, number, number, number, number, number, number, number, number]),\n );\n }\n };\n\n if (isBatched(mesh)) {\n const batch = mesh as BatchedMesh;\n const instance = Array.from({ length: 16 }, () => 0);\n const matrix = Array.from({ length: 16 }, () => 0);\n // `getMatrixAt` fills whatever has three's `fromArray`, so no Matrix4 is needed.\n const target = {\n fromArray(array: ArrayLike<number>, offset = 0) {\n for (let i = 0; i < 16; i += 1) {\n instance[i] = array[offset + i]!;\n }\n\n return target;\n },\n };\n\n for (let id = 0; id < batch.maxInstanceCount; id += 1) {\n let range: {\n vertexStart: number;\n vertexCount: number;\n indexStart: number;\n indexCount: number;\n };\n\n try {\n if (!batch.getVisibleAt(id)) {\n continue;\n }\n\n range = batch.getGeometryRangeAt(batch.getGeometryIdAt(id)) as typeof range;\n batch.getMatrixAt(id, target as never);\n } catch {\n continue;\n }\n\n multiply(world, instance, matrix);\n\n if (index) {\n emit(matrix, range.indexStart, range.indexCount, true);\n } else {\n emit(matrix, range.vertexStart, range.vertexCount, false);\n }\n }\n\n return;\n }\n\n const { start, count } = mesh.geometry.drawRange;\n const elements = index?.count ?? position.count;\n const first = Math.min(start, elements);\n const drawn = Math.min(elements, start + count) - first;\n\n if (isInstanced(mesh)) {\n const matrix = Array.from({ length: 16 }, () => 0);\n const instances = mesh.instanceMatrix.array;\n\n for (let i = 0; i < mesh.count; i += 1) {\n multiply(world, Array.prototype.slice.call(instances, i * 16, i * 16 + 16), matrix);\n emit(matrix, first, drawn, index !== null);\n }\n\n return;\n }\n\n emit(world, first, drawn, index !== null);\n};\n\n/** A box as [minX, minY, minZ, maxX, maxY, maxZ]. */\ntype Box = [number, number, number, number, number, number];\n\nconst emptyBox = (): Box => [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];\n\nconst grow = (box: Box, x: number, y: number, z: number) => {\n box[0] = Math.min(box[0], x);\n box[1] = Math.min(box[1], y);\n box[2] = Math.min(box[2], z);\n box[3] = Math.max(box[3], x);\n box[4] = Math.max(box[4], y);\n box[5] = Math.max(box[5], z);\n};\n\n/** Per axis, how far apart two boxes are (0 where they overlap). */\nconst apart = (\n a: ArrayLike<number>,\n ai: number,\n b: ArrayLike<number>,\n bi: number,\n out: number[],\n) => {\n for (let axis = 0; axis < 3; axis += 1) {\n out[axis] = Math.max(0, a[ai + axis]! - b[bi + axis + 3]!, b[bi + axis]! - a[ai + axis + 3]!);\n }\n\n return Math.hypot(out[0]!, out[1]!, out[2]!);\n};\n\nconst round = (value: number) => {\n const rounded = Math.round(value * 1000) / 1000;\n\n return rounded === 0 ? 0 : rounded;\n};\n\n/** The visible meshes a target stands for. */\nconst meshesOf = (\n root: Object3D,\n target: Target,\n { tagKey = \"studioObject\", bakes = \"sources\" }: MeasureOptions,\n role: string,\n) => {\n let objects: Object3D[];\n\n if (typeof target === \"string\") {\n const tagged: Object3D[] = [];\n const named: Object3D[] = [];\n\n walk(root, {}, (object) => {\n const tag: unknown = object.userData[tagKey];\n\n if (tag !== undefined && tag !== null && String(tag) === target) {\n tagged.push(object);\n } else if (object.name === target) {\n named.push(object);\n }\n });\n objects = tagged.length > 0 ? tagged : named;\n\n if (objects.length === 0) {\n throw new Error(\n `three-audit: ${role}: nothing is tagged userData.${tagKey} = \"${target}\" or named \"${target}\"`,\n );\n }\n } else {\n objects = [asObject3D(target)];\n }\n\n const meshes = new Set<Mesh>();\n\n for (const object of objects) {\n walk(object, { visibleOnly: true, bakes }, (child) => {\n if (isMesh(child)) {\n meshes.add(child);\n }\n });\n }\n\n return meshes;\n};\n\n/** A target's world envelope, from its visible meshes' vertices; `null` when it draws nothing. */\nexport const measureBounds = (\n root: AnyObject3D,\n target: Target,\n options: MeasureOptions = {},\n): Bounds | null => {\n const scene = asObject3D(root);\n\n scene.updateWorldMatrix(true, true);\n\n const box = emptyBox();\n\n for (const mesh of meshesOf(scene, target, options, \"bounds\")) {\n eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n grow(box, ax, ay, az);\n grow(box, bx, by, bz);\n grow(box, cx, cy, cz);\n });\n }\n\n if (box[0] > box[3]) {\n return null;\n }\n\n const min = [box[0], box[1], box[2]].map(round) as Bounds[\"min\"];\n const max = [box[3], box[4], box[5]].map(round) as Bounds[\"max\"];\n\n return {\n min,\n max,\n size: [0, 1, 2].map((axis) => round(box[axis + 3]! - box[axis]!)) as Bounds[\"size\"],\n centre: [0, 1, 2].map((axis) => round((box[axis]! + box[axis + 3]!) / 2)) as Bounds[\"centre\"],\n };\n};\n\n/** Triangle boxes past this many pairs, a mesh pair is measured by its whole boxes instead. */\nconst PAIR_LIMIT = 4_000_000;\n\ntype Measured = { mesh: Mesh; box: Box; triangles: Float64Array | null };\n\n/**\n * The gap between two targets, and which of their meshes come closest. Throws when a target\n * matches nothing; `null` when either draws nothing. Meshes in both targets are left out of `a`.\n */\nexport const measureClearance = (\n root: AnyObject3D,\n a: Target,\n b: Target,\n options: MeasureOptions = {},\n): Clearance | null => {\n const scene = asObject3D(root);\n const tagKey = options.tagKey ?? \"studioObject\";\n\n scene.updateWorldMatrix(true, true);\n\n const inB = meshesOf(scene, b, options, \"clearance b\");\n const inA = [...meshesOf(scene, a, options, \"clearance a\")].filter((mesh) => !inB.has(mesh));\n const measure = (mesh: Mesh): Measured => {\n const box = emptyBox();\n\n eachTriangle(mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n grow(box, ax, ay, az);\n grow(box, bx, by, bz);\n grow(box, cx, cy, cz);\n });\n\n return { mesh, box, triangles: null };\n };\n const sideA = inA.map(measure).filter(({ box }) => box[0] <= box[3]);\n const sideB = [...inB].map(measure).filter(({ box }) => box[0] <= box[3]);\n\n if (sideA.length === 0 || sideB.length === 0) {\n return null;\n }\n\n const trianglesOf = (side: Measured) => {\n if (!side.triangles) {\n const boxes: number[] = [];\n\n eachTriangle(side.mesh, (ax, ay, az, bx, by, bz, cx, cy, cz) => {\n boxes.push(\n Math.min(ax, bx, cx),\n Math.min(ay, by, cy),\n Math.min(az, bz, cz),\n Math.max(ax, bx, cx),\n Math.max(ay, by, cy),\n Math.max(az, bz, cz),\n );\n });\n side.triangles = Float64Array.from(boxes);\n }\n\n return side.triangles;\n };\n\n const axes = [0, 0, 0];\n const pairs = sideA\n .flatMap((left) =>\n sideB.map((right) => ({ left, right, distance: apart(left.box, 0, right.box, 0, axes) })),\n )\n .sort((x, y) => x.distance - y.distance);\n let best = Infinity;\n let bestAxes = [0, 0, 0];\n let between: [Mesh, Mesh] = [pairs[0]!.left.mesh, pairs[0]!.right.mesh];\n\n for (const { left, right, distance } of pairs) {\n if (distance >= best) {\n break;\n }\n\n const leftTriangles = trianglesOf(left);\n const rightTriangles = trianglesOf(right);\n // Only triangles that could beat the best so far, measured against the other mesh's box.\n const near = (triangles: Float64Array, other: Box) => {\n const kept: number[] = [];\n\n for (let t = 0; t < triangles.length; t += 6) {\n if (apart(triangles, t, other, 0, axes) < best) {\n kept.push(t);\n }\n }\n\n return kept;\n };\n const nearLeft = near(leftTriangles, right.box);\n const nearRight = near(rightTriangles, left.box);\n\n if (nearLeft.length * nearRight.length > PAIR_LIMIT) {\n if (distance < best) {\n best = distance;\n apart(left.box, 0, right.box, 0, axes);\n bestAxes = [...axes];\n between = [left.mesh, right.mesh];\n }\n\n continue;\n }\n\n for (const i of nearLeft) {\n for (const j of nearRight) {\n const gap = apart(leftTriangles, i, rightTriangles, j, axes);\n\n if (gap < best) {\n best = gap;\n bestAxes = [...axes];\n between = [left.mesh, right.mesh];\n }\n }\n }\n }\n\n const separated = bestAxes.flatMap((value, axis) => (value > 0 ? [axis] : []));\n\n return {\n gap: round(best),\n axis: separated.length === 1 ? ([\"x\", \"y\", \"z\"] as const)[separated[0]!]! : null,\n between: [meshLabel(between[0], scene, tagKey), meshLabel(between[1], scene, tagKey)],\n };\n};\n","/**\n * The checks as console globals, for a dev page or a headless browser's `eval`: `summary()`,\n * `tris()`, `meshes()`, `census()`, `audit()`, `zfight()`, `report()`, `bbox()`, `clearance()`,\n * and with a renderer `ledger()`, `beginLedger()` and `blackFrames()`. All of them also sit on\n * `threeAudit`, which is the only global with `globals: false`.\n */\nimport type { BatchedMesh } from \"three\";\n\nimport { auditScene, type AuditSummaryOptions, summarizeScene } from \"./audit-scene.ts\";\nimport { findBadGeometry } from \"./bad-geometry.ts\";\nimport { type ProbeRenderer, watchBlackFrames } from \"./black-frames.ts\";\nimport {\n beginDrawLedger,\n type DrawLedgerOptions,\n type LedgerRenderer,\n printDrawLedger,\n recordDrawLedger,\n type RecordDrawLedgerOptions,\n} from \"./draw-ledger.ts\";\nimport { FRAME_TIMEOUT, nextFrame } from \"./frames.ts\";\nimport { measureBounds, measureClearance, type Target } from \"./measure.ts\";\nimport type { AnyObject3D, Bakes, Skip } from \"./scene.ts\";\nimport { type CensusOptions, countTriangles, geometryCensus, listMeshes } from \"./triangles.ts\";\nimport { findZFighting } from \"./z-fighting.ts\";\n\nexport type AuditHelpersOptions = {\n /** A `WebGLRenderer`, for `ledger()`, `beginLedger()` and `blackFrames()`. */\n renderer?: (LedgerRenderer & ProbeRenderer) | undefined;\n skip?: Skip | undefined;\n tagKey?: string;\n /** What checks see of a bake: its parts (default) or its merged meshes. See `Bakes`. */\n bakes?: Bakes | undefined;\n /** Names geometries inside a batch in `census()`: see `geometryCensus`'s `label`. */\n batchLabel?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;\n /**\n * Also put each helper on `globalThis` under its short name (`tris`, `report`…). Default true;\n * false leaves only `threeAudit`, for pages where short names collide with other tooling.\n */\n globals?: boolean;\n};\n\nconst NAMESPACE = \"threeAudit\";\n\nconst NAMES = [\n \"auditRoot\",\n \"summary\",\n \"tris\",\n \"meshes\",\n \"census\",\n \"audit\",\n \"zfight\",\n \"report\",\n \"bbox\",\n \"clearance\",\n \"ledger\",\n \"beginLedger\",\n \"blackFrames\",\n] as const;\n\nconst needsRenderer = (name: string) => () => {\n throw new Error(`three-audit: ${name}() needs installAuditHelpers(scene, { renderer })`);\n};\n\n/**\n * Put the helpers on `globalThis`, and on `globalThis.threeAudit`. Returns a function that\n * removes them.\n *\n * vanilla: installAuditHelpers(scene, { renderer })\n * R3F: const { scene, gl } = useThree();\n * useLayoutEffect(() => installAuditHelpers(scene, { renderer: gl }), [scene, gl]);\n */\nexport const installAuditHelpers = (root: AnyObject3D, options: AuditHelpersOptions = {}) => {\n const { renderer, skip, batchLabel, bakes } = options;\n const tagKey = options.tagKey ?? \"studioObject\";\n\n const helpers: Record<(typeof NAMES)[number], unknown> = {\n auditRoot: root,\n summary: (tag?: string, summaryOptions: Pick<AuditSummaryOptions, \"gap\" | \"zFighting\"> = {}) =>\n summarizeScene(root, {\n skip,\n tagKey,\n bakes,\n ...summaryOptions,\n ...(tag === undefined ? {} : { tag }),\n }),\n tris: () => countTriangles(root, { skip }),\n meshes: () => listMeshes(root, { skip }),\n census: (censusOptions: Pick<CensusOptions, \"budget\" | \"label\"> = {}) =>\n geometryCensus(root, { skip, label: batchLabel, ...censusOptions }),\n audit: () => findBadGeometry(root, { skip, tagKey, bakes }),\n zfight: (gap?: number, self?: boolean) =>\n findZFighting(root, {\n skip,\n tagKey,\n bakes,\n ...(gap === undefined ? {} : { gap }),\n ...(self === undefined ? {} : { self }),\n }),\n report: (tag?: string) =>\n auditScene(root, { skip, tagKey, bakes, ...(tag === undefined ? {} : { tag }) }),\n bbox: (target: Target) => measureBounds(root, target, { tagKey, bakes }),\n clearance: (a: Target, b: Target) => measureClearance(root, a, b, { tagKey, bakes }),\n ledger: renderer\n ? async (ledgerOptions?: RecordDrawLedgerOptions) => {\n const ledger = await recordDrawLedger(renderer, ledgerOptions);\n\n printDrawLedger(ledger);\n\n return ledger;\n }\n : needsRenderer(\"ledger\"),\n beginLedger: renderer\n ? (ledgerOptions?: DrawLedgerOptions) => {\n const recording = beginDrawLedger(renderer, ledgerOptions);\n\n return {\n end: () => {\n const ledger = recording.end();\n\n printDrawLedger(ledger);\n\n return ledger;\n },\n };\n }\n : needsRenderer(\"beginLedger\"),\n blackFrames: renderer\n ? async (seconds = 10) => {\n await nextFrame(\n \"blackFrames()\",\n FRAME_TIMEOUT,\n \"Black frames only show in a visible tab: bring it to the front.\",\n );\n\n const watch = watchBlackFrames(renderer);\n\n return new Promise<{ frames: number; blackFrames: readonly number[] }>((resolve) => {\n setTimeout(() => {\n watch.stop();\n resolve({ frames: watch.frames, blackFrames: watch.blackFrames });\n }, seconds * 1000);\n });\n }\n : needsRenderer(\"blackFrames\"),\n };\n const namespace = { ...helpers };\n const scope = globalThis as Record<string, unknown>;\n\n if (options.globals !== false) {\n Object.assign(scope, helpers);\n }\n\n scope[NAMESPACE] = namespace;\n\n return () => {\n for (const name of NAMES) {\n if (scope[name] === helpers[name]) {\n delete scope[name];\n }\n }\n\n if (scope[NAMESPACE] === namespace) {\n delete scope[NAMESPACE];\n }\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkmake/three-audit",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Scene checks for three.js: triangle counts, cost census, NaN normals, z-fighting, black frames and a one-frame draw-call ledger. Zero dependencies.",
5
5
  "keywords": [
6
6
  "audit",
@@ -13,7 +13,7 @@
13
13
  "triangles",
14
14
  "z-fighting"
15
15
  ],
16
- "homepage": "https://github.com/zkmake/three-kit/tree/main/packages/three-audit#readme",
16
+ "homepage": "https://zkmake.github.io/three-kit/three-audit/",
17
17
  "bugs": "https://github.com/zkmake/three-kit/issues",
18
18
  "license": "MIT",
19
19
  "author": "Zubin Khavarian",
@@ -1 +0,0 @@
1
- {"version":3,"file":"audit-scene-CJG1DgRF.js","names":[],"sources":["../src/scene.ts","../src/bad-geometry.ts","../src/triangles.ts","../src/z-fighting.ts","../src/audit-scene.ts"],"sourcesContent":["/**\n * Scene-graph plumbing every check shares: the walk, mesh kinds, triangle counts, tags and names.\n *\n * Nothing here imports three at runtime; objects are recognised by three's own `is*` flags, so the\n * checks work on whatever copy of three the app bundles.\n */\nimport type { BatchedMesh, BufferGeometry, InstancedMesh, Material, Mesh, Object3D } from \"three\";\n\n/**\n * Any three.js object, from whichever copy of three's types the app has. The checks take this, so\n * an app on a newer `@types/three` than this package was built against passes its scene as is.\n */\nexport type AnyObject3D = { readonly isObject3D: true; readonly children: readonly object[] };\n\n/** The object as three's own type, for the walk. */\nexport const asObject3D = (object: AnyObject3D) => object as unknown as Object3D;\n\n/** Leave an object and everything under it out of a check. */\nexport type Skip = (object: Object3D) => boolean;\n\nexport type WalkOptions = {\n skip?: Skip | undefined;\n /** Prune hidden objects and their subtrees, as the renderer does. */\n visibleOnly?: boolean;\n};\n\n/** Depth-first, in child order. A skipped (or, with `visibleOnly`, hidden) object prunes its subtree. */\nexport const walk = (root: Object3D, options: WalkOptions, visit: (object: Object3D) => void) => {\n const stack = [root];\n\n while (stack.length > 0) {\n const object = stack.pop()!;\n\n if ((options.visibleOnly === true && !object.visible) || options.skip?.(object) === true) {\n continue;\n }\n\n visit(object);\n\n for (let i = object.children.length - 1; i >= 0; i -= 1) {\n stack.push(object.children[i]!);\n }\n }\n};\n\nexport const isMesh = (object: Object3D): object is Mesh => (object as Mesh).isMesh === true;\n\n/** Anything the renderer draws: a mesh, line, point cloud or sprite. */\nexport const isDrawable = (object: Object3D) => {\n const flags = object as Partial<Record<\"isMesh\" | \"isLine\" | \"isPoints\" | \"isSprite\", boolean>>;\n\n return (\n flags.isMesh === true ||\n flags.isLine === true ||\n flags.isPoints === true ||\n flags.isSprite === true\n );\n};\n\nexport const isInstanced = (mesh: Mesh): mesh is InstancedMesh =>\n (mesh as InstancedMesh).isInstancedMesh === true;\n\nexport const isBatched = (mesh: Mesh): mesh is BatchedMesh =>\n (mesh as unknown as BatchedMesh).isBatchedMesh === true;\n\n/** Triangles one draw of this geometry makes: indexed or not, within its draw range. */\nexport const triangleCount = (geometry: BufferGeometry) => {\n const elements = geometry.index?.count ?? geometry.attributes.position?.count ?? 0;\n const { start, count } = geometry.drawRange;\n const drawn = Math.min(elements, start + count) - start;\n\n return Math.max(0, Math.floor(drawn / 3));\n};\n\n/**\n * A batch's visible instances by the geometry each draws: how many, and that geometry's triangles\n * (its range in the shared buffer). In first-drawn order.\n */\nexport const batchedGeometries = (batch: BatchedMesh) => {\n const geometries = new Map<number, { instances: number; triangles: number }>();\n\n for (let id = 0; id < batch.maxInstanceCount; id += 1) {\n let geometryId: number;\n\n // Deleted and never-used ids throw.\n try {\n if (!batch.getVisibleAt(id)) {\n continue;\n }\n\n geometryId = batch.getGeometryIdAt(id);\n } catch {\n continue;\n }\n\n const known = geometries.get(geometryId);\n\n if (known) {\n known.instances += 1;\n continue;\n }\n\n const range = batch.getGeometryRangeAt(geometryId) as {\n count?: number;\n indexCount: number;\n vertexCount: number;\n };\n const elements = range.count ?? (batch.geometry.index ? range.indexCount : range.vertexCount);\n\n geometries.set(geometryId, { instances: 1, triangles: Math.floor(elements / 3) });\n }\n\n return geometries;\n};\n\n/** Triangles a batch draws across its visible instances, and how many instances that is. */\nexport const batchedTriangles = (batch: BatchedMesh) => {\n let triangles = 0;\n let instances = 0;\n\n for (const geometry of batchedGeometries(batch).values()) {\n triangles += geometry.triangles * geometry.instances;\n instances += geometry.instances;\n }\n\n return { instances, triangles };\n};\n\n/** The tag of the nearest tagged ancestor (the object itself included), or `null`. */\nexport const tagOf = (object: Object3D, tagKey: string) => {\n for (let at: Object3D | null = object; at; at = at.parent) {\n const tag: unknown = at.userData[tagKey];\n\n if (tag !== undefined && tag !== null) {\n return String(tag);\n }\n }\n\n return null;\n};\n\n/**\n * A skip that keeps only what's tagged `tag`: prunes objects tagged otherwise, and meshes outside\n * any object tagged `tag`. Throws when nothing under `root` carries the tag.\n */\nexport const onlyTag = (root: Object3D, tag: string, tagKey: string, skip?: Skip): Skip => {\n let found = false;\n\n walk(root, { skip }, (object) => {\n const own: unknown = object.userData[tagKey];\n\n found ||= own !== undefined && own !== null && String(own) === tag;\n });\n\n if (!found) {\n throw new Error(`three-audit: nothing is tagged userData.${tagKey} = \"${tag}\"`);\n }\n\n return (object) => {\n if (skip?.(object) === true) {\n return true;\n }\n\n const own: unknown = object.userData[tagKey];\n\n if (own !== undefined && own !== null && String(own) !== tag) {\n return true;\n }\n\n return isDrawable(object) && tagOf(object, tagKey) !== tag;\n };\n};\n\nexport const hasTag = (root: Object3D, tagKey: string, skip?: Skip) => {\n let found = false;\n\n walk(root, { skip }, (object) => {\n found ||= object.userData[tagKey] !== undefined && object.userData[tagKey] !== null;\n });\n\n return found;\n};\n\nexport const meshName = (mesh: Mesh) => mesh.name || mesh.geometry.name || mesh.geometry.type;\n\n/**\n * The named ancestors above an object, outermost first, from `root` (included when named) down:\n * at most the nearest `depth`, with `…` for the rest. An unnamed ancestor carrying `tagKey` goes\n * by its tag.\n */\nexport const pathOf = (object: Object3D, root: Object3D | null, tagKey?: string, depth = 3) => {\n const names: string[] = [];\n\n for (let at = object.parent; at; at = at === root ? null : at.parent) {\n const tag: unknown = tagKey === undefined ? undefined : at.userData[tagKey];\n const name = at.name || (tag === undefined || tag === null ? \"\" : String(tag));\n\n if (name) {\n names.unshift(name);\n }\n }\n\n return names.length > depth ? [\"…\", ...names.slice(-depth)] : names;\n};\n\n/** `path/to/mesh [material]`: enough to find a mesh in the scene without walking it by hand. */\nexport const meshLabel = (mesh: Mesh, root: Object3D | null, tagKey?: string) =>\n `${[...pathOf(mesh, root, tagKey), meshName(mesh)].join(\"/\")} [${materialName(mesh)}]`;\n\nexport const materialName = (mesh: Mesh) => {\n const material = (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material) as\n | Material\n | undefined;\n\n return material ? material.name || material.type : \"none\";\n};\n\n/** Adds a property JSON and `console.table` leave out: a live object that would serialise the scene. */\nexport const withHidden = <Row extends object, Key extends string, Value>(\n row: Row,\n key: Key,\n value: Value,\n) => Object.defineProperty(row, key, { value, enumerable: false }) as Row & Record<Key, Value>;\n","/**\n * Geometry that lights a pixel NaN: a non-finite position or normal, or a zero-length normal on a\n * triangle with area. Bloom and other blurs spread one NaN pixel into a black block.\n */\nimport type { BufferGeometry, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n isMesh,\n meshLabel,\n meshName,\n type Skip,\n tagOf,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type BadGeometryOptions = {\n skip?: Skip | undefined;\n /** `userData` key naming the part a mesh belongs to, for the `under` column. */\n tagKey?: string;\n};\n\nexport type BadGeometryRow = {\n mesh: string;\n /** The tagged part it sits in, or its parent's name. */\n under: string;\n /** Vertices with a NaN or infinite position or normal component. */\n nonFinite: number;\n /** Vertices of triangles with area whose normal has no length. */\n zeroNormals: number;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly object: Mesh;\n};\n\nconst corner = (geometry: BufferGeometry, k: number) => geometry.index?.getX(k) ?? k;\n\nconst check = (geometry: BufferGeometry) => {\n const position = geometry.attributes.position!;\n const normal = geometry.attributes.normal;\n const flagged = new Uint8Array(position.count);\n let nonFinite = 0;\n let zeroNormals = 0;\n\n for (let v = 0; v < position.count; v += 1) {\n const finite =\n Number.isFinite(position.getX(v)) &&\n Number.isFinite(position.getY(v)) &&\n Number.isFinite(position.getZ(v)) &&\n (!normal ||\n (Number.isFinite(normal.getX(v)) &&\n Number.isFinite(normal.getY(v)) &&\n Number.isFinite(normal.getZ(v))));\n\n if (!finite) {\n flagged[v] = 1;\n nonFinite += 1;\n }\n }\n\n if (!normal) {\n return { nonFinite, zeroNormals };\n }\n\n const corners = geometry.index?.count ?? position.count;\n\n for (let k = 0; k + 2 < corners; k += 3) {\n const a = corner(geometry, k);\n const b = corner(geometry, k + 1);\n const c = corner(geometry, k + 2);\n const ux = position.getX(b) - position.getX(a);\n const uy = position.getY(b) - position.getY(a);\n const uz = position.getZ(b) - position.getZ(a);\n const vx = position.getX(c) - position.getX(a);\n const vy = position.getY(c) - position.getY(a);\n const vz = position.getZ(c) - position.getZ(a);\n const area = Math.hypot(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx);\n\n if (!(area > 1e-8)) {\n continue;\n }\n\n for (const v of [a, b, c]) {\n if (flagged[v] === 0 && Math.hypot(normal.getX(v), normal.getY(v), normal.getZ(v)) < 1e-4) {\n flagged[v] = 1;\n zeroNormals += 1;\n }\n }\n }\n\n return { nonFinite, zeroNormals };\n};\n\n/** Meshes with geometry that renders NaN, hidden ones included. `[]` is a pass. */\nexport const findBadGeometry = (\n root: AnyObject3D,\n options: BadGeometryOptions = {},\n): BadGeometryRow[] => {\n const tagKey = options.tagKey ?? \"studioObject\";\n const rows: BadGeometryRow[] = [];\n\n walk(asObject3D(root), { skip: options.skip }, (object) => {\n if (!isMesh(object) || !object.geometry.attributes.position) {\n return;\n }\n\n const { nonFinite, zeroNormals } = check(object.geometry);\n\n if (nonFinite > 0 || zeroNormals > 0) {\n const under = tagOf(object, tagKey) ?? object.parent?.name ?? \"\";\n\n rows.push(\n withHidden({ mesh: meshName(object), under, nonFinite, zeroNormals }, \"object\", object),\n );\n }\n });\n\n return rows;\n};\n\nexport type EmptyMeshRow = {\n /** `path/to/mesh [material]`. */\n mesh: string;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly object: Mesh;\n};\n\n/**\n * Meshes with no vertices, hidden ones included: what a merge of nothing leaves behind. They draw\n * nothing, but each one is still a draw call and a leak to dispose. A draw range of zero doesn't\n * count: that's usually a pool waiting to fill.\n */\nexport const findEmptyMeshes = (\n root: AnyObject3D,\n options: Pick<BadGeometryOptions, \"skip\" | \"tagKey\"> = {},\n): EmptyMeshRow[] => {\n const scene = asObject3D(root);\n const rows: EmptyMeshRow[] = [];\n\n walk(scene, { skip: options.skip }, (object) => {\n if (isMesh(object) && (object.geometry.attributes.position?.count ?? 0) === 0) {\n rows.push(\n withHidden(\n { mesh: meshLabel(object, scene, options.tagKey ?? \"studioObject\") },\n \"object\",\n object,\n ),\n );\n }\n });\n\n return rows;\n};\n","/**\n * Where a triangle budget goes: the total one pass draws, the meshes drawing it, and the\n * geometries behind them.\n */\nimport type { BatchedMesh, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n batchedGeometries,\n batchedTriangles,\n isBatched,\n isDrawable,\n isInstanced,\n isMesh,\n materialName,\n meshName,\n pathOf,\n type Skip,\n triangleCount,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type TriangleOptions = { skip?: Skip | undefined };\n\nexport type MeshRow = {\n /** `path/to/mesh`: its named ancestors, then its name (or its geometry's name or type). */\n name: string;\n material: string;\n /** Triangles in one copy. A batch's is an average: `geometryCensus` breaks it down. */\n triangles: number;\n instances: number;\n total: number;\n /** Not enumerable: kept out of JSON and `console.table`. */\n readonly mesh: Mesh;\n};\n\nexport type GeometryRow = {\n /**\n * `geometry.name`, or its type and the start of its uuid. For a geometry in a batch, the batch's\n * `label` for it, or `batch#id`.\n */\n geometry: string;\n /** Copies drawn: every mesh using it, instances counted. */\n uses: number;\n triangles: number;\n total: number;\n /** Its part of the census's total, 0–1. */\n share: number;\n /** With `budget`: whether `share` is over it. */\n overBudget?: boolean;\n};\n\nexport type CensusOptions = TriangleOptions & {\n /**\n * Names a geometry inside a batch, e.g. from the app's own table of what it added. Default\n * `batch#id`, the batch named by its path.\n */\n label?: ((batch: BatchedMesh, geometryId: number) => string | undefined) | undefined;\n /** Flag rows whose share of the total is over this, 0–1: `0.2` for anything over 20%. */\n budget?: number | undefined;\n};\n\nconst cost = (mesh: Mesh) => {\n if (isBatched(mesh)) {\n return batchedTriangles(mesh);\n }\n\n const instances = isInstanced(mesh) ? mesh.count : 1;\n\n return { instances, triangles: triangleCount(mesh.geometry) * instances };\n};\n\n/**\n * Triangles one pass draws: visible meshes, instances counted, no frustum culling. A shadow pass\n * adds roughly as much again for each shadow-casting light.\n */\nexport const countTriangles = (root: AnyObject3D, options: TriangleOptions = {}) => {\n let total = 0;\n\n walk(asObject3D(root), { skip: options.skip, visibleOnly: true }, (object) => {\n if (isMesh(object)) {\n total += cost(object).triangles;\n }\n });\n\n return total;\n};\n\n/** Every visible mesh, most triangles drawn first. */\nexport const listMeshes = (scene: AnyObject3D, options: TriangleOptions = {}): MeshRow[] => {\n const root = asObject3D(scene);\n const rows: MeshRow[] = [];\n\n walk(root, { skip: options.skip, visibleOnly: true }, (object) => {\n if (!isMesh(object)) {\n return;\n }\n\n const { instances, triangles } = cost(object);\n const row = {\n name: [...pathOf(object, root), meshName(object)].join(\"/\"),\n material: materialName(object),\n triangles: instances > 0 ? Math.round(triangles / instances) : 0,\n instances,\n total: triangles,\n };\n\n rows.push(withHidden(row, \"mesh\", object));\n });\n\n return rows.sort((a, b) => b.total - a.total);\n};\n\n/**\n * Geometries by triangles drawn, hidden meshes included, most first. Name geometries\n * (`geometry.name`) to make this readable. A batch counts each geometry in it separately, by its\n * visible instances: the row a budget is really spent on.\n */\nexport const geometryCensus = (root: AnyObject3D, options: CensusOptions = {}): GeometryRow[] => {\n const scene = asObject3D(root);\n const rows = new Map<string, GeometryRow>();\n const add = (key: string, triangles: number, uses: number) => {\n const row = rows.get(key) ?? { geometry: key, uses: 0, triangles, total: 0, share: 0 };\n\n row.uses += uses;\n row.total += triangles * uses;\n rows.set(key, row);\n };\n\n walk(scene, { skip: options.skip }, (object) => {\n if (!isMesh(object) || !object.geometry.attributes.position) {\n return;\n }\n\n if (isBatched(object)) {\n const name = [...pathOf(object, scene), meshName(object)].join(\"/\");\n\n for (const [id, { instances, triangles }] of batchedGeometries(object)) {\n add(options.label?.(object, id) ?? `${name}#${id}`, triangles, instances);\n }\n\n return;\n }\n\n const geometry = object.geometry;\n\n add(\n geometry.name || `${geometry.type}:${geometry.uuid.slice(0, 6)}`,\n triangleCount(geometry),\n isInstanced(object) ? object.count : 1,\n );\n });\n\n const all = [...rows.values()];\n const sum = all.reduce((total, row) => total + row.total, 0);\n\n for (const row of all) {\n row.share = sum > 0 ? Math.round((row.total / sum) * 1000) / 1000 : 0;\n\n if (options.budget !== undefined) {\n row.overBudget = row.total / Math.max(sum, 1) > options.budget;\n }\n }\n\n return all.sort((a, b) => b.total - a.total);\n};\n\n/**\n * Draw calls one main pass makes: one per visible mesh, line or point cloud, or one per material\n * group for a mesh with several materials. Before frustum culling; each shadow-casting light adds\n * one per caster. `recordDrawLedger` counts the real thing.\n */\nexport const countDraws = (root: AnyObject3D, options: TriangleOptions = {}) => {\n let draws = 0;\n\n walk(asObject3D(root), { skip: options.skip, visibleOnly: true }, (object) => {\n if (!isDrawable(object)) {\n return;\n }\n\n const drawable = object as Partial<Mesh>;\n\n const material = drawable.material;\n\n if (Array.isArray(material)) {\n const groups = drawable.geometry?.groups ?? [];\n\n draws += groups.filter(\n (group) => material[group.materialIndex ?? 0]?.visible !== false,\n ).length;\n } else if (material?.visible !== false) {\n draws += 1;\n }\n });\n\n return draws;\n};\n","/**\n * Z-fighting found by geometry instead of by eye: pairs of meshes with triangles in (nearly) one\n * plane, facing the same way, overlapping. Those flicker wherever depth precision runs out.\n *\n * Triangles are bucketed by their quantised plane (normal and offset), so only triangles in\n * neighbouring buckets are compared. A pair counts only when each triangle's corners lie within\n * `gap` of the other's plane, measured at the triangles: the offsets are measured from the world\n * origin, and far from it two faces a degree or two apart can share one while metres apart where\n * they stand. Overlap in the shared plane is a separating-axis test, then clipped for its area.\n *\n * Left out, as they can't fight: hidden objects, instanced and batched meshes (and geometry a\n * shader positions, `InstancedBufferGeometry`), pairs where neither side writes depth or one\n * doesn't test it, and pairs where either material has a polygon offset (the usual decal fix).\n */\nimport type { Material, Mesh } from \"three\";\n\nimport {\n type AnyObject3D,\n asObject3D,\n hasTag,\n isBatched,\n isInstanced,\n isMesh,\n meshLabel,\n type Skip,\n tagOf,\n walk,\n withHidden,\n} from \"./scene.ts\";\n\nexport type ZFightingOptions = {\n /**\n * How close two planes count as one, in world units. 0.004 (4 mm at 1 unit = 1 m) is about what\n * depth precision loses with a camera ~50 m out. Scale it with your units.\n */\n gap?: number;\n /** Also compare triangles within one mesh: for merged geometry whose parts share a buffer. */\n self?: boolean;\n /**\n * Only meshes inside a tagged object (`userData[tagKey]`), so a studio's ground and props stay\n * out. Defaults to true when anything under the root is tagged, false otherwise.\n */\n tagged?: boolean;\n tagKey?: string;\n skip?: Skip | undefined;\n};\n\nexport type ZFightingRow = {\n /** `path/to/mesh [material] @ (x, y, z)`: named ancestors, and the centre in world space. */\n a: string;\n b: string;\n /** Overlapping triangle pairs between the two. */\n triangles: number;\n /**\n * The planes they share, most overlap first: `z = -3.000, facing +z` when axis-aligned, else\n * `(nx, ny, nz) · p = d`. Both faces point along the normal: opposite-facing faces never pair.\n */\n planes: string[];\n /** Where they overlap: the area in world units², and its centre in world space. */\n overlap: { area: number; centre: [number, number, number] };\n /** Identical pairs (same labels, triangles and planes) folded into this row. */\n count: number;\n /** Not enumerable: kept out of JSON and `console.table`. The first pair folded in. */\n readonly meshes: readonly [Mesh, Mesh];\n /** Not enumerable: every pair folded in. */\n readonly pairs: readonly (readonly [Mesh, Mesh])[];\n};\n\n/** Normals quantised to 1/40: neighbouring buckets are searched, so this only sets bucket size. */\nconst QUANT = 40;\n/** Normals this aligned count as parallel (about 2.6°). */\nconst PARALLEL = 0.999;\n\ntype Part = {\n mesh: Mesh;\n label: string;\n min: number[];\n max: number[];\n /** Some material writes depth / tests it / offsets it. */\n writes: boolean;\n tests: boolean;\n offset: boolean;\n};\n\nconst round = (value: number, places = 2) => {\n const scale = 10 ** places;\n const rounded = Math.round(value * scale) / scale;\n\n return rounded === 0 ? 0 : rounded;\n};\n\nconst labelOf = (part: Part) => {\n const centre = part.min.map((low, axis) => round((low + part.max[axis]!) / 2));\n\n return `${part.label} @ (${centre.join(\", \")})`;\n};\n\nconst fixed = (value: number) => round(value, 3).toFixed(3);\n\n/** `z = -3.000, facing +z` for an axis-aligned plane, `(nx, ny, nz) · p = d` for any other. */\nconst planeLabel = (nx: number, ny: number, nz: number, d: number) => {\n for (const [axis, value] of [\n [\"x\", nx],\n [\"y\", ny],\n [\"z\", nz],\n ] as const) {\n if (Math.abs(value) > 0.9999) {\n return `${axis} = ${fixed(d / value)}, facing ${value > 0 ? \"+\" : \"-\"}${axis}`;\n }\n }\n\n return `(${fixed(nx)}, ${fixed(ny)}, ${fixed(nz)}) · p = ${fixed(d)}`;\n};\n\nconst materialsOf = (mesh: Mesh) =>\n (Array.isArray(mesh.material) ? mesh.material : [mesh.material]).filter(\n (material): material is Material => material !== undefined && material !== null,\n );\n\n/** Whether two parts can fight: one writes depth the other tests, and neither is offset. */\nconst canFight = (a: Part, b: Part) =>\n !a.offset && !b.offset && ((a.writes && b.tests) || (b.writes && a.tests));\n\n/** Pairs of meshes that z-fight, most overlapping triangles first. `[]` is a pass. */\nexport const findZFighting = (\n scene: AnyObject3D,\n options: ZFightingOptions = {},\n): ZFightingRow[] => {\n const root = asObject3D(scene);\n const gap = options.gap ?? 0.004;\n const self = options.self ?? false;\n const tagKey = options.tagKey ?? \"studioObject\";\n const tagged = options.tagged ?? hasTag(root, tagKey, options.skip);\n\n root.updateWorldMatrix(true, true);\n\n const parts: Part[] = [];\n // Per triangle: its part, plane (nx, ny, nz, d) and world corners (9 numbers).\n const owner: number[] = [];\n const planes: number[] = [];\n const corners: number[] = [];\n const keys: [number, number, number, number][] = [];\n const buckets = new Map<string, number[]>();\n const keyOf = (x: number, y: number, z: number, d: number) => `${x},${y},${z},${d}`;\n\n walk(root, { skip: options.skip, visibleOnly: true }, (object) => {\n if (\n !isMesh(object) ||\n isInstanced(object) ||\n isBatched(object) ||\n (object.geometry as { isInstancedBufferGeometry?: boolean }).isInstancedBufferGeometry ===\n true ||\n !object.geometry.attributes.position\n ) {\n return;\n }\n\n if (tagged && tagOf(object, tagKey) === null) {\n return;\n }\n\n const materials = materialsOf(object).filter((material) => material.visible !== false);\n\n if (materials.length === 0) {\n return;\n }\n\n const part = parts.length;\n const { index, attributes } = object.geometry;\n const position = attributes.position!;\n const e = object.matrixWorld.elements;\n const min = [Infinity, Infinity, Infinity];\n const max = [-Infinity, -Infinity, -Infinity];\n const count = index?.count ?? position.count;\n const world = [0, 0, 0, 0, 0, 0, 0, 0, 0];\n\n parts.push({\n mesh: object,\n label: meshLabel(object, root, tagKey),\n min,\n max,\n writes: materials.some((material) => material.depthWrite !== false),\n tests: materials.some((material) => material.depthTest !== false),\n offset: materials.some(\n (material) =>\n material.polygonOffset === true &&\n (material.polygonOffsetFactor !== 0 || material.polygonOffsetUnits !== 0),\n ),\n });\n\n for (let k = 0; k + 2 < count; k += 3) {\n for (let c = 0; c < 3; c += 1) {\n const v = index?.getX(k + c) ?? k + c;\n const x = position.getX(v);\n const y = position.getY(v);\n const z = position.getZ(v);\n\n for (let axis = 0; axis < 3; axis += 1) {\n const value = e[axis]! * x + e[axis + 4]! * y + e[axis + 8]! * z + e[axis + 12]!;\n\n world[c * 3 + axis] = value;\n min[axis] = Math.min(min[axis]!, value);\n max[axis] = Math.max(max[axis]!, value);\n }\n }\n\n const [ax, ay, az, bx, by, bz, cx, cy, cz] = world as [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n ];\n const ux = bx - ax;\n const uy = by - ay;\n const uz = bz - az;\n const vx = cx - ax;\n const vy = cy - ay;\n const vz = cz - az;\n let nx = uy * vz - uz * vy;\n let ny = uz * vx - ux * vz;\n let nz = ux * vy - uy * vx;\n const length = Math.hypot(nx, ny, nz);\n\n // Slivers under ~1e-5 m² show nothing.\n if (!(length / 2 >= 1e-5)) {\n continue;\n }\n\n nx /= length;\n ny /= length;\n nz /= length;\n\n const d = nx * ax + ny * ay + nz * az;\n const id = owner.length;\n const key: [number, number, number, number] = [\n Math.round(nx * QUANT),\n Math.round(ny * QUANT),\n Math.round(nz * QUANT),\n Math.round(d / gap),\n ];\n const bucket = buckets.get(keyOf(...key));\n\n owner.push(part);\n planes.push(nx, ny, nz, d);\n corners.push(...world);\n keys.push(key);\n\n if (bucket) {\n bucket.push(id);\n } else {\n buckets.set(keyOf(...key), [id]);\n }\n }\n });\n\n // Overlap in s's plane, with a margin so triangles merely sharing an edge don't count.\n const margin = gap / 2;\n const flat = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n // s's plane axes from the last `overlaps`: u, then v.\n const frame = [0, 0, 0, 0, 0, 0];\n\n const overlaps = (s: number, t: number) => {\n const S = s * 9;\n const T = t * 9;\n let ux = corners[S + 3]! - corners[S]!;\n let uy = corners[S + 4]! - corners[S + 1]!;\n let uz = corners[S + 5]! - corners[S + 2]!;\n const length = Math.hypot(ux, uy, uz) || 1;\n\n ux /= length;\n uy /= length;\n uz /= length;\n\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const vx = ny * uz - nz * uy;\n const vy = nz * ux - nx * uz;\n const vz = nx * uy - ny * ux;\n\n frame[0] = ux;\n frame[1] = uy;\n frame[2] = uz;\n frame[3] = vx;\n frame[4] = vy;\n frame[5] = vz;\n\n // Corners 0–2 are s, 3–5 are t, as (x, y) in the plane.\n for (let i = 0; i < 6; i += 1) {\n const at = i < 3 ? S + i * 3 : T + (i - 3) * 3;\n const px = corners[at]!;\n const py = corners[at + 1]!;\n const pz = corners[at + 2]!;\n\n flat[i * 2] = px * ux + py * uy + pz * uz;\n flat[i * 2 + 1] = px * vx + py * vy + pz * vz;\n }\n\n for (let edge = 0; edge < 6; edge += 1) {\n const from = edge;\n const to = edge < 3 ? (edge + 1) % 3 : 3 + ((edge + 1) % 3);\n const ex = flat[to * 2]! - flat[from * 2]!;\n const ey = flat[to * 2 + 1]! - flat[from * 2 + 1]!;\n const edgeLength = Math.hypot(ex, ey) || 1;\n const axisX = -ey / edgeLength;\n const axisY = ex / edgeLength;\n let p0 = Infinity;\n let p1 = -Infinity;\n let q0 = Infinity;\n let q1 = -Infinity;\n\n for (let i = 0; i < 6; i += 1) {\n const value = flat[i * 2]! * axisX + flat[i * 2 + 1]! * axisY;\n\n if (i < 3) {\n p0 = Math.min(p0, value);\n p1 = Math.max(p1, value);\n } else {\n q0 = Math.min(q0, value);\n q1 = Math.max(q1, value);\n }\n }\n\n if (Math.min(p1, q1) - Math.max(p0, q0) < margin) {\n return false;\n }\n }\n\n return true;\n };\n\n /**\n * The overlap `overlaps` just found, as area and centroid in s's plane: t's triangle clipped to\n * s's (Sutherland–Hodgman), then the shoelace formula.\n */\n const clipped = () => {\n const turn =\n (flat[2]! - flat[0]!) * (flat[5]! - flat[1]!) - (flat[4]! - flat[0]!) * (flat[3]! - flat[1]!);\n // s's corners counter-clockwise.\n const order = turn >= 0 ? [0, 1, 2] : [0, 2, 1];\n let polygon = flat.slice(6, 12);\n\n for (let edge = 0; edge < 3 && polygon.length >= 6; edge += 1) {\n const ax = flat[order[edge]! * 2]!;\n const ay = flat[order[edge]! * 2 + 1]!;\n const ex = flat[order[(edge + 1) % 3]! * 2]! - ax;\n const ey = flat[order[(edge + 1) % 3]! * 2 + 1]! - ay;\n const next: number[] = [];\n const n = polygon.length / 2;\n\n for (let i = 0; i < n; i += 1) {\n const px = polygon[i * 2]!;\n const py = polygon[i * 2 + 1]!;\n const qx = polygon[((i + 1) % n) * 2]!;\n const qy = polygon[((i + 1) % n) * 2 + 1]!;\n const dp = ex * (py - ay) - ey * (px - ax);\n const dq = ex * (qy - ay) - ey * (qx - ax);\n\n if (dp >= 0) {\n next.push(px, py);\n }\n\n if (dp >= 0 !== dq >= 0) {\n const k = dp / (dp - dq);\n\n next.push(px + (qx - px) * k, py + (qy - py) * k);\n }\n }\n\n polygon = next;\n }\n\n let twice = 0;\n let cx = 0;\n let cy = 0;\n const n = polygon.length / 2;\n\n for (let i = 0; i < n; i += 1) {\n const x0 = polygon[i * 2]!;\n const y0 = polygon[i * 2 + 1]!;\n const x1 = polygon[((i + 1) % n) * 2]!;\n const y1 = polygon[((i + 1) % n) * 2 + 1]!;\n const cross = x0 * y1 - x1 * y0;\n\n twice += cross;\n cx += (x0 + x1) * cross;\n cy += (y0 + y1) * cross;\n }\n\n return Math.abs(twice) < 1e-12\n ? { area: 0, x: 0, y: 0 }\n : { area: Math.abs(twice) / 2, x: cx / (3 * twice), y: cy / (3 * twice) };\n };\n\n // Every corner of t within `gap` of s's plane, measured where t stands.\n const onPlane = (s: number, t: number) => {\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const d = planes[s * 4 + 3]!;\n\n for (let c = 0; c < 3; c += 1) {\n const at = t * 9 + c * 3;\n\n if (Math.abs(nx * corners[at]! + ny * corners[at + 1]! + nz * corners[at + 2]! - d) > gap) {\n return false;\n }\n }\n\n return true;\n };\n\n type Pair = {\n a: number;\n b: number;\n triangles: number;\n area: number;\n /** Area-weighted sums of the overlap's centre. */\n centre: [number, number, number];\n planes: Map<string, number>;\n };\n\n const pairs = new Map<string, Pair>();\n\n // Every neighbouring bucket, so near-equal planes that round apart still meet.\n for (let s = 0; s < owner.length; s += 1) {\n const [kx, ky, kz, kd] = keys[s]!;\n\n for (let i = -1; i <= 1; i += 1) {\n for (let j = -1; j <= 1; j += 1) {\n for (let k = -1; k <= 1; k += 1) {\n for (let l = -1; l <= 1; l += 1) {\n for (const t of buckets.get(keyOf(kx + i, ky + j, kz + k, kd + l)) ?? []) {\n const a = owner[s]!;\n const b = owner[t]!;\n\n if (\n t <= s ||\n (a === b && !self) ||\n !canFight(parts[a]!, parts[b]!) ||\n planes[s * 4]! * planes[t * 4]! +\n planes[s * 4 + 1]! * planes[t * 4 + 1]! +\n planes[s * 4 + 2]! * planes[t * 4 + 2]! <\n PARALLEL ||\n !onPlane(s, t) ||\n !onPlane(t, s) ||\n !overlaps(s, t)\n ) {\n continue;\n }\n\n const key = a < b ? `${a}:${b}` : `${b}:${a}`;\n const pair = pairs.get(key) ?? {\n a: Math.min(a, b),\n b: Math.max(a, b),\n triangles: 0,\n area: 0,\n centre: [0, 0, 0],\n planes: new Map<string, number>(),\n };\n const nx = planes[s * 4]!;\n const ny = planes[s * 4 + 1]!;\n const nz = planes[s * 4 + 2]!;\n const d = planes[s * 4 + 3]!;\n const { area, x, y } = clipped();\n const plane = planeLabel(nx, ny, nz, d);\n\n pair.triangles += 1;\n pair.area += area;\n pair.centre[0] += area * (x * frame[0]! + y * frame[3]! + d * nx);\n pair.centre[1] += area * (x * frame[1]! + y * frame[4]! + d * ny);\n pair.centre[2] += area * (x * frame[2]! + y * frame[5]! + d * nz);\n pair.planes.set(plane, (pair.planes.get(plane) ?? 0) + area);\n pairs.set(key, pair);\n }\n }\n }\n }\n }\n }\n\n // Fold identical pairs (pooled copies of one prop, all at one spot) into one row each.\n const rows = new Map<string, ZFightingRow & { pairs: (readonly [Mesh, Mesh])[] }>();\n\n for (const pair of [...pairs.values()].sort((x, y) => y.triangles - x.triangles)) {\n const meshes = [parts[pair.a]!.mesh, parts[pair.b]!.mesh] as const;\n const a = labelOf(parts[pair.a]!);\n const b = labelOf(parts[pair.b]!);\n const planeLabels = [...pair.planes.entries()]\n .sort((x, y) => y[1] - x[1])\n .map(([plane]) => plane);\n const key = JSON.stringify([a, b, pair.triangles, planeLabels]);\n const folded = rows.get(key);\n\n if (folded) {\n folded.count += 1;\n folded.pairs.push(meshes);\n continue;\n }\n\n const weight = pair.area > 0 ? pair.area : 1;\n const row = withHidden(\n withHidden(\n {\n a,\n b,\n triangles: pair.triangles,\n planes: planeLabels,\n overlap: {\n area: round(pair.area, 4),\n centre: pair.centre.map((sum) => round(sum / weight)) as [number, number, number],\n },\n count: 1,\n },\n \"meshes\",\n meshes,\n ),\n \"pairs\",\n [meshes],\n );\n\n rows.set(key, row);\n }\n\n return [...rows.values()];\n};\n","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, \"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 } = options;\n const skip = scopeOf(root, options);\n const keyed = tagKey === undefined ? { skip } : { skip, 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<AuditSceneOptions, \"gap\" | \"skip\" | \"tag\" | \"tagKey\"> & {\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 } = options;\n const skip = scopeOf(root, options);\n const keyed = tagKey === undefined ? { skip } : { skip, 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 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":"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"}