@absolutejs/geometry 0.2.0 → 0.4.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 +67 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/materialPartition.d.ts +39 -0
- package/dist/materialPartition.js +117 -0
- package/dist/overlap.d.ts +30 -0
- package/dist/overlap.js +61 -0
- package/dist/signedVolume.d.ts +1 -0
- package/dist/signedVolume.js +15 -0
- package/dist/solidComponents.js +1 -15
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -131,3 +131,70 @@ 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.
|
|
158
|
+
|
|
159
|
+
## Exclusive material partition (0.4.0)
|
|
160
|
+
|
|
161
|
+
`partitionSolidMaterials(meshes, kernel, maximumOperations = 126)` implements
|
|
162
|
+
ordered solid ownership. Cell i is input i minus the union of earlier inputs.
|
|
163
|
+
The caller explicitly chooses the order; this library does not invent a material
|
|
164
|
+
priority. Inputs must be valid oriented solid boundaries, including cavity walls.
|
|
165
|
+
|
|
166
|
+
Supply `{create(mesh), subtract(a,b), union(a,b), mesh(solid)}`. Native handles
|
|
167
|
+
implement `volume()` and `delete()`; every construction/operation returns a fresh
|
|
168
|
+
handle, and failures inside the adapter must clean up allocations not returned.
|
|
169
|
+
`mesh` returns Float64 positions and Uint32 indices in the supplied local frame.
|
|
170
|
+
Return a valid closed boundary, or two empty arrays for an empty solid. The
|
|
171
|
+
package copies all borrowed arrays and releases native handles on success/failure.
|
|
172
|
+
At most three native handles are retained at once, including the output of the
|
|
173
|
+
current operation; previous union/intermediate geometry is promptly released.
|
|
174
|
+
|
|
175
|
+
Results include:
|
|
176
|
+
- `cells`: one entry per input, with stable `source` index, mesh and volume.
|
|
177
|
+
A fully covered source remains an explicitly empty cell.
|
|
178
|
+
- `exterior`: the separate whole-union boundary and volume.
|
|
179
|
+
- `inputVolumes`, `authoredVolume`, `materialVolume`, `volumeDifference`,
|
|
180
|
+
`maximumVolumeError` and `operations`. The signed volume difference is not
|
|
181
|
+
clamped; insignificant negative roundoff can remain.
|
|
182
|
+
|
|
183
|
+
Cell walls include internal material interfaces. **Do not render every cell wall
|
|
184
|
+
as exposed material or infer that two touching cells are detached.** Exterior face
|
|
185
|
+
provenance, adjacency, strength, density, fracture surfaces and extraction/physics
|
|
186
|
+
acceptance remain host concerns. In particular this does not turn an existing
|
|
187
|
+
positive-overlap attachment graph into a surface-interface graph.
|
|
188
|
+
|
|
189
|
+
Limits: 1–64 inputs, 100,000 input vertices/200,000 input triangles, up to 126
|
|
190
|
+
Boolean operations, and 200,000 exported vertices/400,000 exported triangles
|
|
191
|
+
across all cells plus exterior. Native input/output volume and total conservation
|
|
192
|
+
errors above 1e-5 reject. These are acceptance budgets, not certified geometric
|
|
193
|
+
error bounds or a proof against a faulty Boolean backend. Topology checks do not
|
|
194
|
+
detect all geometric self-intersections. Use a cancellable worker: a synchronous
|
|
195
|
+
backend call cannot be interrupted by an operation counter.
|
|
196
|
+
|
|
197
|
+
The implementation uses the set identity
|
|
198
|
+
`C_i = S_i ∖ (S_0 ∪ … ∪ S_(i−1))`; its tests use Manifold only as a development
|
|
199
|
+
dependency. [Manifold's documentation](https://manifoldcad.org/docs/jsuser/classes/Manifold.html)
|
|
200
|
+
describes the native Boolean/volume contract. No Boolean engine is bundled.
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ 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';
|
|
7
|
+
export { partitionSolidMaterials, type MaterialPartitionKernel, type SolidMaterialPartition } from './materialPartition.js';
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,5 @@ 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';
|
|
7
|
+
export { partitionSolidMaterials } from './materialPartition.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { OverlapMesh, OverlapSolid } from './overlap.js';
|
|
2
|
+
export type MaterialPartitionKernel<S extends OverlapSolid> = {
|
|
3
|
+
create(mesh: OverlapMesh): S;
|
|
4
|
+
subtract(a: S, b: S): S;
|
|
5
|
+
union(a: S, b: S): S;
|
|
6
|
+
mesh(solid: S): OverlapMesh;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Ordered CSG material ownership: cell i = input i minus all earlier inputs.
|
|
10
|
+
* Input order is the host's explicit policy. The separate union mesh is the
|
|
11
|
+
* exterior; cell boundaries include INTERNAL partition walls and must not be
|
|
12
|
+
* blindly rendered or interpreted as fractures. No attachment/strength inference.
|
|
13
|
+
*
|
|
14
|
+
* The host must supply correct Boolean operations and fresh disposable handles.
|
|
15
|
+
* Meshes are valid, consistently oriented, non-self-intersecting solid boundaries;
|
|
16
|
+
* closedComponents checks topology, not geometric self-intersection. All input
|
|
17
|
+
* geometry is copied/rebased before the host sees it. Returned meshes are copied.
|
|
18
|
+
* Native work is bounded by input/output sizes and operation count, not wall time.
|
|
19
|
+
* Run in a cancellable worker for a wall-clock deadline.
|
|
20
|
+
*/
|
|
21
|
+
export declare function partitionSolidMaterials<S extends OverlapSolid>(meshes: readonly OverlapMesh[], kernel: MaterialPartitionKernel<S>, maximumOperations?: number): {
|
|
22
|
+
cells: ({
|
|
23
|
+
source: number;
|
|
24
|
+
} & OverlapMesh & {
|
|
25
|
+
volume: number;
|
|
26
|
+
})[];
|
|
27
|
+
exterior: {
|
|
28
|
+
positions: Float64Array<ArrayBuffer>;
|
|
29
|
+
indices: Uint32Array<ArrayBuffer>;
|
|
30
|
+
volume: number;
|
|
31
|
+
};
|
|
32
|
+
inputVolumes: number[];
|
|
33
|
+
authoredVolume: number;
|
|
34
|
+
materialVolume: number;
|
|
35
|
+
volumeDifference: number;
|
|
36
|
+
maximumVolumeError: number;
|
|
37
|
+
operations: number;
|
|
38
|
+
};
|
|
39
|
+
export type SolidMaterialPartition = ReturnType<typeof partitionSolidMaterials>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { closedComponents } from './closedComponents.js';
|
|
2
|
+
import { signedVolume } from './signedVolume.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ordered CSG material ownership: cell i = input i minus all earlier inputs.
|
|
5
|
+
* Input order is the host's explicit policy. The separate union mesh is the
|
|
6
|
+
* exterior; cell boundaries include INTERNAL partition walls and must not be
|
|
7
|
+
* blindly rendered or interpreted as fractures. No attachment/strength inference.
|
|
8
|
+
*
|
|
9
|
+
* The host must supply correct Boolean operations and fresh disposable handles.
|
|
10
|
+
* Meshes are valid, consistently oriented, non-self-intersecting solid boundaries;
|
|
11
|
+
* closedComponents checks topology, not geometric self-intersection. All input
|
|
12
|
+
* geometry is copied/rebased before the host sees it. Returned meshes are copied.
|
|
13
|
+
* Native work is bounded by input/output sizes and operation count, not wall time.
|
|
14
|
+
* Run in a cancellable worker for a wall-clock deadline.
|
|
15
|
+
*/
|
|
16
|
+
export function partitionSolidMaterials(meshes, kernel, maximumOperations = 126) {
|
|
17
|
+
if (!Array.isArray(meshes) || !meshes.length || meshes.length > 64 ||
|
|
18
|
+
!kernel || !['create', 'subtract', 'union', 'mesh'].every(k => typeof kernel[k] === 'function') ||
|
|
19
|
+
!Number.isSafeInteger(maximumOperations) || maximumOperations < 0 || maximumOperations > 126 ||
|
|
20
|
+
2 * (meshes.length - 1) > maximumOperations ||
|
|
21
|
+
meshes.reduce((n, m) => n + (m?.positions?.length ?? Infinity), 0) > 300000 ||
|
|
22
|
+
meshes.reduce((n, m) => n + (m?.indices?.length ?? Infinity), 0) > 600000)
|
|
23
|
+
throw Error('Invalid material partition budget');
|
|
24
|
+
const inputs = meshes;
|
|
25
|
+
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
|
|
26
|
+
const volumes = inputs.map(m => {
|
|
27
|
+
if (!(m.positions instanceof Float64Array) || !(m.indices instanceof Uint32Array) || !m.indices.length)
|
|
28
|
+
throw Error('Invalid material partition mesh');
|
|
29
|
+
closedComponents(m.positions, m.indices);
|
|
30
|
+
const v = signedVolume(m.positions, m.indices);
|
|
31
|
+
if (!Number.isFinite(v) || v <= 0)
|
|
32
|
+
throw Error('Invalid material input volume');
|
|
33
|
+
m.positions.forEach((p, i) => { min[i % 3] = Math.min(min[i % 3], p); max[i % 3] = Math.max(max[i % 3], p); });
|
|
34
|
+
return v;
|
|
35
|
+
});
|
|
36
|
+
const origin = min.map((v, k) => v / 2 + max[k] / 2);
|
|
37
|
+
const handles = new Set(), seen = new Set();
|
|
38
|
+
const own = (s) => {
|
|
39
|
+
if (seen.has(s))
|
|
40
|
+
throw Error('Material kernel must return fresh handles');
|
|
41
|
+
seen.add(s);
|
|
42
|
+
handles.add(s);
|
|
43
|
+
return s;
|
|
44
|
+
};
|
|
45
|
+
const release = (s) => { handles.delete(s); s.delete(); };
|
|
46
|
+
let maximumVolumeError = 0, outputVertices = 0, outputIndices = 0, operations = 0;
|
|
47
|
+
const checkVolume = (actual, expected) => {
|
|
48
|
+
const error = Math.abs(actual / expected - 1);
|
|
49
|
+
if (!Number.isFinite(error) || error > 1e-5)
|
|
50
|
+
throw Error('Material volume precision budget exceeded');
|
|
51
|
+
maximumVolumeError = Math.max(maximumVolumeError, error);
|
|
52
|
+
};
|
|
53
|
+
const read = (s) => {
|
|
54
|
+
const volume = s.volume();
|
|
55
|
+
if (!Number.isFinite(volume) || volume < 0)
|
|
56
|
+
throw Error('Invalid partition output volume');
|
|
57
|
+
const raw = kernel.mesh(s);
|
|
58
|
+
if (!(raw?.positions instanceof Float64Array) || !(raw?.indices instanceof Uint32Array) ||
|
|
59
|
+
(outputVertices += raw.positions.length / 3) > 200000 ||
|
|
60
|
+
(outputIndices += raw.indices.length) > 1200000)
|
|
61
|
+
throw Error('Material output budget exceeded');
|
|
62
|
+
const positions = raw.positions.slice(), indices = raw.indices.slice();
|
|
63
|
+
if (!indices.length) {
|
|
64
|
+
if (positions.length || volume !== 0)
|
|
65
|
+
throw Error('Invalid empty material cell');
|
|
66
|
+
return { positions, indices, volume };
|
|
67
|
+
}
|
|
68
|
+
closedComponents(positions, indices);
|
|
69
|
+
checkVolume(signedVolume(positions, indices), volume);
|
|
70
|
+
for (let i = 0; i < positions.length; i++)
|
|
71
|
+
positions[i] = positions[i] + origin[i % 3];
|
|
72
|
+
// Re-check precision after restoring the requested coordinate frame.
|
|
73
|
+
checkVolume(signedVolume(positions, indices), volume);
|
|
74
|
+
return { positions, indices, volume };
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
const cells = [];
|
|
78
|
+
let combined;
|
|
79
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
80
|
+
const m = inputs[i];
|
|
81
|
+
const input = own(kernel.create({
|
|
82
|
+
positions: Float64Array.from(m.positions, (v, j) => v - origin[j % 3]),
|
|
83
|
+
indices: m.indices.slice(),
|
|
84
|
+
}));
|
|
85
|
+
checkVolume(input.volume(), volumes[i]);
|
|
86
|
+
const exclusive = combined ? own(kernel.subtract(input, combined)) : input;
|
|
87
|
+
if (combined)
|
|
88
|
+
operations++;
|
|
89
|
+
if (exclusive.volume() > input.volume() * 1.00001)
|
|
90
|
+
throw Error('Partition subtraction enlarged material');
|
|
91
|
+
cells.push({ source: i, ...read(exclusive) });
|
|
92
|
+
if (combined) {
|
|
93
|
+
release(exclusive);
|
|
94
|
+
const previous = combined;
|
|
95
|
+
combined = own(kernel.union(previous, input));
|
|
96
|
+
operations++;
|
|
97
|
+
release(previous);
|
|
98
|
+
release(input);
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
combined = input;
|
|
102
|
+
}
|
|
103
|
+
const exterior = read(combined);
|
|
104
|
+
const materialVolume = cells.reduce((n, c) => n + c.volume, 0);
|
|
105
|
+
checkVolume(materialVolume, exterior.volume);
|
|
106
|
+
const authoredVolume = volumes.reduce((n, v) => n + v, 0);
|
|
107
|
+
if (exterior.volume > authoredVolume * 1.00001)
|
|
108
|
+
throw Error('Partition union enlarged material');
|
|
109
|
+
return { cells, exterior, inputVolumes: volumes, authoredVolume,
|
|
110
|
+
materialVolume: exterior.volume, volumeDifference: authoredVolume - exterior.volume,
|
|
111
|
+
maximumVolumeError, operations };
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
for (const h of [...handles].reverse())
|
|
115
|
+
h.delete();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -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
|
+
};
|
package/dist/overlap.js
ADDED
|
@@ -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
|
+
}
|
package/dist/solidComponents.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.4.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
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"@types/bun": "1.4.2",
|
|
37
|
+
"manifold-3d": "3.5.1",
|
|
38
|
+
"typescript": "5.9.3"
|
|
38
39
|
}
|
|
39
40
|
}
|