@helix3/helix-cli 0.1.20-helix3.381 → 0.1.20-helix3.382

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.
Files changed (31) hide show
  1. package/dist/continuum/adminPackagePublishCommand.d.ts +1 -0
  2. package/dist/continuum/adminPackagePublishCommand.js +2 -1
  3. package/dist/continuum/adminPackagePublishCommand.js.map +1 -1
  4. package/dist/continuum/missingNormals.d.ts +48 -0
  5. package/dist/continuum/missingNormals.js +99 -0
  6. package/dist/continuum/missingNormals.js.map +1 -0
  7. package/dist/glb/index.d.ts +1 -0
  8. package/dist/glb/index.js +7 -1
  9. package/dist/glb/index.js.map +1 -1
  10. package/dist/glb/smooth-normals.d.ts +98 -0
  11. package/dist/glb/smooth-normals.js +395 -0
  12. package/dist/glb/smooth-normals.js.map +1 -0
  13. package/dist/index.js +53 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/tsconfig.build.tsbuildinfo +1 -1
  16. package/dist/visualQa/browser.js +2 -1
  17. package/dist/visualQa/browser.js.map +1 -1
  18. package/dist/visualQa/candidate.d.ts +2 -0
  19. package/dist/visualQa/candidate.js +1 -1
  20. package/dist/visualQa/candidate.js.map +1 -1
  21. package/dist/visualQa/command.d.ts +1 -1
  22. package/dist/visualQa/command.js +9 -4
  23. package/dist/visualQa/command.js.map +1 -1
  24. package/package.json +1 -1
  25. package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
  26. package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
  27. package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
  28. package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
  29. package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
  30. package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
  31. package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
@@ -4,6 +4,7 @@ export interface ContinuumAdminPackagePublishDependencies {
4
4
  requireAuth(): CliCredentials;
5
5
  publishLegacyItemPackage(credentials: CliCredentials, path: string, options: {
6
6
  confirmPublicListing: boolean;
7
+ allowMissingNormals?: boolean;
7
8
  }): Promise<unknown>;
8
9
  printJson(value: unknown): void;
9
10
  fail(message: string): never;
@@ -8,9 +8,10 @@ function registerContinuumAdminPackagePublishCommand(continuum, dependencies) {
8
8
  .description('Admin-only: publish and pin a legacy Universal Item Package through Continuum')
9
9
  .argument('<input>', 'publication JSON; file object paths are resolved beside this file')
10
10
  .option('--confirm-public-listing', 'explicitly authorize public Vault/Marketplace Listings for this CLI invocation')
11
+ .option('--allow-missing-normals', 'publish GLB objects exactly as written even when they have no NORMAL attribute (default: smooth normals are written first)')
11
12
  .action(async (path, options) => {
12
13
  try {
13
- const receipt = await dependencies.publishLegacyItemPackage(dependencies.requireAuth(), path, { confirmPublicListing: options.confirmPublicListing === true });
14
+ const receipt = await dependencies.publishLegacyItemPackage(dependencies.requireAuth(), path, { confirmPublicListing: options.confirmPublicListing === true, ...(options.allowMissingNormals ? { allowMissingNormals: true } : {}) });
14
15
  dependencies.printJson(receipt);
15
16
  }
16
17
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"adminPackagePublishCommand.js","sourceRoot":"","sources":["../../src/continuum/adminPackagePublishCommand.ts"],"names":[],"mappings":";;AAeA,kGAqBC;AAtBD,4FAA4F;AAC5F,SAAgB,2CAA2C,CACzD,SAAkB,EAClB,YAAsD;IAEtD,SAAS;SACN,OAAO,CAAC,6BAA6B,CAAC;SACtC,WAAW,CAAC,+EAA+E,CAAC;SAC5F,QAAQ,CAAC,SAAS,EAAE,mEAAmE,CAAC;SACxF,MAAM,CAAC,0BAA0B,EAAE,gFAAgF,CAAC;SACpH,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAA2C,EAAE,EAAE;QAC1E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,wBAAwB,CACzD,YAAY,CAAC,WAAW,EAAE,EAC1B,IAAI,EACJ,EAAE,oBAAoB,EAAE,OAAO,CAAC,oBAAoB,KAAK,IAAI,EAAE,CAChE,CAAC;YACF,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,YAAY,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"adminPackagePublishCommand.js","sourceRoot":"","sources":["../../src/continuum/adminPackagePublishCommand.ts"],"names":[],"mappings":";;AAeA,kGAsBC;AAvBD,4FAA4F;AAC5F,SAAgB,2CAA2C,CACzD,SAAkB,EAClB,YAAsD;IAEtD,SAAS;SACN,OAAO,CAAC,6BAA6B,CAAC;SACtC,WAAW,CAAC,+EAA+E,CAAC;SAC5F,QAAQ,CAAC,SAAS,EAAE,mEAAmE,CAAC;SACxF,MAAM,CAAC,0BAA0B,EAAE,gFAAgF,CAAC;SACpH,MAAM,CAAC,yBAAyB,EAAE,4HAA4H,CAAC;SAC/J,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAA0E,EAAE,EAAE;QACzG,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,wBAAwB,CACzD,YAAY,CAAC,WAAW,EAAE,EAC1B,IAAI,EACJ,EAAE,oBAAoB,EAAE,OAAO,CAAC,oBAAoB,KAAK,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CACvI,CAAC;YACF,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,YAAY,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { ContinuumPackageVersion } from '@hypersoniclabs/helix-manifest';
2
+ import { type EnsureNormalsReport } from '../glb/smooth-normals';
3
+ import type { ContinuumObjectPublication } from './types';
4
+ /**
5
+ * Publication-time guard for a Package's GLB objects.
6
+ *
7
+ * A GLB with no NORMAL attribute renders faceted everywhere (glTF 2.0 makes
8
+ * every runtime fall back to flat face normals), and a sealed Package Version
9
+ * is immutable: the bytes you seal are the bytes every owner gets, forever.
10
+ * The only moment the defect is cheap to fix is before sealing, so
11
+ * `helix continuum publish-package` writes smooth NORMALs into any GLB object
12
+ * that lacks them, then re-derives the CID and byte counts the descriptor
13
+ * declared.
14
+ *
15
+ * That re-derivation is only sound when the descriptor names the GLB solely
16
+ * through Package Version object references (`root`, `variants[].object`,
17
+ * `source`). A vehicle definition or any other sibling object that quotes the
18
+ * mesh by CID would be left pointing at bytes that no longer exist, so in that
19
+ * case nothing is rewritten and the publish is refused with the remedy
20
+ * (`helix glb add-normals`, rebuild the descriptor) or an explicit opt-out.
21
+ */
22
+ export interface MissingNormalsOptions {
23
+ creaseAngleDeg?: number;
24
+ /** Receives one line per rewritten object. Defaults to stderr so a receipt on stdout stays machine-readable. */
25
+ log?: (message: string) => void;
26
+ }
27
+ export interface NormalsRewrite {
28
+ filename: string;
29
+ oldCid: string;
30
+ newCid: string;
31
+ oldBytes: number;
32
+ newBytes: number;
33
+ report: EnsureNormalsReport;
34
+ }
35
+ export declare class MissingNormalsError extends Error {
36
+ readonly filename: string;
37
+ readonly cid: string;
38
+ readonly fixablePrimitives: number;
39
+ constructor(filename: string, cid: string, fixablePrimitives: number, detail: string);
40
+ }
41
+ /**
42
+ * Adds smooth normals to every GLB object that lacks them, rewriting the
43
+ * Package Version and the object list in place. Returns what changed.
44
+ *
45
+ * Only inline (file) objects are examined: an object adopted by CID from an
46
+ * existing asset is already published and cannot be re-derived here.
47
+ */
48
+ export declare function addMissingNormalsToPublication(packageVersion: ContinuumPackageVersion, objects: ContinuumObjectPublication[], options?: MissingNormalsOptions): Promise<NormalsRewrite[]>;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MissingNormalsError = void 0;
4
+ exports.addMissingNormalsToPublication = addMissingNormalsToPublication;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const smooth_normals_1 = require("../glb/smooth-normals");
7
+ class MissingNormalsError extends Error {
8
+ filename;
9
+ cid;
10
+ fixablePrimitives;
11
+ constructor(filename, cid, fixablePrimitives, detail) {
12
+ super(`${filename} has ${fixablePrimitives} mesh primitive(s) with no NORMAL attribute, so it renders faceted everywhere, ` +
13
+ `and ${detail}. Fix the mesh first: \`helix glb add-normals ${filename}\`, rebuild the descriptor around the new file, ` +
14
+ 'and publish again. Pass --allow-missing-normals only for a mesh that is deliberately flat-shaded.');
15
+ this.filename = filename;
16
+ this.cid = cid;
17
+ this.fixablePrimitives = fixablePrimitives;
18
+ this.name = 'MissingNormalsError';
19
+ }
20
+ }
21
+ exports.MissingNormalsError = MissingNormalsError;
22
+ const GLB_MEDIA_TYPE = 'model/gltf-binary';
23
+ const cidOf = (bytes) => `cid:sha256:${(0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex')}`;
24
+ function isReference(value) {
25
+ return (typeof value === 'object' &&
26
+ value !== null &&
27
+ typeof value.cid === 'string' &&
28
+ typeof value.bytes === 'number');
29
+ }
30
+ /** Visits every Package Version object reference carrying `cid`, in place. */
31
+ function replaceReferences(node, oldCid, next) {
32
+ if (Array.isArray(node))
33
+ return node.reduce((n, child) => n + replaceReferences(child, oldCid, next), 0);
34
+ if (typeof node !== 'object' || node === null)
35
+ return 0;
36
+ let replaced = 0;
37
+ if (isReference(node) && node.cid === oldCid) {
38
+ node.cid = next.cid;
39
+ node.bytes = next.bytes;
40
+ replaced++;
41
+ }
42
+ for (const value of Object.values(node))
43
+ replaced += replaceReferences(value, oldCid, next);
44
+ return replaced;
45
+ }
46
+ function mentions(bytes, text) {
47
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).includes(text);
48
+ }
49
+ /**
50
+ * Adds smooth normals to every GLB object that lacks them, rewriting the
51
+ * Package Version and the object list in place. Returns what changed.
52
+ *
53
+ * Only inline (file) objects are examined: an object adopted by CID from an
54
+ * existing asset is already published and cannot be re-derived here.
55
+ */
56
+ async function addMissingNormalsToPublication(packageVersion, objects, options = {}) {
57
+ const log = options.log ?? ((message) => console.error(message));
58
+ const creaseAngleDeg = options.creaseAngleDeg ?? smooth_normals_1.DEFAULT_CREASE_ANGLE_DEG;
59
+ const rewrites = [];
60
+ for (const object of objects) {
61
+ if (object.source.kind !== 'inline')
62
+ continue;
63
+ const filename = object.source.filename ?? object.reference.cid;
64
+ if (object.reference.mediaType !== GLB_MEDIA_TYPE && !/\.glb$/i.test(filename))
65
+ continue;
66
+ const original = object.source.bytes;
67
+ const { bytes, report } = await (0, smooth_normals_1.ensureSmoothNormalsGlb)(original, { creaseAngleDeg });
68
+ if (!report.changed)
69
+ continue;
70
+ const oldCid = object.reference.cid;
71
+ const oldHex = oldCid.slice('cid:sha256:'.length);
72
+ const quotedBy = objects.find((other) => other !== object && other.source.kind === 'inline' && mentions(other.source.bytes, oldHex));
73
+ if (quotedBy) {
74
+ throw new MissingNormalsError(filename, oldCid, report.fixable, `another object in this publication (${quotedBy.reference.role ?? quotedBy.reference.cid}) quotes the mesh by its CID, so it cannot be rewritten safely here`);
75
+ }
76
+ const next = { cid: cidOf(bytes), bytes: bytes.byteLength };
77
+ const references = replaceReferences(packageVersion, oldCid, next);
78
+ if (references === 0) {
79
+ throw new MissingNormalsError(filename, oldCid, report.fixable, 'the Package Version does not reference it as root, variant or source, so the descriptor cannot be re-derived safely here');
80
+ }
81
+ object.reference = { ...object.reference, cid: next.cid, bytes: next.bytes };
82
+ object.source = { ...object.source, bytes };
83
+ const closure = packageVersion.expectedClosure;
84
+ if (closure)
85
+ closure.totalBytes += next.bytes - original.byteLength;
86
+ rewrites.push({
87
+ filename,
88
+ oldCid,
89
+ newCid: next.cid,
90
+ oldBytes: original.byteLength,
91
+ newBytes: next.bytes,
92
+ report,
93
+ });
94
+ log(`✔ ${filename}: ${report.fixable} primitive(s) had no NORMAL; wrote smooth normals ` +
95
+ `(crease ${creaseAngleDeg}°, ${original.byteLength} → ${next.bytes} bytes, ${oldCid.slice(0, 22)}… → ${next.cid.slice(0, 22)}…)`);
96
+ }
97
+ return rewrites;
98
+ }
99
+ //# sourceMappingURL=missingNormals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missingNormals.js","sourceRoot":"","sources":["../../src/continuum/missingNormals.ts"],"names":[],"mappings":"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"}
@@ -9,3 +9,4 @@
9
9
  */
10
10
  export { GlbParseError, summarizeGlb, type GlbBoundingBox, type GlbSummary, type GlbTextureInfo, } from './glb-summary';
11
11
  export { measureDrawCalls, type DrawCallBreakdown, type DrawCallOffender, } from './draw-calls';
12
+ export { DEFAULT_CREASE_ANGLE_DEG, addSmoothNormals, auditGlbNormals, auditNormals, ensureSmoothNormalsGlb, type EnsureNormalsReport, type NormalsAudit, type NormalsSkipReason, type SmoothNormalsOptions, type SmoothNormalsResult, } from './smooth-normals';
package/dist/glb/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.measureDrawCalls = exports.summarizeGlb = exports.GlbParseError = void 0;
3
+ exports.ensureSmoothNormalsGlb = exports.auditNormals = exports.auditGlbNormals = exports.addSmoothNormals = exports.DEFAULT_CREASE_ANGLE_DEG = exports.measureDrawCalls = exports.summarizeGlb = exports.GlbParseError = void 0;
4
4
  /**
5
5
  * `@hypersoniclabs/helix-cli/glb` — shared GLB pipeline modules.
6
6
  *
@@ -15,4 +15,10 @@ Object.defineProperty(exports, "GlbParseError", { enumerable: true, get: functio
15
15
  Object.defineProperty(exports, "summarizeGlb", { enumerable: true, get: function () { return glb_summary_1.summarizeGlb; } });
16
16
  var draw_calls_1 = require("./draw-calls");
17
17
  Object.defineProperty(exports, "measureDrawCalls", { enumerable: true, get: function () { return draw_calls_1.measureDrawCalls; } });
18
+ var smooth_normals_1 = require("./smooth-normals");
19
+ Object.defineProperty(exports, "DEFAULT_CREASE_ANGLE_DEG", { enumerable: true, get: function () { return smooth_normals_1.DEFAULT_CREASE_ANGLE_DEG; } });
20
+ Object.defineProperty(exports, "addSmoothNormals", { enumerable: true, get: function () { return smooth_normals_1.addSmoothNormals; } });
21
+ Object.defineProperty(exports, "auditGlbNormals", { enumerable: true, get: function () { return smooth_normals_1.auditGlbNormals; } });
22
+ Object.defineProperty(exports, "auditNormals", { enumerable: true, get: function () { return smooth_normals_1.auditNormals; } });
23
+ Object.defineProperty(exports, "ensureSmoothNormalsGlb", { enumerable: true, get: function () { return smooth_normals_1.ensureSmoothNormalsGlb; } });
18
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glb/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6CAMuB;AALrB,4GAAA,aAAa,OAAA;AACb,2GAAA,YAAY,OAAA;AAMd,2CAIsB;AAHpB,8GAAA,gBAAgB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glb/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6CAMuB;AALrB,4GAAA,aAAa,OAAA;AACb,2GAAA,YAAY,OAAA;AAMd,2CAIsB;AAHpB,8GAAA,gBAAgB,OAAA;AAKlB,mDAW0B;AAVxB,0HAAA,wBAAwB,OAAA;AACxB,kHAAA,gBAAgB,OAAA;AAChB,iHAAA,eAAe,OAAA;AACf,8GAAA,YAAY,OAAA;AACZ,wHAAA,sBAAsB,OAAA"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Smooth vertex NORMALs for meshes that ship without any.
3
+ *
4
+ * glTF 2.0 obliges a renderer to fall back to FLAT per-face normals when a
5
+ * primitive has no NORMAL attribute. three.js, the HELIX viewer and the
6
+ * thumbnail renderer all do exactly that, so a mesh exported without normals
7
+ * (Meshy/Tripo output, `trimesh` exports, hand-rolled writers) renders as a
8
+ * faceted polygon soup no matter how dense it is. The fix belongs in the asset:
9
+ * the renderers are correct.
10
+ *
11
+ * This module synthesises the missing attribute deterministically:
12
+ * - vertices are grouped by POSITION (a UV seam duplicates a vertex, and a
13
+ * seam must NOT become a lighting seam),
14
+ * - each triangle corner gets the angle-weighted average of the face normals
15
+ * around its position group that lie within `creaseAngleDeg` of the
16
+ * corner's own face, so hard edges (a car's panel lines, a table's corners)
17
+ * stay crisp while curved surfaces shade smooth,
18
+ * - a vertex is split only where the resulting corner normals differ.
19
+ *
20
+ * Invariants:
21
+ * - A primitive that already carries NORMAL is NEVER touched. Authored
22
+ * normals are the creator's intent, flat or smooth.
23
+ * - Only TRIANGLES primitives are shaded. Lines, points, strips and fans have
24
+ * no meaningful surface normal here.
25
+ * - Primitives with morph targets are left alone: a split vertex table would
26
+ * have to be mirrored into every target, and a face rig that loses a
27
+ * blendshape is a worse defect than a faceted one. They are reported.
28
+ * - Primitives whose material is KHR_materials_unlit never read a normal and
29
+ * are left alone, so an unlit asset round-trips byte-identical.
30
+ * - A GLB with nothing to fix comes back as the SAME bytes, never a
31
+ * re-serialisation: no needless change of content identity.
32
+ *
33
+ * The backend ships a sibling of this routine for generated (Dreamer) props
34
+ * (`helix-backend-api/src/universal-items/optimization/smooth-normals.ts`); the
35
+ * two share the algorithm, not the crease default.
36
+ */
37
+ import type { Document } from '@gltf-transform/core';
38
+ /**
39
+ * Edges sharper than this stay hard. 40 degrees keeps 45-degree bevels,
40
+ * panel-line steps and box corners crisp while smoothing the 5-25 degree facets
41
+ * of a tessellated curve; Dreamer's 60 is looser because generated props lean
42
+ * on a baked normal map for their hard detail.
43
+ */
44
+ export declare const DEFAULT_CREASE_ANGLE_DEG = 40;
45
+ export interface SmoothNormalsOptions {
46
+ creaseAngleDeg?: number;
47
+ }
48
+ /** Why a primitive without NORMAL was left without one. */
49
+ export type NormalsSkipReason = 'morph-targets' | 'non-triangles' | 'no-position' | 'no-faces' | 'unlit';
50
+ export interface NormalsAudit {
51
+ /** Every primitive in the document. */
52
+ primitives: number;
53
+ /** Primitives that already carry NORMAL. */
54
+ withNormal: number;
55
+ /** Primitives with no NORMAL that `addSmoothNormals` can fix. */
56
+ fixable: number;
57
+ /** Primitives with no NORMAL that it will not touch, by reason. */
58
+ skipped: Record<NormalsSkipReason, number>;
59
+ }
60
+ export interface SmoothNormalsResult {
61
+ primitivesProcessed: number;
62
+ verticesBefore: number;
63
+ verticesAfter: number;
64
+ }
65
+ export interface EnsureNormalsReport extends NormalsAudit {
66
+ changed: boolean;
67
+ creaseAngleDeg: number;
68
+ verticesBefore: number;
69
+ verticesAfter: number;
70
+ /** Set when the file could not be read (for example Draco-compressed); nothing was changed. */
71
+ unreadable?: string;
72
+ }
73
+ /** Counts what a document has and what `addSmoothNormals` would do about it. */
74
+ export declare function auditNormals(doc: Document): NormalsAudit;
75
+ /**
76
+ * Add smooth vertex normals to every fixable triangle primitive of `doc` that
77
+ * has none. Run `pruneReplacedAccessors` (or write through an optimiser that
78
+ * prunes) afterwards to drop the accessors that were replaced.
79
+ */
80
+ export declare function addSmoothNormals(doc: Document, options?: SmoothNormalsOptions): SmoothNormalsResult;
81
+ /**
82
+ * Audit a GLB's normals without changing it. `unreadable` is set instead of
83
+ * counts when the file cannot be parsed here (Draco needs a decoder this
84
+ * package does not ship).
85
+ */
86
+ export declare function auditGlbNormals(bytes: Uint8Array): Promise<{
87
+ audit: NormalsAudit | null;
88
+ unreadable?: string;
89
+ }>;
90
+ /**
91
+ * Return `bytes` with smooth NORMALs added to every primitive that lacks them.
92
+ * When there is nothing to fix (or the file cannot be read) the original bytes
93
+ * come back untouched and `changed` is false.
94
+ */
95
+ export declare function ensureSmoothNormalsGlb(bytes: Uint8Array, options?: SmoothNormalsOptions): Promise<{
96
+ bytes: Uint8Array;
97
+ report: EnsureNormalsReport;
98
+ }>;