@absolutejs/geometry 0.2.0 → 0.3.0

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
@@ -131,3 +131,27 @@ touching/crossing shells and exhausted budgets throw rather than guess.
131
131
 
132
132
  Sources: [Manifold's oriented boundary representation](https://manifoldcad.org/docs/jsuser/classes/Manifold.html)
133
133
  and [Jacobson et al.'s generalized winding numbers](https://igl.ethz.ch/projects/winding-number/robust-inside-outside-segmentation-using-generalized-winding-numbers-siggraph-2013-jacobson-et-al.pdf).
134
+
135
+ ## Initial material overlap audit (0.3.0)
136
+
137
+ `auditSolidOverlaps(meshes, kernel, maximumQueries = 256)` checks solid intersection
138
+ volumes before physics publication. Supply `{create(mesh), intersect(a,b)}` using
139
+ your Boolean backend. Handles implement `volume()` and `delete()`; each operation
140
+ must return a new owned handle. A factory that throws must release any allocation
141
+ it made before throwing. The audit releases all successfully returned handles on
142
+ both success and failure, and passes private position/index copies to the kernel.
143
+ No Boolean engine is bundled or required at runtime.
144
+
145
+ The result includes per-mesh oriented volumes, pair indices/intersection volumes,
146
+ fraction of the smaller solid, query count, bounds skips and maximum input volume
147
+ round-trip error. Touching/disjoint bounds need no native intersection. Candidate
148
+ pairs are rebased before construction. Native volume error above 1e-5 or impossible
149
+ intersection volume rejects; tiny nonzero intersections are reported, not rounded
150
+ away. The host chooses its material-resolution threshold explicitly.
151
+
152
+ Use valid oriented material boundaries, including cavity walls. The API does not
153
+ repair overlapping shells within one input mesh, assign source ownership, union
154
+ models, predict extraction clearance or validate dynamic contact penetration.
155
+ Bounds are not proof of overlap. Limits: 64 meshes, 100,000 total vertices,
156
+ 200,000 total triangles, and at most 1,024 requested pair queries. A host worker
157
+ must impose its own lifetime bound on its injected native kernel.
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { alphaFromRgba, alphaFromImageBitmap } from './alpha.js';
3
3
  export { closedComponents } from './closedComponents.js';
4
4
  export { solidPointMembership } from './solidPointMembership.js';
5
5
  export { solidComponents } from './solidComponents.js';
6
+ export { auditSolidOverlaps, type OverlapMesh, type OverlapSolid, type SolidOverlapAudit } from './overlap.js';
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { alphaFromRgba, alphaFromImageBitmap } from './alpha.js';
3
3
  export { closedComponents } from './closedComponents.js';
4
4
  export { solidPointMembership } from './solidPointMembership.js';
5
5
  export { solidComponents } from './solidComponents.js';
6
+ export { auditSolidOverlaps } from './overlap.js';
@@ -0,0 +1,30 @@
1
+ export type OverlapMesh = {
2
+ positions: Float64Array;
3
+ indices: Uint32Array;
4
+ };
5
+ /** The host injects its Boolean kernel; all returned handles must be disposable. */
6
+ export type OverlapSolid = {
7
+ volume(): number;
8
+ delete(): void;
9
+ };
10
+ export type SolidOverlapAudit = ReturnType<typeof auditSolidOverlaps>;
11
+ /** Audit initial material overlap, not dynamic contact depth. Inputs must be valid
12
+ * oriented solid boundaries in one frame. No ownership, de-duplication or repair
13
+ * is inferred. Bounds skip disjoint/touching pairs; candidate pairs are rebased
14
+ * before invoking the host's kernel. Every native handle is released. */
15
+ export declare function auditSolidOverlaps<S extends OverlapSolid>(meshes: readonly OverlapMesh[], kernel: {
16
+ create(mesh: OverlapMesh): S;
17
+ intersect(a: S, b: S): S;
18
+ }, maximumQueries?: number): {
19
+ meshes: number;
20
+ volumes: number[];
21
+ pairs: {
22
+ a: number;
23
+ b: number;
24
+ volume: number;
25
+ fractionOfSmaller: number;
26
+ }[];
27
+ queries: number;
28
+ boundsSkipped: number;
29
+ maximumInputVolumeError: number;
30
+ };
@@ -0,0 +1,61 @@
1
+ import { closedComponents } from './closedComponents.js';
2
+ import { signedVolume } from './signedVolume.js';
3
+ /** Audit initial material overlap, not dynamic contact depth. Inputs must be valid
4
+ * oriented solid boundaries in one frame. No ownership, de-duplication or repair
5
+ * is inferred. Bounds skip disjoint/touching pairs; candidate pairs are rebased
6
+ * before invoking the host's kernel. Every native handle is released. */
7
+ export function auditSolidOverlaps(meshes, kernel, maximumQueries = 256) {
8
+ if (!Array.isArray(meshes) || meshes.length > 64 || !kernel || typeof kernel.create !== 'function' || typeof kernel.intersect !== 'function' ||
9
+ !Number.isSafeInteger(maximumQueries) || maximumQueries < 1 || maximumQueries > 1024 ||
10
+ meshes.reduce((n, m) => n + (m?.positions?.length ?? Infinity), 0) > 300000 ||
11
+ meshes.reduce((n, m) => n + (m?.indices?.length ?? Infinity), 0) > 600000)
12
+ throw Error('Invalid overlap audit budget');
13
+ const records = meshes.map(mesh => {
14
+ if (!mesh.indices.length)
15
+ throw Error('Empty overlap solid');
16
+ closedComponents(mesh.positions, mesh.indices);
17
+ const volume = signedVolume(mesh.positions, mesh.indices);
18
+ if (!(volume > 0) || !Number.isFinite(volume))
19
+ throw Error('Invalid overlap solid volume');
20
+ const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
21
+ mesh.positions.forEach((v, i) => { min[i % 3] = Math.min(min[i % 3], v); max[i % 3] = Math.max(max[i % 3], v); });
22
+ return { ...mesh, volume, min, max };
23
+ });
24
+ const pairs = [];
25
+ let queries = 0, boundsSkipped = 0, maximumInputVolumeError = 0;
26
+ for (let a = 0; a < records.length; a++)
27
+ for (let b = a + 1; b < records.length; b++) {
28
+ const one = records[a], two = records[b];
29
+ if (one.min.some((v, k) => v >= two.max[k] || one.max[k] <= two.min[k])) {
30
+ boundsSkipped++;
31
+ continue;
32
+ }
33
+ if (++queries > maximumQueries)
34
+ throw Error('Overlap query budget exceeded');
35
+ const origin = one.min.map((v, k) => (Math.min(v, two.min[k]) + Math.max(one.max[k], two.max[k])) / 2);
36
+ const handles = [];
37
+ try {
38
+ const solids = [one, two].map(record => {
39
+ const local = Float64Array.from(record.positions, (v, i) => v - origin[i % 3]);
40
+ const solid = kernel.create({ positions: local, indices: record.indices.slice() });
41
+ handles.push(solid);
42
+ const error = Math.abs(solid.volume() / record.volume - 1);
43
+ if (!Number.isFinite(error) || error > 1e-5)
44
+ throw Error('Overlap input precision budget exceeded');
45
+ maximumInputVolumeError = Math.max(maximumInputVolumeError, error);
46
+ return solid;
47
+ });
48
+ const intersection = kernel.intersect(solids[0], solids[1]);
49
+ handles.push(intersection);
50
+ const volume = intersection.volume(), fractionOfSmaller = volume / Math.min(one.volume, two.volume);
51
+ if (!Number.isFinite(volume) || volume < 0 || fractionOfSmaller > 1.00001)
52
+ throw Error('Invalid overlap volume');
53
+ pairs.push({ a, b, volume, fractionOfSmaller });
54
+ }
55
+ finally {
56
+ for (const handle of handles.reverse())
57
+ handle.delete();
58
+ }
59
+ }
60
+ return { meshes: records.length, volumes: records.map(r => r.volume), pairs, queries, boundsSkipped, maximumInputVolumeError };
61
+ }
@@ -0,0 +1 @@
1
+ export declare function signedVolume(p: Float64Array, i: Uint32Array): number;
@@ -0,0 +1,15 @@
1
+ export function signedVolume(p, i) {
2
+ const o = i[0] * 3, ox = p[o], oy = p[o + 1], oz = p[o + 2];
3
+ let sum = 0, error = 0;
4
+ for (let t = 0; t < i.length; t += 3) {
5
+ const a = i[t] * 3, b = i[t + 1] * 3, c = i[t + 2] * 3;
6
+ const ax = p[a] - ox, ay = p[a + 1] - oy, az = p[a + 2] - oz;
7
+ const bx = p[b] - ox, by = p[b + 1] - oy, bz = p[b + 2] - oz;
8
+ const cx = p[c] - ox, cy = p[c + 1] - oy, cz = p[c + 2] - oz;
9
+ const term = (ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) + az * (bx * cy - by * cx)) / 6;
10
+ const increment = term - error, next = sum + increment;
11
+ error = next - sum - increment;
12
+ sum = next;
13
+ }
14
+ return sum;
15
+ }
@@ -1,3 +1,4 @@
1
+ import { signedVolume } from './signedVolume.js';
1
2
  import { closedComponents } from './closedComponents.js';
2
3
  import { solidPointMembership } from './solidPointMembership.js';
3
4
  /** Material components of a valid oriented Boolean boundary, restricted by explicit
@@ -77,18 +78,3 @@ export function solidComponents(positions, indices, owners, maxTriangleTests = 2
77
78
  });
78
79
  return { components, shells: shells.length, cavities: holes.length, pointQueries, triangleTests };
79
80
  }
80
- function signedVolume(p, i) {
81
- const o = i[0] * 3, ox = p[o], oy = p[o + 1], oz = p[o + 2];
82
- let sum = 0, error = 0;
83
- for (let t = 0; t < i.length; t += 3) {
84
- const a = i[t] * 3, b = i[t + 1] * 3, c = i[t + 2] * 3;
85
- const ax = p[a] - ox, ay = p[a + 1] - oy, az = p[a + 2] - oz;
86
- const bx = p[b] - ox, by = p[b + 1] - oy, bz = p[b + 2] - oz;
87
- const cx = p[c] - ox, cy = p[c + 1] - oy, cz = p[c + 2] - oz;
88
- const term = (ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) + az * (bx * cy - by * cx)) / 6;
89
- const increment = term - error, next = sum + increment;
90
- error = next - sum - increment;
91
- sum = next;
92
- }
93
- return sum;
94
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/geometry",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Bounded geometry projection, oriented material components and point membership for worker-side web 3D tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,8 @@
33
33
  "check:package": "bun run build && bun test ./tests"
34
34
  },
35
35
  "devDependencies": {
36
- "typescript": "5.9.3",
37
- "@types/bun": "1.4.2"
36
+ "@types/bun": "1.4.2",
37
+ "manifold-3d": "3.5.1",
38
+ "typescript": "5.9.3"
38
39
  }
39
40
  }