@forgeax/engine-geometry 0.1.30 → 0.1.32
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 +15 -5
- package/dist/__tests__/procedural.unit.test.d.ts +2 -0
- package/dist/__tests__/procedural.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +360 -10
- package/dist/index.mjs.map +1 -1
- package/dist/procedural.d.ts +13 -0
- package/dist/procedural.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/__tests__/procedural.unit.test.ts +310 -0
- package/src/index.ts +7 -0
- package/src/procedural.ts +447 -0
package/README.md
CHANGED
|
@@ -7,10 +7,11 @@ forgeax-engine. A **leaf** package: depends on `@forgeax/engine-ecs` (`Result`),
|
|
|
7
7
|
|
|
8
8
|
## 30-second self-introduction
|
|
9
9
|
|
|
10
|
-
- **Surface**:
|
|
10
|
+
- **Surface**: 10 reusable 3D procedural factories
|
|
11
11
|
(`createBoxGeometry` / `createCapsuleGeometry` / `createConeGeometry` /
|
|
12
12
|
`createCylinderGeometry` / `createPlaneGeometry` / `createSphereGeometry` /
|
|
13
|
-
`createTorusGeometry`
|
|
13
|
+
`createTorusGeometry` / `createExtrusionGeometry` / `createSweepGeometry` /
|
|
14
|
+
`createRevolutionGeometry`),
|
|
14
15
|
each returning `Result<MeshAsset, AssetError>`; the vertex-attribute-layout
|
|
15
16
|
SSOT (`deriveVertexBufferLayout` / `buildMeshAttributeMapForUvSets` /
|
|
16
17
|
`GpuVertexBufferLayoutEntry`); and the tangent + interleave helpers
|
|
@@ -125,7 +126,13 @@ const sphere = createSphereGeometry(1, 32, 24);
|
|
|
125
126
|
// then hand the resulting Handle<MeshAsset> to MeshFilter.assetHandle.
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
The
|
|
129
|
+
The primitive factories cover the common built-in shapes. For reusable authored
|
|
130
|
+
geometry, `createExtrusionGeometry` ear-clips a simple concave 2D contour and
|
|
131
|
+
adds cap and side normals, `createSweepGeometry` follows a 3D path with a
|
|
132
|
+
circular profile, and `createRevolutionGeometry` turns a `(radius, height)`
|
|
133
|
+
profile around the Y axis. Each operation validates finite input before
|
|
134
|
+
allocating a MeshAsset and returns through the same tangent, layout, and AABB
|
|
135
|
+
owners. For an imported
|
|
129
136
|
glTF mesh, use `@forgeax/engine-gltf`; FBX follows the same source-plus-Meta
|
|
130
137
|
route through `@forgeax/engine-fbx`. Runtime consumption is
|
|
131
138
|
`@forgeax/engine-assets-runtime` after the owning importer has cooked the asset.
|
|
@@ -154,7 +161,7 @@ if (!filled.ok || !ring.ok) return;
|
|
|
154
161
|
|
|
155
162
|
## API surface
|
|
156
163
|
|
|
157
|
-
###
|
|
164
|
+
### 10 procedural geometry factories
|
|
158
165
|
|
|
159
166
|
Each returns `Result<MeshAsset, AssetError>`. Interleaved vertex layout:
|
|
160
167
|
position (3xf32) + normal (3xf32) + uv (2xf32) = 8 floats/vertex at creation
|
|
@@ -170,6 +177,9 @@ time; expanded to the 12-float runtime layout (adds tangent vec4) by
|
|
|
170
177
|
| `createPlaneGeometry` | `(w, h, wSeg?, hSeg?)` | 1 / dim; XY plane, +Z normal |
|
|
171
178
|
| `createSphereGeometry` | `(radius, wSeg?, hSeg?)` | wSeg >= 3, hSeg >= 2 |
|
|
172
179
|
| `createTorusGeometry` | `(radius, tube, radSeg?, tubSeg?)` | radSeg >= 3, tubSeg >= 3 |
|
|
180
|
+
| `createExtrusionGeometry` | `(contour, depth)` | 3+ finite simple contour points, depth > 0 |
|
|
181
|
+
| `createSweepGeometry` | `(path, radius, radialSeg?)` | 2+ distinct finite 3D points, radius > 0, radialSeg >= 3 |
|
|
182
|
+
| `createRevolutionGeometry` | `(profile, radialSeg?)` | 2+ finite non-negative-radius points spanning height, radialSeg >= 3 |
|
|
173
183
|
|
|
174
184
|
All factories populate `position` / `normal` / `uv` attributes with lowercase
|
|
175
185
|
Three.js-r184 key naming. `VertexAttributeMap` also accepts optional `color`
|
|
@@ -233,7 +243,7 @@ them in sync.
|
|
|
233
243
|
|
|
234
244
|
## Three.js r184 mental alignment
|
|
235
245
|
|
|
236
|
-
The
|
|
246
|
+
The seven primitive factory signatures mirror Three.js `BufferGeometry` constructors
|
|
237
247
|
byte-for-byte: parameter order, attribute key lowercasing (`position` / `normal`
|
|
238
248
|
/ `uv`), and segment-count defaults. AI users migrating from Three.js can swap
|
|
239
249
|
the import path and the return-shape check:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedural.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/procedural.unit.test.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { compute2dBounds, create2dGeometry, create2dRingGeometry, type Shape2d,
|
|
|
10
10
|
export { createEdgesGeometry, createWireframeGeometry } from './edges';
|
|
11
11
|
export { createMeshBuilder, type MeshBuilder, type MeshBuilderOptions, type MeshBuilderSubmesh, } from './mesh-builder';
|
|
12
12
|
export { createPlaneGeometry } from './plane';
|
|
13
|
+
export { createExtrusionGeometry, createRevolutionGeometry, createSweepGeometry, type Vec2Point, type Vec3Point, } from './procedural';
|
|
13
14
|
export { createSphereGeometry } from './sphere';
|
|
14
15
|
export { computeTangentVec4 } from './tangent';
|
|
15
16
|
export { createTeapotGeometry, type TeapotMeshAsset, type TeapotProvenance, } from './teapot';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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"}
|
|
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,EACL,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AACtB,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
|
@@ -475,13 +475,13 @@ function deriveVertexLayoutProjectionFromMask(mask) {
|
|
|
475
475
|
return ok(deriveVertexLayoutProjection(map));
|
|
476
476
|
}
|
|
477
477
|
function packInterleavedVertexAttributes(map, vertexCount) {
|
|
478
|
-
const
|
|
478
|
+
const invalid3 = (detail) => err(new VertexAttributePackError(detail));
|
|
479
479
|
if (!Number.isInteger(vertexCount) || vertexCount < 0) {
|
|
480
|
-
return
|
|
480
|
+
return invalid3({ field: "vertexCount", reason: "vertex-count-invalid", actual: vertexCount });
|
|
481
481
|
}
|
|
482
482
|
const projection = deriveVertexLayoutProjection(map);
|
|
483
483
|
if (projection.attributes.length === 0) {
|
|
484
|
-
return
|
|
484
|
+
return invalid3({ field: "attributes", reason: "attributes-empty", actualCount: 0 });
|
|
485
485
|
}
|
|
486
486
|
const output = new ArrayBuffer(projection.arrayStride * vertexCount);
|
|
487
487
|
const outputFloats = new Float32Array(output);
|
|
@@ -493,7 +493,7 @@ function packInterleavedVertexAttributes(map, vertexCount) {
|
|
|
493
493
|
const components = entry.byteLength / (uint16 ? 2 : 4);
|
|
494
494
|
const bytesPerComponent = uint16 ? 2 : 4;
|
|
495
495
|
if (sourceValue instanceof ArrayBuffer && sourceValue.byteLength % bytesPerComponent !== 0) {
|
|
496
|
-
return
|
|
496
|
+
return invalid3({
|
|
497
497
|
field: entry.key,
|
|
498
498
|
reason: "attribute-cardinality-mismatch",
|
|
499
499
|
vertexCount,
|
|
@@ -504,7 +504,7 @@ function packInterleavedVertexAttributes(map, vertexCount) {
|
|
|
504
504
|
}
|
|
505
505
|
const source = elementView(sourceValue, entry.format);
|
|
506
506
|
if (source === void 0 || (uint16 ? storageOf(sourceValue) !== "uint16" && storageOf(sourceValue) !== "array-buffer" : storageOf(sourceValue) !== "float32" && storageOf(sourceValue) !== "array-buffer")) {
|
|
507
|
-
return
|
|
507
|
+
return invalid3({
|
|
508
508
|
field: entry.key,
|
|
509
509
|
reason: "attribute-storage-invalid",
|
|
510
510
|
expectedStorage: uint16 ? "uint16" : "float32",
|
|
@@ -513,7 +513,7 @@ function packInterleavedVertexAttributes(map, vertexCount) {
|
|
|
513
513
|
}
|
|
514
514
|
const expectedLength = vertexCount * components;
|
|
515
515
|
if (source.length !== expectedLength) {
|
|
516
|
-
return
|
|
516
|
+
return invalid3({
|
|
517
517
|
field: entry.key,
|
|
518
518
|
reason: "attribute-cardinality-mismatch",
|
|
519
519
|
vertexCount,
|
|
@@ -526,7 +526,7 @@ function packInterleavedVertexAttributes(map, vertexCount) {
|
|
|
526
526
|
for (let elementIndex = 0; elementIndex < source.length; elementIndex += 1) {
|
|
527
527
|
const component = source[elementIndex];
|
|
528
528
|
if (!Number.isFinite(component)) {
|
|
529
|
-
return
|
|
529
|
+
return invalid3({
|
|
530
530
|
field: "color",
|
|
531
531
|
reason: "attribute-non-finite",
|
|
532
532
|
elementIndex,
|
|
@@ -2706,6 +2706,356 @@ function createMeshBuilder(options = {}) {
|
|
|
2706
2706
|
for (const submesh of options.submeshes ?? []) submeshes.push({ ...submesh });
|
|
2707
2707
|
return { appendVertices, appendIndices, addSubmesh, build };
|
|
2708
2708
|
}
|
|
2709
|
+
function invalid2(field, detail) {
|
|
2710
|
+
return err(
|
|
2711
|
+
new AssetError({
|
|
2712
|
+
code: "asset-parse-failed",
|
|
2713
|
+
expected: `valid procedural geometry input for ${field}`,
|
|
2714
|
+
hint: ASSET_ERROR_HINTS["asset-parse-failed"],
|
|
2715
|
+
detail: { field, value: detail, reason: detail }
|
|
2716
|
+
})
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
function finitePoint2(point) {
|
|
2720
|
+
return Number.isFinite(point.x) && Number.isFinite(point.y);
|
|
2721
|
+
}
|
|
2722
|
+
function cross2(a, b, c) {
|
|
2723
|
+
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
2724
|
+
}
|
|
2725
|
+
function signedArea(points) {
|
|
2726
|
+
let value = 0;
|
|
2727
|
+
for (let i = 0; i < points.length; i++) {
|
|
2728
|
+
const a = points[i];
|
|
2729
|
+
const b = points[(i + 1) % points.length];
|
|
2730
|
+
value += a.x * b.y - b.x * a.y;
|
|
2731
|
+
}
|
|
2732
|
+
return value / 2;
|
|
2733
|
+
}
|
|
2734
|
+
function onSegment(a, b, p) {
|
|
2735
|
+
return Math.min(a.x, b.x) <= p.x + 1e-8 && p.x <= Math.max(a.x, b.x) + 1e-8 && Math.min(a.y, b.y) <= p.y + 1e-8 && p.y <= Math.max(a.y, b.y) + 1e-8;
|
|
2736
|
+
}
|
|
2737
|
+
function edgesCross(a, b, c, d) {
|
|
2738
|
+
const ab = cross2(a, b, c);
|
|
2739
|
+
const abD = cross2(a, b, d);
|
|
2740
|
+
const cdA = cross2(c, d, a);
|
|
2741
|
+
const cdB = cross2(c, d, b);
|
|
2742
|
+
if (Math.abs(ab) < 1e-8 && onSegment(a, b, c)) return true;
|
|
2743
|
+
if (Math.abs(abD) < 1e-8 && onSegment(a, b, d)) return true;
|
|
2744
|
+
if (Math.abs(cdA) < 1e-8 && onSegment(c, d, a)) return true;
|
|
2745
|
+
if (Math.abs(cdB) < 1e-8 && onSegment(c, d, b)) return true;
|
|
2746
|
+
return ab > 0 !== abD > 0 && cdA > 0 !== cdB > 0;
|
|
2747
|
+
}
|
|
2748
|
+
function selfIntersects(points) {
|
|
2749
|
+
for (let i = 0; i < points.length; i++) {
|
|
2750
|
+
const a = points[i];
|
|
2751
|
+
const b = points[(i + 1) % points.length];
|
|
2752
|
+
for (let j = i + 1; j < points.length; j++) {
|
|
2753
|
+
if (j === i || (j + 1) % points.length === i || (i + 1) % points.length === j) continue;
|
|
2754
|
+
const c = points[j];
|
|
2755
|
+
const d = points[(j + 1) % points.length];
|
|
2756
|
+
if (edgesCross(a, b, c, d)) return true;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
return false;
|
|
2760
|
+
}
|
|
2761
|
+
function insideTriangle(a, b, c, point) {
|
|
2762
|
+
return cross2(a, b, point) >= -1e-8 && cross2(b, c, point) >= -1e-8 && cross2(c, a, point) >= -1e-8;
|
|
2763
|
+
}
|
|
2764
|
+
function triangulate(points) {
|
|
2765
|
+
const order = points.map((_, index) => index);
|
|
2766
|
+
if (signedArea(points) < 0) order.reverse();
|
|
2767
|
+
const triangles = [];
|
|
2768
|
+
let guard = 0;
|
|
2769
|
+
while (order.length > 3 && guard++ < points.length * points.length) {
|
|
2770
|
+
let clipped = false;
|
|
2771
|
+
for (let i = 0; i < order.length; i++) {
|
|
2772
|
+
const previous = order[(i + order.length - 1) % order.length];
|
|
2773
|
+
const current = order[i];
|
|
2774
|
+
const next = order[(i + 1) % order.length];
|
|
2775
|
+
if (cross2(
|
|
2776
|
+
points[previous],
|
|
2777
|
+
points[current],
|
|
2778
|
+
points[next]
|
|
2779
|
+
) <= 1e-8)
|
|
2780
|
+
continue;
|
|
2781
|
+
let contains = false;
|
|
2782
|
+
for (const candidate of order) {
|
|
2783
|
+
if (candidate !== previous && candidate !== current && candidate !== next && insideTriangle(
|
|
2784
|
+
points[previous],
|
|
2785
|
+
points[current],
|
|
2786
|
+
points[next],
|
|
2787
|
+
points[candidate]
|
|
2788
|
+
)) {
|
|
2789
|
+
contains = true;
|
|
2790
|
+
break;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
if (contains) continue;
|
|
2794
|
+
triangles.push(previous, current, next);
|
|
2795
|
+
order.splice(i, 1);
|
|
2796
|
+
clipped = true;
|
|
2797
|
+
break;
|
|
2798
|
+
}
|
|
2799
|
+
if (!clipped) return void 0;
|
|
2800
|
+
}
|
|
2801
|
+
if (order.length !== 3) return void 0;
|
|
2802
|
+
triangles.push(order[0], order[1], order[2]);
|
|
2803
|
+
return triangles;
|
|
2804
|
+
}
|
|
2805
|
+
function pushVertex(vertices, position, normal, uv) {
|
|
2806
|
+
const index = vertices.length / FACTORY_FLOATS_PER_VERTEX;
|
|
2807
|
+
vertices.push(
|
|
2808
|
+
position[0],
|
|
2809
|
+
position[1],
|
|
2810
|
+
position[2],
|
|
2811
|
+
normal[0],
|
|
2812
|
+
normal[1],
|
|
2813
|
+
normal[2],
|
|
2814
|
+
uv[0],
|
|
2815
|
+
uv[1]
|
|
2816
|
+
);
|
|
2817
|
+
return index;
|
|
2818
|
+
}
|
|
2819
|
+
function pushTriangle(vertices, indices, a, b, c) {
|
|
2820
|
+
indices.push(
|
|
2821
|
+
pushVertex(vertices, a.p, a.n, a.uv),
|
|
2822
|
+
pushVertex(vertices, b.p, b.n, b.uv),
|
|
2823
|
+
pushVertex(vertices, c.p, c.n, c.uv)
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
function createExtrusionGeometry(contour, depth) {
|
|
2827
|
+
if (!Number.isFinite(depth) || depth <= 0) return invalid2("depth", "must be positive and finite");
|
|
2828
|
+
const points = contour.length > 1 && contour[0]?.x === contour[contour.length - 1]?.x && contour[0]?.y === contour[contour.length - 1]?.y ? contour.slice(0, -1) : [...contour];
|
|
2829
|
+
if (points.length < 3 || points.some((point) => !finitePoint2(point)))
|
|
2830
|
+
return invalid2("contour", "needs at least three finite points");
|
|
2831
|
+
const area2 = signedArea(points);
|
|
2832
|
+
if (Math.abs(area2) < 1e-8) return invalid2("contour", "area must be non-zero");
|
|
2833
|
+
if (selfIntersects(points)) return invalid2("contour", "must not self-intersect");
|
|
2834
|
+
const capTriangles = triangulate(points);
|
|
2835
|
+
if (capTriangles === void 0) return invalid2("contour", "could not be triangulated");
|
|
2836
|
+
const vertices = [];
|
|
2837
|
+
const indices = [];
|
|
2838
|
+
const half = depth / 2;
|
|
2839
|
+
for (let i = 0; i < capTriangles.length; i += 3) {
|
|
2840
|
+
const a = points[capTriangles[i]];
|
|
2841
|
+
const b = points[capTriangles[i + 1]];
|
|
2842
|
+
const c = points[capTriangles[i + 2]];
|
|
2843
|
+
pushTriangle(
|
|
2844
|
+
vertices,
|
|
2845
|
+
indices,
|
|
2846
|
+
{ p: [a.x, a.y, half], n: [0, 0, 1], uv: [a.x, a.y] },
|
|
2847
|
+
{ p: [b.x, b.y, half], n: [0, 0, 1], uv: [b.x, b.y] },
|
|
2848
|
+
{ p: [c.x, c.y, half], n: [0, 0, 1], uv: [c.x, c.y] }
|
|
2849
|
+
);
|
|
2850
|
+
pushTriangle(
|
|
2851
|
+
vertices,
|
|
2852
|
+
indices,
|
|
2853
|
+
{ p: [c.x, c.y, -half], n: [0, 0, -1], uv: [c.x, c.y] },
|
|
2854
|
+
{ p: [b.x, b.y, -half], n: [0, 0, -1], uv: [b.x, b.y] },
|
|
2855
|
+
{ p: [a.x, a.y, -half], n: [0, 0, -1], uv: [a.x, a.y] }
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
let perimeter = 0;
|
|
2859
|
+
for (let i = 0; i < points.length; i++) {
|
|
2860
|
+
const a = points[i];
|
|
2861
|
+
const b = points[(i + 1) % points.length];
|
|
2862
|
+
perimeter += Math.hypot(b.x - a.x, b.y - a.y);
|
|
2863
|
+
}
|
|
2864
|
+
let distance = 0;
|
|
2865
|
+
for (let i = 0; i < points.length; i++) {
|
|
2866
|
+
const a = points[i];
|
|
2867
|
+
const b = points[(i + 1) % points.length];
|
|
2868
|
+
const edge = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2869
|
+
const u0 = perimeter === 0 ? 0 : distance / perimeter;
|
|
2870
|
+
const u1 = perimeter === 0 ? 1 : (distance + edge) / perimeter;
|
|
2871
|
+
const nx = b.y - a.y;
|
|
2872
|
+
const ny = -(b.x - a.x);
|
|
2873
|
+
const length = Math.hypot(nx, ny) || 1;
|
|
2874
|
+
const normal = area2 >= 0 ? [nx / length, ny / length, 0] : [-nx / length, -ny / length, 0];
|
|
2875
|
+
if (area2 >= 0) {
|
|
2876
|
+
pushTriangle(
|
|
2877
|
+
vertices,
|
|
2878
|
+
indices,
|
|
2879
|
+
{ p: [a.x, a.y, -half], n: normal, uv: [u0, 0] },
|
|
2880
|
+
{ p: [b.x, b.y, -half], n: normal, uv: [u1, 0] },
|
|
2881
|
+
{ p: [b.x, b.y, half], n: normal, uv: [u1, 1] }
|
|
2882
|
+
);
|
|
2883
|
+
pushTriangle(
|
|
2884
|
+
vertices,
|
|
2885
|
+
indices,
|
|
2886
|
+
{ p: [a.x, a.y, -half], n: normal, uv: [u0, 0] },
|
|
2887
|
+
{ p: [b.x, b.y, half], n: normal, uv: [u1, 1] },
|
|
2888
|
+
{ p: [a.x, a.y, half], n: normal, uv: [u0, 1] }
|
|
2889
|
+
);
|
|
2890
|
+
} else {
|
|
2891
|
+
pushTriangle(
|
|
2892
|
+
vertices,
|
|
2893
|
+
indices,
|
|
2894
|
+
{ p: [a.x, a.y, -half], n: normal, uv: [u0, 0] },
|
|
2895
|
+
{ p: [b.x, b.y, half], n: normal, uv: [u1, 1] },
|
|
2896
|
+
{ p: [b.x, b.y, -half], n: normal, uv: [u1, 0] }
|
|
2897
|
+
);
|
|
2898
|
+
pushTriangle(
|
|
2899
|
+
vertices,
|
|
2900
|
+
indices,
|
|
2901
|
+
{ p: [a.x, a.y, -half], n: normal, uv: [u0, 0] },
|
|
2902
|
+
{ p: [a.x, a.y, half], n: normal, uv: [u0, 1] },
|
|
2903
|
+
{ p: [b.x, b.y, half], n: normal, uv: [u1, 1] }
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
distance += edge;
|
|
2907
|
+
}
|
|
2908
|
+
return meshFromInterleaved(new Float32Array(vertices), new Uint32Array(indices));
|
|
2909
|
+
}
|
|
2910
|
+
function createSweepGeometry(path, radius, radialSegments = 12) {
|
|
2911
|
+
if (!Number.isFinite(radius) || radius <= 0)
|
|
2912
|
+
return invalid2("radius", "must be positive and finite");
|
|
2913
|
+
const segments = radialSegments | 0;
|
|
2914
|
+
if (path.length < 2 || path.some((point) => point.length !== 3 || point.some((value) => !Number.isFinite(value))))
|
|
2915
|
+
return invalid2("path", "needs at least two finite 3D points");
|
|
2916
|
+
if (segments < 3) return invalid2("radialSegments", "must be at least 3");
|
|
2917
|
+
const first = path[0];
|
|
2918
|
+
const last = path[path.length - 1];
|
|
2919
|
+
const closed = path.length > 3 && Math.hypot(first[0] - last[0], first[1] - last[1], first[2] - last[2]) <= 1e-8;
|
|
2920
|
+
for (let left = 0; left < path.length; left++) {
|
|
2921
|
+
const a = path[left];
|
|
2922
|
+
for (let right = left + 1; right < path.length; right++) {
|
|
2923
|
+
const b = path[right];
|
|
2924
|
+
if (closed && left === 0 && right === path.length - 1) continue;
|
|
2925
|
+
if (Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) <= 1e-8) {
|
|
2926
|
+
return invalid2("path", "must not contain repeated points");
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
const lengths = [0];
|
|
2931
|
+
for (let i = 1; i < path.length; i++) {
|
|
2932
|
+
const previous = path[i - 1];
|
|
2933
|
+
const current = path[i];
|
|
2934
|
+
lengths.push(
|
|
2935
|
+
lengths[i - 1] + Math.hypot(current[0] - previous[0], current[1] - previous[1], current[2] - previous[2])
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
const total = lengths[lengths.length - 1];
|
|
2939
|
+
if (!(total > 0)) return invalid2("path", "must contain distinct points");
|
|
2940
|
+
const vertices = [];
|
|
2941
|
+
const indices = [];
|
|
2942
|
+
for (let row = 0; row < path.length; row++) {
|
|
2943
|
+
const point = path[row];
|
|
2944
|
+
const previous = path[Math.max(0, row - 1)];
|
|
2945
|
+
const next = path[Math.min(path.length - 1, row + 1)];
|
|
2946
|
+
let tx = next[0] - previous[0];
|
|
2947
|
+
let ty = next[1] - previous[1];
|
|
2948
|
+
let tz = next[2] - previous[2];
|
|
2949
|
+
const tangentLength = Math.hypot(tx, ty, tz) || 1;
|
|
2950
|
+
tx /= tangentLength;
|
|
2951
|
+
ty /= tangentLength;
|
|
2952
|
+
tz /= tangentLength;
|
|
2953
|
+
const reference = Math.abs(ty) < 0.95 ? [0, 1, 0] : [1, 0, 0];
|
|
2954
|
+
let nx = ty * reference[2] - tz * reference[1];
|
|
2955
|
+
let ny = tz * reference[0] - tx * reference[2];
|
|
2956
|
+
let nz = tx * reference[1] - ty * reference[0];
|
|
2957
|
+
const nLength = Math.hypot(nx, ny, nz) || 1;
|
|
2958
|
+
nx /= nLength;
|
|
2959
|
+
ny /= nLength;
|
|
2960
|
+
nz /= nLength;
|
|
2961
|
+
const bx = ty * nz - tz * ny;
|
|
2962
|
+
const by = tz * nx - tx * nz;
|
|
2963
|
+
const bz = tx * ny - ty * nx;
|
|
2964
|
+
for (let column = 0; column <= segments; column++) {
|
|
2965
|
+
const angle = column / segments * Math.PI * 2;
|
|
2966
|
+
const radialX = Math.cos(angle) * nx + Math.sin(angle) * bx;
|
|
2967
|
+
const radialY = Math.cos(angle) * ny + Math.sin(angle) * by;
|
|
2968
|
+
const radialZ = Math.cos(angle) * nz + Math.sin(angle) * bz;
|
|
2969
|
+
pushVertex(
|
|
2970
|
+
vertices,
|
|
2971
|
+
[point[0] + radialX * radius, point[1] + radialY * radius, point[2] + radialZ * radius],
|
|
2972
|
+
[radialX, radialY, radialZ],
|
|
2973
|
+
[column / segments, lengths[row] / total]
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
const stride = segments + 1;
|
|
2978
|
+
const position = (index) => [
|
|
2979
|
+
vertices[index * 8],
|
|
2980
|
+
vertices[index * 8 + 1],
|
|
2981
|
+
vertices[index * 8 + 2]
|
|
2982
|
+
];
|
|
2983
|
+
for (let row = 0; row < path.length - 1; row++) {
|
|
2984
|
+
for (let column = 0; column < segments; column++) {
|
|
2985
|
+
const a = row * stride + column;
|
|
2986
|
+
const b = a + 1;
|
|
2987
|
+
const c = (row + 1) * stride + column + 1;
|
|
2988
|
+
const d = (row + 1) * stride + column;
|
|
2989
|
+
const p = position(a);
|
|
2990
|
+
const q = position(b);
|
|
2991
|
+
const r = position(c);
|
|
2992
|
+
const ab = [q[0] - p[0], q[1] - p[1], q[2] - p[2]];
|
|
2993
|
+
const ac = [r[0] - p[0], r[1] - p[1], r[2] - p[2]];
|
|
2994
|
+
const normal = [
|
|
2995
|
+
ab[1] * ac[2] - ab[2] * ac[1],
|
|
2996
|
+
ab[2] * ac[0] - ab[0] * ac[2],
|
|
2997
|
+
ab[0] * ac[1] - ab[1] * ac[0]
|
|
2998
|
+
];
|
|
2999
|
+
const center = path[row];
|
|
3000
|
+
const outward = [p[0] - center[0], p[1] - center[1], p[2] - center[2]];
|
|
3001
|
+
const dot = normal[0] * outward[0] + normal[1] * outward[1] + normal[2] * outward[2];
|
|
3002
|
+
if (dot > 0) indices.push(a, b, c, a, c, d);
|
|
3003
|
+
else indices.push(a, c, b, a, d, c);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
return meshFromInterleaved(new Float32Array(vertices), new Uint32Array(indices));
|
|
3007
|
+
}
|
|
3008
|
+
function createRevolutionGeometry(profile, radialSegments = 24) {
|
|
3009
|
+
const segments = radialSegments | 0;
|
|
3010
|
+
if (profile.length < 2 || profile.some((point) => !finitePoint2(point) || point.x < 0))
|
|
3011
|
+
return invalid2("profile", "needs at least two finite points with non-negative radius");
|
|
3012
|
+
if (segments < 3) return invalid2("radialSegments", "must be at least 3");
|
|
3013
|
+
const first = profile[0];
|
|
3014
|
+
if (first === void 0 || !profile.some((point) => point.y !== first.y))
|
|
3015
|
+
return invalid2("profile", "must span a non-zero height");
|
|
3016
|
+
for (let left = 0; left < profile.length; left++) {
|
|
3017
|
+
const a = profile[left];
|
|
3018
|
+
for (let right = left + 1; right < profile.length; right++) {
|
|
3019
|
+
const b = profile[right];
|
|
3020
|
+
if (Math.hypot(a.x - b.x, a.y - b.y) <= 1e-8) {
|
|
3021
|
+
return invalid2("profile", "must not contain repeated points");
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
const vertices = [];
|
|
3026
|
+
const indices = [];
|
|
3027
|
+
for (let row = 0; row < profile.length; row++) {
|
|
3028
|
+
const point = profile[row];
|
|
3029
|
+
const previous = profile[Math.max(0, row - 1)];
|
|
3030
|
+
const next = profile[Math.min(profile.length - 1, row + 1)];
|
|
3031
|
+
const dr = next.x - previous.x;
|
|
3032
|
+
const dy = next.y - previous.y;
|
|
3033
|
+
const normalLength = Math.hypot(dy, dr) || 1;
|
|
3034
|
+
for (let column = 0; column <= segments; column++) {
|
|
3035
|
+
const u = column / segments;
|
|
3036
|
+
const angle = u * Math.PI * 2;
|
|
3037
|
+
const c = Math.cos(angle);
|
|
3038
|
+
const s = Math.sin(angle);
|
|
3039
|
+
pushVertex(
|
|
3040
|
+
vertices,
|
|
3041
|
+
[point.x * c, point.y, point.x * s],
|
|
3042
|
+
[c * dy / normalLength, -dr / normalLength, s * dy / normalLength],
|
|
3043
|
+
[u, row / (profile.length - 1)]
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
const stride = segments + 1;
|
|
3048
|
+
for (let row = 0; row < profile.length - 1; row++) {
|
|
3049
|
+
for (let column = 0; column < segments; column++) {
|
|
3050
|
+
const a = row * stride + column;
|
|
3051
|
+
const b = a + 1;
|
|
3052
|
+
const c = (row + 1) * stride + column + 1;
|
|
3053
|
+
const d = (row + 1) * stride + column;
|
|
3054
|
+
indices.push(a, d, b, b, d, c);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return meshFromInterleaved(new Float32Array(vertices), new Uint32Array(indices));
|
|
3058
|
+
}
|
|
2709
3059
|
var PATCHES = new Uint16Array([
|
|
2710
3060
|
0,
|
|
2711
3061
|
1,
|
|
@@ -4137,7 +4487,7 @@ function evaluatePatch(surface, s, t, fitLid, blinn) {
|
|
|
4137
4487
|
}
|
|
4138
4488
|
return { point, ds, dt };
|
|
4139
4489
|
}
|
|
4140
|
-
function
|
|
4490
|
+
function cross3(a, b) {
|
|
4141
4491
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
4142
4492
|
}
|
|
4143
4493
|
function normalize(value) {
|
|
@@ -4179,7 +4529,7 @@ function createTeapotGeometry(size = 0.8, segments = 18, bottom = true, lid = tr
|
|
|
4179
4529
|
for (let tStep = 0; tStep <= steps; tStep++) {
|
|
4180
4530
|
const t = tStep / steps;
|
|
4181
4531
|
const evaluated = evaluatePatch(surface, s, t, fitLid, blinn);
|
|
4182
|
-
const normalRaw =
|
|
4532
|
+
const normalRaw = cross3(evaluated.dt, evaluated.ds);
|
|
4183
4533
|
const normal = evaluated.point[0] === 0 && evaluated.point[1] === 0 ? [0, evaluated.point[2] > maxHeight2 ? 1 : -1, 0] : normalize([normalRaw[0], normalRaw[2], -normalRaw[1]]);
|
|
4184
4534
|
const position = [
|
|
4185
4535
|
Math.fround(trueSize * evaluated.point[0]),
|
|
@@ -4282,6 +4632,6 @@ function createTorusGeometry(radius, tube, radialSegments = 8, tubularSegments =
|
|
|
4282
4632
|
return meshFromInterleaved(vertices, indices);
|
|
4283
4633
|
}
|
|
4284
4634
|
|
|
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 };
|
|
4635
|
+
export { DEFAULT_VERTEX_ATTRIBUTE_MAP, PROCEDURAL_FLOATS_PER_VERTEX, SKIN_VERTEX_ATTRIBUTE_MAP, VertexAttributePackError, buildMeshAttributeMapForUvSets, compute2dBounds, computeTangentVec4, create2dGeometry, create2dRingGeometry, createBoxGeometry, createCapsuleGeometry, createConeGeometry, createCylinderGeometry, createEdgesGeometry, createExtrusionGeometry, createMeshBuilder, createPlaneGeometry, createPrimitiveMesh, createProceduralMesh, createRevolutionGeometry, createSphereGeometry, createSweepGeometry, createTeapotGeometry, createTorusGeometry, createWireframeGeometry, decodeMeshBinary, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection, deriveVertexCount, deriveVertexLayoutProjection, deriveVertexLayoutProjectionFromMask, meshAssetContribution, meshAssetDecoder, meshAssetKind, meshFromInterleaved, normalizeMeshPayload, packInterleavedVertexAttributes };
|
|
4286
4636
|
//# sourceMappingURL=index.mjs.map
|
|
4287
4637
|
//# sourceMappingURL=index.mjs.map
|