@forgeax/engine-geometry 0.1.20 → 0.1.21

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/dist/index.mjs CHANGED
@@ -1114,6 +1114,9 @@ function normalizeMeshPayload(payload, refs) {
1114
1114
  if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return void 0;
1115
1115
  const source = payload;
1116
1116
  if (source.kind !== "mesh") return void 0;
1117
+ const sourceAttributes = source.attributes !== null && typeof source.attributes === "object" ? source.attributes : void 0;
1118
+ const projection = sourceAttributes === void 0 ? void 0 : deriveVertexLayoutProjection(sourceAttributes);
1119
+ const floatsPerVertex = projection === void 0 || projection.attributes.length === 0 ? PROCEDURAL_FLOATS_PER_VERTEX : projection.arrayStride / Float32Array.BYTES_PER_ELEMENT;
1117
1120
  return meshFromParts(
1118
1121
  {
1119
1122
  vertices: source.vertices,
@@ -1124,7 +1127,7 @@ function normalizeMeshPayload(payload, refs) {
1124
1127
  ...Array.isArray(source.materialSlots) ? { materialSlots: source.materialSlots } : {},
1125
1128
  ...source.morphTargets === void 0 ? {} : { morphTargets: source.morphTargets },
1126
1129
  ...source.morphWeights === void 0 ? {} : { morphWeights: source.morphWeights },
1127
- floatsPerVertex: PROCEDURAL_FLOATS_PER_VERTEX
1130
+ floatsPerVertex
1128
1131
  },
1129
1132
  refs
1130
1133
  );
@@ -2325,6 +2328,367 @@ function createEdgesGeometry(source, thresholdAngleDegrees = DEFAULT_THRESHOLD_D
2325
2328
  );
2326
2329
  return lineMesh(edges.map(({ a, b }) => ({ a, b })));
2327
2330
  }
2331
+ var ATTRIBUTE_KEYS = [
2332
+ "position",
2333
+ "normal",
2334
+ "uv",
2335
+ "tangent",
2336
+ "skinIndex",
2337
+ "skinWeight",
2338
+ "uv1",
2339
+ "uv2",
2340
+ "uv3",
2341
+ "uv4",
2342
+ "uv5",
2343
+ "uv6",
2344
+ "uv7",
2345
+ "color"
2346
+ ];
2347
+ var ATTRIBUTE_COMPONENTS = {
2348
+ position: 3,
2349
+ normal: 3,
2350
+ uv: 2,
2351
+ tangent: 4,
2352
+ skinIndex: 4,
2353
+ skinWeight: 4,
2354
+ uv1: 2,
2355
+ uv2: 2,
2356
+ uv3: 2,
2357
+ uv4: 2,
2358
+ uv5: 2,
2359
+ uv6: 2,
2360
+ uv7: 2,
2361
+ color: 4
2362
+ };
2363
+ var TOPOLOGIES = [
2364
+ "point-list",
2365
+ "line-list",
2366
+ "line-strip",
2367
+ "triangle-list",
2368
+ "triangle-strip"
2369
+ ];
2370
+ function failure(field, value, reason) {
2371
+ return err(
2372
+ new AssetError({
2373
+ code: "asset-invalid-value",
2374
+ expected: `valid MeshBuilder ${field}: ${reason}`,
2375
+ hint: ASSET_ERROR_HINTS["asset-invalid-value"],
2376
+ detail: { field, value, reason }
2377
+ })
2378
+ );
2379
+ }
2380
+ function sourceView(key, value) {
2381
+ if (key === "skinIndex") {
2382
+ if (value instanceof Uint16Array) return value;
2383
+ if (value instanceof ArrayBuffer && value.byteLength % Uint16Array.BYTES_PER_ELEMENT === 0) {
2384
+ return new Uint16Array(value);
2385
+ }
2386
+ return void 0;
2387
+ }
2388
+ if (value instanceof Float32Array) return value;
2389
+ if (value instanceof ArrayBuffer && value.byteLength % Float32Array.BYTES_PER_ELEMENT === 0) {
2390
+ return new Float32Array(value);
2391
+ }
2392
+ return void 0;
2393
+ }
2394
+ function cloneAttribute(key, value) {
2395
+ const view = sourceView(key, value);
2396
+ if (view === void 0) return void 0;
2397
+ return view.slice();
2398
+ }
2399
+ function attributeKeys(attributes) {
2400
+ return ATTRIBUTE_KEYS.filter((key) => attributes[key] !== void 0);
2401
+ }
2402
+ function appendArray(target, source) {
2403
+ if (target instanceof Uint16Array && source instanceof Uint16Array) {
2404
+ const output = new Uint16Array(target.length + source.length);
2405
+ output.set(target, 0);
2406
+ output.set(source, target.length);
2407
+ return output;
2408
+ }
2409
+ if (target instanceof Float32Array && source instanceof Float32Array) {
2410
+ const output = new Float32Array(target.length + source.length);
2411
+ output.set(target, 0);
2412
+ output.set(source, target.length);
2413
+ return output;
2414
+ }
2415
+ return target;
2416
+ }
2417
+ function copySlots(slots) {
2418
+ return (slots ?? [{ slotName: "Default" }]).map((slot) => ({
2419
+ slotName: slot.slotName,
2420
+ ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
2421
+ ...slot.defaultMaterial === void 0 ? {} : { defaultMaterial: slot.defaultMaterial }
2422
+ }));
2423
+ }
2424
+ function validateAttributeBatch(attributes) {
2425
+ const keys = attributeKeys(attributes);
2426
+ if (keys.length === 0)
2427
+ return failure("attributes", [], "at least one canonical attribute is required");
2428
+ const position = attributes.position;
2429
+ if (position === void 0)
2430
+ return failure("attributes.position", void 0, "position is required");
2431
+ const positionView = sourceView("position", position);
2432
+ if (positionView === void 0) {
2433
+ return failure(
2434
+ "attributes.position",
2435
+ typeof position,
2436
+ "position must use Float32Array or ArrayBuffer"
2437
+ );
2438
+ }
2439
+ if (positionView.length === 0 || positionView.length % 3 !== 0) {
2440
+ return failure(
2441
+ "attributes.position",
2442
+ positionView.length,
2443
+ "position cardinality must be a non-zero multiple of 3"
2444
+ );
2445
+ }
2446
+ const vertexCount = positionView.length / 3;
2447
+ for (const key of keys) {
2448
+ const value = attributes[key];
2449
+ if (value === void 0) continue;
2450
+ const view = sourceView(key, value);
2451
+ if (view === void 0) {
2452
+ return failure(
2453
+ key,
2454
+ typeof value,
2455
+ key === "skinIndex" ? "storage must be Uint16Array" : "storage must be Float32Array"
2456
+ );
2457
+ }
2458
+ const expectedLength = vertexCount * ATTRIBUTE_COMPONENTS[key];
2459
+ if (view.length !== expectedLength) {
2460
+ return failure(key, view.length, `cardinality must be ${expectedLength}`);
2461
+ }
2462
+ for (let elementIndex = 0; elementIndex < view.length; elementIndex += 1) {
2463
+ const valueAt = view[elementIndex];
2464
+ if (valueAt === void 0 || !Number.isFinite(valueAt)) {
2465
+ return failure(key, valueAt, `${key}[${elementIndex}] must be finite`);
2466
+ }
2467
+ if (key === "skinIndex" && (!Number.isInteger(valueAt) || valueAt < 0 || valueAt > 65535)) {
2468
+ return failure(key, valueAt, `${key}[${elementIndex}] must be an integer in [0, 65535]`);
2469
+ }
2470
+ }
2471
+ }
2472
+ return ok({ keys, vertexCount });
2473
+ }
2474
+ function validateSlots(slots) {
2475
+ if (slots.length === 0)
2476
+ return failure("materialSlots", slots.length, "at least one material slot is required");
2477
+ const names = /* @__PURE__ */ new Set();
2478
+ for (let index = 0; index < slots.length; index += 1) {
2479
+ const slot = slots[index];
2480
+ const name = slot?.slotName.trim() ?? "";
2481
+ if (name.length === 0 || names.has(name)) {
2482
+ return failure(`materialSlots[${index}].slotName`, name, "must be non-empty and unique");
2483
+ }
2484
+ names.add(name);
2485
+ }
2486
+ return ok(void 0);
2487
+ }
2488
+ function completedSubmeshes(requested, vertexCount, indexCount, materialSlotCount) {
2489
+ const source = requested.length === 0 ? [
2490
+ {
2491
+ indexOffset: 0,
2492
+ indexCount,
2493
+ vertexCount,
2494
+ topology: "triangle-list",
2495
+ materialSlot: 0
2496
+ }
2497
+ ] : requested;
2498
+ const output = [];
2499
+ for (let index = 0; index < source.length; index += 1) {
2500
+ const candidate = source[index];
2501
+ if (candidate === void 0)
2502
+ return failure(`submeshes[${index}]`, void 0, "entry is required");
2503
+ const indexOffset = candidate.indexOffset ?? 0;
2504
+ const submeshIndexCount = candidate.indexCount ?? (indexCount > 0 ? indexCount : 0);
2505
+ const submeshVertexCount = candidate.vertexCount ?? vertexCount;
2506
+ const topology = candidate.topology ?? "triangle-list";
2507
+ const materialSlot = candidate.materialSlot ?? index;
2508
+ if (!TOPOLOGIES.includes(topology)) {
2509
+ return failure(
2510
+ `submeshes[${index}].topology`,
2511
+ topology,
2512
+ "must be a WebGPU primitive topology"
2513
+ );
2514
+ }
2515
+ if (!Number.isInteger(indexOffset) || indexOffset < 0 || !Number.isInteger(submeshIndexCount) || submeshIndexCount < 0 || !Number.isInteger(submeshVertexCount) || submeshVertexCount < 0 || submeshVertexCount > vertexCount || !Number.isInteger(materialSlot) || materialSlot < 0 || materialSlot >= materialSlotCount) {
2516
+ return failure(
2517
+ `submeshes[${index}]`,
2518
+ JSON.stringify(candidate),
2519
+ "range and material slot are invalid"
2520
+ );
2521
+ }
2522
+ if (indexCount === 0 && (indexOffset !== 0 || submeshIndexCount !== 0)) {
2523
+ return failure(
2524
+ `submeshes[${index}]`,
2525
+ JSON.stringify(candidate),
2526
+ "non-indexed meshes use indexOffset=0 and indexCount=0"
2527
+ );
2528
+ }
2529
+ if (indexCount > 0 && indexOffset + submeshIndexCount > indexCount) {
2530
+ return failure(
2531
+ `submeshes[${index}]`,
2532
+ JSON.stringify(candidate),
2533
+ "index range exceeds the accumulated index buffer"
2534
+ );
2535
+ }
2536
+ if (indexCount === 0 && (topology === "line-strip" || topology === "triangle-strip")) {
2537
+ return failure(
2538
+ `submeshes[${index}].topology`,
2539
+ topology,
2540
+ "strip topology requires an index buffer"
2541
+ );
2542
+ }
2543
+ output.push({
2544
+ indexOffset,
2545
+ indexCount: submeshIndexCount,
2546
+ vertexCount: submeshVertexCount,
2547
+ topology,
2548
+ materialSlot
2549
+ });
2550
+ }
2551
+ return ok(Object.freeze(output));
2552
+ }
2553
+ function createMeshBuilder(options = {}) {
2554
+ const attributes = {};
2555
+ const indices = [];
2556
+ const submeshes = [];
2557
+ const materialSlots = copySlots(options.materialSlots);
2558
+ const appendVertices = (batch) => {
2559
+ const checked = validateAttributeBatch(batch);
2560
+ if (!checked.ok) return checked;
2561
+ const incomingKeys = checked.value.keys;
2562
+ const existingKeys = ATTRIBUTE_KEYS.filter((key) => attributes[key] !== void 0);
2563
+ if (existingKeys.length > 0 && existingKeys.join("|") !== incomingKeys.join("|")) {
2564
+ return failure(
2565
+ "attributes",
2566
+ incomingKeys.join(","),
2567
+ "every appended batch must carry the same canonical keys"
2568
+ );
2569
+ }
2570
+ for (const key of incomingKeys) {
2571
+ const value = batch[key];
2572
+ if (value === void 0) continue;
2573
+ const cloned = cloneAttribute(key, value);
2574
+ if (cloned === void 0) return failure(key, typeof value, "storage could not be cloned");
2575
+ const previous = attributes[key];
2576
+ attributes[key] = previous === void 0 ? cloned : appendArray(previous, cloned);
2577
+ }
2578
+ return ok(void 0);
2579
+ };
2580
+ const appendIndices = (batch) => {
2581
+ for (let index = 0; index < batch.length; index += 1) {
2582
+ const value = Number(batch[index]);
2583
+ if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
2584
+ return failure("indices", value, `indices[${index}] must be an integer in [0, 2^32-1]`);
2585
+ }
2586
+ indices.push(value);
2587
+ }
2588
+ return ok(void 0);
2589
+ };
2590
+ const addSubmesh = (submesh) => {
2591
+ if (submesh === null || typeof submesh !== "object") {
2592
+ return failure("submeshes", submesh, "entry must be an object");
2593
+ }
2594
+ submeshes.push({ ...submesh });
2595
+ return ok(void 0);
2596
+ };
2597
+ const build = () => {
2598
+ const source = attributes;
2599
+ const checked = validateAttributeBatch(source);
2600
+ if (!checked.ok) return checked;
2601
+ const slotCheck = validateSlots(materialSlots);
2602
+ if (!slotCheck.ok) return slotCheck;
2603
+ const vertexCount = checked.value.vertexCount;
2604
+ const position = source.position;
2605
+ if (position === void 0)
2606
+ return failure("attributes.position", void 0, "position is required");
2607
+ const positionView = sourceView("position", position);
2608
+ if (!(positionView instanceof Float32Array)) {
2609
+ return failure("attributes.position", typeof position, "position storage is invalid");
2610
+ }
2611
+ let maxIndex = 0;
2612
+ for (const value of indices) maxIndex = Math.max(maxIndex, value);
2613
+ const indexArray2 = indices.length === 0 ? void 0 : maxIndex <= 65535 ? new Uint16Array(indices) : new Uint32Array(indices);
2614
+ if (indexArray2 !== void 0) {
2615
+ for (let index = 0; index < indexArray2.length; index += 1) {
2616
+ const value = indexArray2[index];
2617
+ if (value === void 0 || value >= vertexCount) {
2618
+ return failure(
2619
+ "indices",
2620
+ value ?? -1,
2621
+ `indices[${index}] must be less than vertexCount (${vertexCount})`
2622
+ );
2623
+ }
2624
+ }
2625
+ }
2626
+ const packed = packInterleavedVertexAttributes(source, vertexCount);
2627
+ if (!packed.ok) return packed;
2628
+ const ranges = completedSubmeshes(
2629
+ submeshes,
2630
+ vertexCount,
2631
+ indexArray2?.length ?? 0,
2632
+ materialSlots.length
2633
+ );
2634
+ if (!ranges.ok) return ranges;
2635
+ const aabb = box3.fromPositions(box3.create(), positionView);
2636
+ const copiedAttributes = {};
2637
+ for (const key of checked.value.keys) {
2638
+ const value = source[key];
2639
+ if (value === void 0) continue;
2640
+ const cloned = cloneAttribute(key, value);
2641
+ if (cloned === void 0) return failure(key, typeof value, "storage could not be cloned");
2642
+ if (key === "skinIndex") {
2643
+ if (!(cloned instanceof Uint16Array)) {
2644
+ return failure(key, typeof value, "skinIndex storage must be Uint16Array");
2645
+ }
2646
+ copiedAttributes.skinIndex = cloned;
2647
+ } else {
2648
+ if (!(cloned instanceof Float32Array)) {
2649
+ return failure(key, typeof value, `${key} storage must be Float32Array`);
2650
+ }
2651
+ copiedAttributes[key] = cloned;
2652
+ }
2653
+ }
2654
+ const mesh = {
2655
+ kind: "mesh",
2656
+ vertices: packed.value.vertices.slice(),
2657
+ ...indexArray2 === void 0 ? {} : { indices: indexArray2.slice() },
2658
+ attributes: copiedAttributes,
2659
+ aabb: Float32Array.from(aabb),
2660
+ submeshes: ranges.value,
2661
+ materialSlots: Object.freeze(materialSlots.map((slot) => ({ ...slot })))
2662
+ };
2663
+ return ok(Object.freeze(mesh));
2664
+ };
2665
+ if (options.attributes !== void 0) {
2666
+ const result = appendVertices(options.attributes);
2667
+ if (!result.ok) {
2668
+ const constructionError = result.error;
2669
+ return {
2670
+ appendVertices,
2671
+ appendIndices,
2672
+ addSubmesh,
2673
+ build: () => err(constructionError)
2674
+ };
2675
+ }
2676
+ }
2677
+ if (options.indices !== void 0) {
2678
+ const result = appendIndices(options.indices);
2679
+ if (!result.ok) {
2680
+ const constructionError = result.error;
2681
+ return {
2682
+ appendVertices,
2683
+ appendIndices,
2684
+ addSubmesh,
2685
+ build: () => err(constructionError)
2686
+ };
2687
+ }
2688
+ }
2689
+ for (const submesh of options.submeshes ?? []) submeshes.push({ ...submesh });
2690
+ return { appendVertices, appendIndices, addSubmesh, build };
2691
+ }
2328
2692
  var PATCHES = new Uint16Array([
2329
2693
  0,
2330
2694
  1,
@@ -3901,6 +4265,6 @@ function createTorusGeometry(radius, tube, radialSegments = 8, tubularSegments =
3901
4265
  return meshFromInterleaved(vertices, indices);
3902
4266
  }
3903
4267
 
3904
- export { PROCEDURAL_FLOATS_PER_VERTEX, VertexAttributePackError, buildMeshAttributeMapForUvSets, compute2dBounds, computeTangentVec4, create2dGeometry, create2dRingGeometry, createBoxGeometry, createCapsuleGeometry, createConeGeometry, createCylinderGeometry, createEdgesGeometry, createPlaneGeometry, createPrimitiveMesh, createSphereGeometry, createTeapotGeometry, createTorusGeometry, createWireframeGeometry, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, meshAssetContribution, meshAssetDecoder, meshAssetKind, meshFromInterleaved, packInterleavedVertexAttributes };
4268
+ export { PROCEDURAL_FLOATS_PER_VERTEX, VertexAttributePackError, buildMeshAttributeMapForUvSets, compute2dBounds, computeTangentVec4, create2dGeometry, create2dRingGeometry, createBoxGeometry, createCapsuleGeometry, createConeGeometry, createCylinderGeometry, createEdgesGeometry, createMeshBuilder, createPlaneGeometry, createPrimitiveMesh, createSphereGeometry, createTeapotGeometry, createTorusGeometry, createWireframeGeometry, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, meshAssetContribution, meshAssetDecoder, meshAssetKind, meshFromInterleaved, packInterleavedVertexAttributes };
3905
4269
  //# sourceMappingURL=index.mjs.map
3906
4270
  //# sourceMappingURL=index.mjs.map