@forgeax/engine-geometry 0.1.27 → 0.1.29

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
@@ -204,6 +204,8 @@ produce the normal vec4 or mesh result.
204
204
  | `deriveVertexBufferLayout(map, opts?)` | fn | Derives a fixed-order `GpuVertexBufferLayoutEntry[]` from a `VertexAttributeMap`. The canonical 14-key order (position / normal / uv / tangent / skinIndex / skinWeight / uv1..uv7 / color) assigns `@location(N)` per key. Absent keys reserve no space; `opts.shaderUvSetCount` enables clamp-to-last alias for UV sets. |
205
205
  | `deriveVertexLayoutProjection(map)` | fn | Creates the immutable canonical projection (`schemaVersion`, attributes, mask, stride, digest). `color` is fixed at host location 13 and `float32x4`. |
206
206
  | `packInterleavedVertexAttributes(map, vertexCount)` | fn | Packs attribute arrays using that projection and returns `Result<{ projection, vertices }, VertexAttributePackError>`; closed detail reasons identify storage, cardinality, vertex-count, or non-finite failures. |
207
+ | `DEFAULT_VERTEX_ATTRIBUTE_MAP` / `SKIN_VERTEX_ATTRIBUTE_MAP` | const | Canonical presence maps for ordinary and skinned interleaved meshes. Render, recovery, and feature assembly use these maps instead of spelling a fallback layout locally. |
208
+ | `deriveVertexCount(vertices, projection)` | fn | Validates `vertices.byteLength % projection.arrayStride === 0` and derives the integer vertex count shared by upload, LOD, update, morph, and draw-range consumers. |
207
209
  | `deriveVertexLayoutProjectionFromMask(mask)` | fn | Reconstructs a wire projection through the same canonical key owner; returns a typed error for empty or unknown mask bits. |
208
210
  | `buildMeshAttributeMapForUvSets(uvSetCount)` | fn | Synthesise a `VertexAttributeMap` with empty `Float32Array(0)` placeholders for `uv` + `uv1..uvN-1`. Used by the forward record stage to pre-declare UV-set slots before interleaving. |
209
211
  | `GpuVertexBufferLayoutEntry` | type | `{ shaderLocation: number; offset: number; format: GPUVertexFormat }` -- one GPU vertex buffer layout entry. |
@@ -223,6 +225,12 @@ The canonical key order and per-key `GPUVertexFormat` mapping is the SSOT in
223
225
  Shader `@location(N)` declarations and naga reflection deep-equal tests keep
224
226
  them in sync.
225
227
 
228
+ > [!IMPORTANT]
229
+ > Keep vertex bytes and `VertexLayoutProjection` together across every GPU
230
+ > owner. Derive the count with `deriveVertexCount`; a non-divisible payload is
231
+ > rejected before GPU resource construction instead of being truncated into a
232
+ > fractional draw range.
233
+
226
234
  ## Three.js r184 mental alignment
227
235
 
228
236
  The 6 factory signatures mirror Three.js `BufferGeometry` constructors
package/dist/index.d.ts CHANGED
@@ -14,5 +14,5 @@ export { createSphereGeometry } from './sphere';
14
14
  export { computeTangentVec4 } from './tangent';
15
15
  export { createTeapotGeometry, type TeapotMeshAsset, type TeapotProvenance, } from './teapot';
16
16
  export { createTorusGeometry } from './torus';
17
- export { buildMeshAttributeMapForUvSets, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, type GpuVertexBufferLayoutEntry, type PackedVertexAttributes, packInterleavedVertexAttributes, VertexAttributePackError, type VertexLayoutProjection, type VertexLayoutProjectionAttribute, type VertexLayoutProjectionMaskError, } from './vertex-attribute-layout';
17
+ export { buildMeshAttributeMapForUvSets, DEFAULT_VERTEX_ATTRIBUTE_MAP, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexCount, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, type GpuVertexBufferLayoutEntry, type PackedVertexAttributes, packInterleavedVertexAttributes, SKIN_VERTEX_ATTRIBUTE_MAP, VertexAttributePackError, type VertexLayoutProjection, type VertexLayoutProjectionAttribute, type VertexLayoutProjectionMaskError, } from './vertex-attribute-layout';
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,YAAY,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,IAAI,GACV,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EACL,8BAA8B,EAC9B,wBAAwB,EACxB,sCAAsC,EACtC,4BAA4B,EAC5B,oCAAoC,EACpC,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,+BAA+B,EAC/B,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,+BAA+B,EACpC,KAAK,+BAA+B,GACrC,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,YAAY,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,IAAI,GACV,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,EACxB,sCAAsC,EACtC,iBAAiB,EACjB,4BAA4B,EAC5B,oCAAoC,EACpC,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,+BAA+B,EAC/B,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,+BAA+B,EACpC,KAAK,+BAA+B,GACrC,MAAM,2BAA2B,CAAC"}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { decodeMeshBinHeader, MESH_BIN_HEADER_V4_BYTES, MESH_BIN_VERSION } from '@forgeax/engine-pack';
2
2
  import { box3, circle2, box2 } from '@forgeax/engine-math';
3
- import { err, ok, countUvSets, AssetError, ASSET_ERROR_HINTS } from '@forgeax/engine-types';
3
+ import { err, ok, AssetError, ASSET_ERROR_HINTS } from '@forgeax/engine-types';
4
4
 
5
5
  // src/assets/mesh-binary.ts
6
6
  var EPSILON = 1e-8;
@@ -275,9 +275,25 @@ var CANONICAL_KEYS = Object.keys(ATTRIBUTE_FORMAT_MAP).filter(isAttributeKey);
275
275
  var UV_KEYS = CANONICAL_KEYS.filter(
276
276
  (key) => key === "uv" || key.startsWith("uv")
277
277
  );
278
- function emitAliasEntries(entries, fromIndex, toIndex, aliasOffset, currentStride) {
279
- for (let k = fromIndex; k < toIndex && k < UV_KEYS.length; k++) {
278
+ var EMPTY_FLOAT32 = new Float32Array(0);
279
+ var EMPTY_UINT16 = new Uint16Array(0);
280
+ var DEFAULT_VERTEX_ATTRIBUTE_MAP = Object.freeze({
281
+ position: EMPTY_FLOAT32,
282
+ normal: EMPTY_FLOAT32,
283
+ uv: EMPTY_FLOAT32,
284
+ tangent: EMPTY_FLOAT32
285
+ });
286
+ var SKIN_VERTEX_ATTRIBUTE_MAP = Object.freeze({
287
+ ...DEFAULT_VERTEX_ATTRIBUTE_MAP,
288
+ skinIndex: EMPTY_UINT16,
289
+ skinWeight: EMPTY_FLOAT32
290
+ });
291
+ function emitAliasEntries(entries, toIndex, currentStride) {
292
+ const lastUv = entries.filter((entry) => UV_KEYS.includes(entry.key)).at(-1);
293
+ const aliasOffset = lastUv?.offset ?? currentStride;
294
+ for (let k = 0; k < toIndex && k < UV_KEYS.length; k++) {
280
295
  const uvKey = UV_KEYS[k];
296
+ if (entries.some((entry) => entry.key === uvKey)) continue;
281
297
  entries.push({
282
298
  key: uvKey,
283
299
  shaderLocation: CANONICAL_KEYS.indexOf(uvKey),
@@ -285,15 +301,10 @@ function emitAliasEntries(entries, fromIndex, toIndex, aliasOffset, currentStrid
285
301
  format: ATTRIBUTE_FORMAT_MAP[uvKey]
286
302
  });
287
303
  }
288
- return fromIndex === 0 ? currentStride + ATTRIBUTE_BYTE_STRIDE.uv : currentStride;
304
+ return lastUv === void 0 ? currentStride + ATTRIBUTE_BYTE_STRIDE.uv : currentStride;
289
305
  }
290
306
  function buildMeshAttributeMapForUvSets(uvSetCount) {
291
- const map = {
292
- position: new Float32Array(0),
293
- normal: new Float32Array(0),
294
- uv: new Float32Array(0),
295
- tangent: new Float32Array(0)
296
- };
307
+ const map = { ...DEFAULT_VERTEX_ATTRIBUTE_MAP };
297
308
  for (let set = 1; set < uvSetCount && set < UV_KEYS.length; set++) {
298
309
  map[UV_KEYS[set]] = new Float32Array(0);
299
310
  }
@@ -315,14 +326,7 @@ function deriveVertexBufferLayout(map, opts) {
315
326
  }
316
327
  const present = entries.length;
317
328
  if (shaderUvSetCount > 0) {
318
- const meshUvSetCount = countUvSets(map);
319
- if (shaderUvSetCount > meshUvSetCount) {
320
- const lastUvIndex = meshUvSetCount - 1;
321
- const lastUvKey = lastUvIndex >= 0 && lastUvIndex < UV_KEYS.length ? UV_KEYS[lastUvIndex] : void 0;
322
- const aliasOffset = lastUvKey !== void 0 ? CANONICAL_KEYS.indexOf(lastUvKey) : -1;
323
- const aliasByteOffset = aliasOffset >= 0 ? entries.find((e) => e.shaderLocation === aliasOffset)?.offset ?? 0 : offset;
324
- offset = emitAliasEntries(entries, meshUvSetCount, shaderUvSetCount, aliasByteOffset, offset);
325
- }
329
+ offset = emitAliasEntries(entries, shaderUvSetCount, offset);
326
330
  }
327
331
  if (present === 0 && shaderUvSetCount === 0) return [];
328
332
  entries.sort((a, b) => a.shaderLocation - b.shaderLocation);
@@ -347,24 +351,7 @@ function deriveVertexBufferLayoutFromProjection(projection, opts) {
347
351
  let arrayStride = projection.arrayStride;
348
352
  const shaderUvSetCount = opts?.shaderUvSetCount ?? 0;
349
353
  if (shaderUvSetCount > 0) {
350
- const meshUvSetCount = entries.filter(
351
- (entry) => entry.key === "uv" || entry.key.startsWith("uv")
352
- ).length;
353
- if (shaderUvSetCount > meshUvSetCount) {
354
- const lastUv = entries.filter((entry) => entry.key === "uv" || entry.key.startsWith("uv")).at(-1);
355
- const aliasOffset = lastUv?.offset ?? arrayStride;
356
- for (let index = meshUvSetCount; index < shaderUvSetCount && index < UV_KEYS.length; index += 1) {
357
- const key = UV_KEYS[index];
358
- if (key === void 0) continue;
359
- entries.push({
360
- key,
361
- shaderLocation: CANONICAL_KEYS.indexOf(key),
362
- offset: aliasOffset,
363
- format: ATTRIBUTE_FORMAT_MAP[key]
364
- });
365
- }
366
- if (meshUvSetCount === 0) arrayStride += ATTRIBUTE_BYTE_STRIDE.uv;
367
- }
354
+ arrayStride = emitAliasEntries(entries, shaderUvSetCount, arrayStride);
368
355
  }
369
356
  entries.sort((a, b) => a.shaderLocation - b.shaderLocation);
370
357
  return projection.attributes.length === 0 ? [] : [
@@ -378,6 +365,15 @@ function deriveVertexBufferLayoutFromProjection(projection, opts) {
378
365
  }
379
366
  ];
380
367
  }
368
+ function deriveVertexCount(vertices, projection) {
369
+ const stride = projection.arrayStride;
370
+ const byteLength = vertices.byteLength;
371
+ if (!Number.isSafeInteger(stride) || stride <= 0 || !Number.isSafeInteger(byteLength) || byteLength < 0 || byteLength % stride !== 0) {
372
+ return void 0;
373
+ }
374
+ const count = byteLength / stride;
375
+ return Number.isSafeInteger(count) ? count : void 0;
376
+ }
381
377
  function bytesForFormat(format) {
382
378
  if (format === "uint16x4" || format === "float32x2") return 8;
383
379
  if (format === "float32x3") return 12;
@@ -4286,6 +4282,6 @@ function createTorusGeometry(radius, tube, radialSegments = 8, tubularSegments =
4286
4282
  return meshFromInterleaved(vertices, indices);
4287
4283
  }
4288
4284
 
4289
- export { PROCEDURAL_FLOATS_PER_VERTEX, VertexAttributePackError, buildMeshAttributeMapForUvSets, compute2dBounds, computeTangentVec4, create2dGeometry, create2dRingGeometry, createBoxGeometry, createCapsuleGeometry, createConeGeometry, createCylinderGeometry, createEdgesGeometry, createMeshBuilder, createPlaneGeometry, createPrimitiveMesh, createProceduralMesh, createSphereGeometry, createTeapotGeometry, createTorusGeometry, createWireframeGeometry, decodeMeshBinary, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, meshAssetContribution, meshAssetDecoder, meshAssetKind, meshFromInterleaved, normalizeMeshPayload, packInterleavedVertexAttributes };
4285
+ export { DEFAULT_VERTEX_ATTRIBUTE_MAP, PROCEDURAL_FLOATS_PER_VERTEX, SKIN_VERTEX_ATTRIBUTE_MAP, VertexAttributePackError, buildMeshAttributeMapForUvSets, compute2dBounds, computeTangentVec4, create2dGeometry, create2dRingGeometry, createBoxGeometry, createCapsuleGeometry, createConeGeometry, createCylinderGeometry, createEdgesGeometry, createMeshBuilder, createPlaneGeometry, createPrimitiveMesh, createProceduralMesh, createSphereGeometry, createTeapotGeometry, createTorusGeometry, createWireframeGeometry, decodeMeshBinary, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexCount, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, meshAssetContribution, meshAssetDecoder, meshAssetKind, meshFromInterleaved, normalizeMeshPayload, packInterleavedVertexAttributes };
4290
4286
  //# sourceMappingURL=index.mjs.map
4291
4287
  //# sourceMappingURL=index.mjs.map