@forgeax/engine-geometry 0.1.33 → 0.1.34

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
@@ -33,6 +33,12 @@ forgeax-engine. A **leaf** package: depends on `@forgeax/engine-ecs` (`Result`),
33
33
  structured `.detail.field/.value/.reason`. No silent fallback, no `console.warn`,
34
34
  no `null` return (charter P3).
35
35
 
36
+ The canonical vertex layout is interned by its finite attribute-presence mask.
37
+ Mesh maps and packed masks resolve to the same deeply frozen projection; changing
38
+ an authoring map's attributes derives a different mask without retaining the map
39
+ or its buffers. Layout offsets, formats, and digests still derive from the one
40
+ canonical attribute schema.
41
+
36
42
  ## MeshBuilder
37
43
 
38
44
  `MeshBuilder` is the incremental authoring owner for custom geometry. Append
package/dist/index.mjs CHANGED
@@ -400,23 +400,26 @@ function fnv1a(input) {
400
400
  }
401
401
  return `vlp-v1-${(hash >>> 0).toString(16).padStart(8, "0")}`;
402
402
  }
403
+ var layoutsByMask = /* @__PURE__ */ new Map();
403
404
  function deriveVertexLayoutProjection(map) {
405
+ let mask = 0;
406
+ for (let index = 0; index < CANONICAL_KEYS.length; index += 1) {
407
+ const key = CANONICAL_KEYS[index];
408
+ if (key !== void 0 && map[key] !== void 0) mask |= 1 << index;
409
+ }
410
+ return layoutFromMask(mask);
411
+ }
412
+ function layoutFromMask(mask) {
413
+ const cached = layoutsByMask.get(mask);
414
+ if (cached !== void 0) return cached;
404
415
  const attributes = [];
405
416
  let offset = 0;
406
- let mask = 0;
407
- for (const key of CANONICAL_KEYS) {
408
- const value = map[key];
409
- if (value === void 0) continue;
417
+ for (let index = 0; index < CANONICAL_KEYS.length; index += 1) {
418
+ const key = CANONICAL_KEYS[index];
419
+ if (key === void 0 || (mask & 1 << index) === 0) continue;
410
420
  const format = ATTRIBUTE_FORMAT_MAP[key];
411
421
  const byteLength = bytesForFormat(format);
412
- attributes.push({
413
- key,
414
- shaderLocation: CANONICAL_KEYS.indexOf(key),
415
- offset,
416
- format,
417
- byteLength
418
- });
419
- mask |= 1 << CANONICAL_KEYS.indexOf(key);
422
+ attributes.push(Object.freeze({ key, shaderLocation: index, offset, format, byteLength }));
420
423
  offset += byteLength;
421
424
  }
422
425
  const digestInput = [
@@ -427,13 +430,15 @@ function deriveVertexLayoutProjection(map) {
427
430
  (entry) => `${entry.key},${entry.shaderLocation},${entry.offset},${entry.format}`
428
431
  )
429
432
  ].join("|");
430
- return Object.freeze({
433
+ const projection = Object.freeze({
431
434
  schemaVersion: 1,
432
435
  attributes: Object.freeze(attributes),
433
436
  mask,
434
437
  arrayStride: offset,
435
438
  digest: fnv1a(digestInput)
436
439
  });
440
+ layoutsByMask.set(mask, projection);
441
+ return projection;
437
442
  }
438
443
  var VertexAttributePackError = class extends AssetError {
439
444
  constructor(detail) {
@@ -465,14 +470,7 @@ function deriveVertexLayoutProjectionFromMask(mask) {
465
470
  detail: { mask, knownMask, unknownMask, reason: "unknown-bits" }
466
471
  });
467
472
  }
468
- const map = {};
469
- for (let index = 0; index < CANONICAL_KEYS.length; index += 1) {
470
- if ((mask & 1 << index) === 0) continue;
471
- const key = CANONICAL_KEYS[index];
472
- if (key !== void 0)
473
- map[key] = key === "skinIndex" ? new Uint16Array(0) : new Float32Array(0);
474
- }
475
- return ok(deriveVertexLayoutProjection(map));
473
+ return ok(layoutFromMask(unsignedMask));
476
474
  }
477
475
  function packInterleavedVertexAttributes(map, vertexCount) {
478
476
  const invalid3 = (detail) => err(new VertexAttributePackError(detail));