@absolutejs/geometry 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,88 @@
1
+ # Business Source License 1.1
2
+
3
+ **Licensor:** Alex Kahn
4
+
5
+ **Licensed Work:** @absolutejs/geometry (https://github.com/absolutejs/geometry)
6
+
7
+ **Change Date:** October 26, 2030
8
+
9
+ **Change License:** Apache License, Version 2.0
10
+
11
+ ---
12
+
13
+ ## Terms
14
+
15
+ The Licensor hereby grants you the right to copy, modify, create derivative
16
+ works, redistribute, and make non-production use of the Licensed Work. The
17
+ Licensor may make an Additional Use Grant, permitting limited production use.
18
+
19
+ ### Additional Use Grant
20
+
21
+ You may use the Licensed Work in production, provided your use does not include
22
+ any of the following:
23
+
24
+ 1. **Offering a Competing Service.** You may not offer the Licensed Work, or
25
+ any derivative or substantial portion of it, to third parties as a hosted or
26
+ managed service that competes with a hosted geometry processing or 3D asset analysis service. This includes any
27
+ product whose primary value to its users is the functionality the Licensed
28
+ Work provides.
29
+
30
+ 2. **Resale or Redistribution as a Standalone Product.** You may not sell,
31
+ license, or distribute the Licensed Work, or any derivative or fork of it,
32
+ as a standalone commercial product.
33
+
34
+ 3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
35
+ the Licensed Work must prominently credit AbsoluteJS and include a link to
36
+ the original project repository (https://github.com/absolutejs/geometry).
37
+
38
+ For clarity, the following uses are expressly permitted:
39
+
40
+ - Using the Licensed Work to build and operate your own applications, websites,
41
+ internal tools, or SaaS products (whether commercial or non-commercial), so
42
+ long as the Licensed Work itself is not the primary product you are selling.
43
+ - Using the Licensed Work as a dependency in commercial software you build and
44
+ sell, as long as the software is not itself a competing managed service of
45
+ the kind described in clause 1.
46
+ - Providing consulting, development, or professional services to clients using
47
+ the Licensed Work.
48
+ - Forking and modifying the Licensed Work for your own internal use, provided
49
+ attribution is maintained.
50
+
51
+ ### Change Date and Change License
52
+
53
+ On the Change Date specified above, or on such other date as the Licensor may
54
+ specify by written notice, the Licensed Work will be made available under the
55
+ Change License (Apache License, Version 2.0). Until the Change Date, the terms
56
+ of this Business Source License 1.1 apply.
57
+
58
+ ### Trademark
59
+
60
+ This license does not grant you any rights to use the "AbsoluteJS" or
61
+ "@absolutejs" name, logo, or any related trademarks. Forks and derivative works
62
+ must not be named or branded in a manner that suggests endorsement by or
63
+ affiliation with AbsoluteJS or the Licensor.
64
+
65
+ ### Notices
66
+
67
+ You must not remove or obscure any licensing, copyright, or other notices
68
+ included in the Licensed Work.
69
+
70
+ ### No Warranty
71
+
72
+ THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
73
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
74
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
75
+ EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
76
+ WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
77
+ IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
78
+ LICENSED WORK.
79
+
80
+ ---
81
+
82
+ ## Contact
83
+
84
+ For commercial licensing inquiries or additional permissions, contact:
85
+
86
+ - **Alex Kahn**
87
+ - alexkahndev@gmail.com
88
+ - alexkahndev.github.io
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # @absolutejs/geometry
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Worker-side measurement of alpha-masked triangle geometry. The first API measures
4
+ orthographic coverage without Three.js, a renderer, or a GPU. This is useful for
5
+ asset inspection, canopy gap measurement, and tools in a 3D website builder.
6
+
7
+ ```ts
8
+ import { projectAlphaCoverage } from '@absolutejs/geometry';
9
+
10
+ const measurement = projectAlphaCoverage({
11
+ positions: new Float64Array([0, 0, 0, 1, 0, 0, 0, 0, 1]),
12
+ uv: new Float32Array([0, 0, 1, 0, 0, 1]),
13
+ indices: new Uint16Array([0, 1, 2]),
14
+ alpha: new Uint8Array([255]),
15
+ textureWidth: 1,
16
+ textureHeight: 1,
17
+ cutoff: 0.4,
18
+ resolution: 256,
19
+ });
20
+ console.log(measurement.footprintArea, measurement.gapFraction);
21
+ ```
22
+
23
+ ## Coordinate and texture contract
24
+
25
+ Positions are xyz in the caller's projection frame, projected onto xz. Supply
26
+ transformed positions for the actual pose, preferably in local double precision.
27
+ An origin already rounded away in Float32 cannot be recovered. Indexed vertices
28
+ define a convex reference hull; its area includes transparent card extents and
29
+ gaps between disconnected leaves. This is a declared reference footprint, not a
30
+ botanical crown boundary. Units are caller-defined; areas use squared units.
31
+
32
+ UVs must already include any texture transform. Alpha is row-major, one byte per
33
+ texel, with v=0 at the first row: apply any image flip before calling. Bilinear
34
+ sampling uses the base texture level, wrapS/wrapT default to clamp, and alpha at
35
+ or above cutoff is covered. Repeat and mirrored repeat are supported. Faces are
36
+ two-sided; overlapping faces form a union. No color-space conversion applies to
37
+ alpha. Material opacity, vertex alpha, multiple materials and blend materials
38
+ must be resolved by the caller; this API does not silently approximate them.
39
+
40
+ ## Results and accuracy
41
+
42
+ `cells` is a resolution-squared mask: 0 outside the reference hull, 1 open,
43
+ 2 covered. Rows run from minZ to maxZ; columns from minX to maxX. `bounds` is
44
+ [minX, minZ, maxX, maxZ]. `footprintArea` is the analytic hull area;
45
+ `sampledFootprintArea` and `sampledCoveredArea` expose center-sampling error.
46
+ `coveredArea` normalizes the sampled coverage fraction to the analytic hull.
47
+ `gapFraction` is the open fraction of sampled hull cells.
48
+
49
+ Center sampling can miss thin geometry and small holes. Compare resolutions for
50
+ your asset; convergence is evidence, not a rigorous error bound. The game asset
51
+ audit in docs covers all eight canonical trees plus deformed and fallen examples.
52
+ This does not measure light transmission, rainfall interception, storage capacity,
53
+ wind deformation, mip-filtered screen coverage, or hydrologic history.
54
+
55
+ ## Cost and ownership
56
+
57
+ Run this synchronous function in a worker or an offline asset pipeline, never a
58
+ render loop. It does not mutate input buffers. Transfer owned copies when worker
59
+ ownership must change; do not detach a renderer's borrowed geometry buffers.
60
+
61
+ Limits: 300,000 vertices, 200,000 triangles, 4,194,304 texture texels, resolution
62
+ 8–1024, finite UV magnitudes at most 1,000,000. Triangle-local bounding rectangles
63
+ are charged against `maxPixelTests` (default 50 million, maximum 100 million)
64
+ before rasterization. The counter is conservative even when union early-outs
65
+ skip already covered cells. Hull construction sorts indexed vertices; scanline
66
+ setup walks convex hull edges. These limits bound work and allocation, not wall
67
+ time. Worker owners should additionally enforce deadlines and cancellation.
68
+ Malformed, unresolved, degenerate and over-budget inputs throw.
69
+
70
+ ## Development
71
+
72
+ BVM selects Bun `1.4.4+absolute.1` from `.bun-version`.
73
+ Run `bun install --frozen-lockfile` and `bun run check:package`.
74
+ `bun scripts/audit-game-canopies.ts` additionally requires the Hello World checkout
75
+ and its development asset tooling; see docs/canopy-audit/README.md.
76
+
77
+ Licensed under BSL-1.1; see LICENSE for the additional use grant and change date.
78
+
79
+ ## Worker-side alpha preparation (0.1.1)
80
+
81
+ `alphaFromRgba(rgba, width, height)` copies one alpha byte per RGBA8 texel into an
82
+ owned Uint8Array. It accepts Uint8Array or Uint8ClampedArray and validates the
83
+ exact byte length and the same 4,194,304-texel budget before allocation.
84
+
85
+ `alphaFromImageBitmap(bitmap)` reads an already decoded bitmap using a temporary
86
+ OffscreenCanvas 2D context with `willReadFrequently: true`. Call it in a worker;
87
+ it performs synchronous CPU readback wherever it is called. The temporary canvas
88
+ is released afterward. It does not create a WebGL context, close the input bitmap,
89
+ or detach any borrowed renderer resource. The caller owns bitmap cleanup.
90
+ Unsupported runtimes, closed/invalid bitmaps and oversized images fail explicitly.
91
+ There is no hidden main-thread fallback.
92
+
93
+ ImageBitmap supports structured cloning as well as transfer. The browser-worker
94
+ qualification checks exact alpha values and that closing a cloned worker image
95
+ leaves its source usable. Transfer only owned copies if using a transfer list.
96
+ See the [HTML ImageBitmap ownership contract](https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#imagebitmap)
97
+ and [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas).
98
+ These helpers prepare raster inputs; they do not assign hydrologic meaning to alpha.
@@ -0,0 +1,5 @@
1
+ /** Copy one alpha byte per row-major RGBA8 texel. Does not mutate borrowed pixels. */
2
+ export declare function alphaFromRgba(rgba: Uint8Array | Uint8ClampedArray, width: number, height: number): Uint8Array;
3
+ /** Worker/setup-time CPU readback. The caller owns and closes the bitmap.
4
+ * This never closes/detaches the caller's image and never creates a WebGL context. */
5
+ export declare function alphaFromImageBitmap(bitmap: ImageBitmap): Uint8Array;
package/dist/alpha.js ADDED
@@ -0,0 +1,37 @@
1
+ const MAX_TEXELS = 4_194_304;
2
+ function validateSize(width, height) {
3
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 1 || height < 1 || width * height > MAX_TEXELS)
4
+ throw Error('Invalid or oversized alpha source');
5
+ }
6
+ /** Copy one alpha byte per row-major RGBA8 texel. Does not mutate borrowed pixels. */
7
+ export function alphaFromRgba(rgba, width, height) {
8
+ validateSize(width, height);
9
+ if (!(rgba instanceof Uint8Array || rgba instanceof Uint8ClampedArray) || rgba.length !== width * height * 4)
10
+ throw Error('Expected tightly packed RGBA8 alpha source');
11
+ const alpha = new Uint8Array(width * height);
12
+ for (let i = 0; i < alpha.length; i++)
13
+ alpha[i] = rgba[i * 4 + 3];
14
+ return alpha;
15
+ }
16
+ /** Worker/setup-time CPU readback. The caller owns and closes the bitmap.
17
+ * This never closes/detaches the caller's image and never creates a WebGL context. */
18
+ export function alphaFromImageBitmap(bitmap) {
19
+ if (typeof ImageBitmap === 'undefined' || !(bitmap instanceof ImageBitmap))
20
+ throw Error('Expected an available ImageBitmap');
21
+ validateSize(bitmap.width, bitmap.height);
22
+ if (typeof OffscreenCanvas === 'undefined')
23
+ throw Error('OffscreenCanvas is unavailable for alpha readback');
24
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
25
+ try {
26
+ const context = canvas.getContext('2d', { willReadFrequently: true });
27
+ if (!context)
28
+ throw Error('Canvas2D is unavailable for alpha readback');
29
+ context.drawImage(bitmap, 0, 0);
30
+ const pixels = context.getImageData(0, 0, bitmap.width, bitmap.height);
31
+ return alphaFromRgba(pixels.data, pixels.width, pixels.height);
32
+ }
33
+ finally {
34
+ canvas.width = 0;
35
+ canvas.height = 0;
36
+ }
37
+ }
@@ -0,0 +1,2 @@
1
+ export { projectAlphaCoverage, type AlphaProjectionInput, type AlphaProjection, } from "./projection.js";
2
+ export { alphaFromRgba, alphaFromImageBitmap } from './alpha.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { projectAlphaCoverage, } from "./projection.js";
2
+ export { alphaFromRgba, alphaFromImageBitmap } from './alpha.js';
@@ -0,0 +1,37 @@
1
+ export type AlphaProjectionInput = {
2
+ /** xyz coordinates in the requested projection frame; projection is onto xz. */
3
+ positions: Float32Array | Float64Array;
4
+ uv: Float32Array | Float64Array;
5
+ indices: Uint16Array | Uint32Array;
6
+ alpha: Uint8Array;
7
+ textureWidth: number;
8
+ textureHeight: number;
9
+ cutoff: number;
10
+ wrapS?: "clamp" | "repeat" | "mirror";
11
+ wrapT?: "clamp" | "repeat" | "mirror";
12
+ resolution?: number;
13
+ maxPixelTests?: number;
14
+ };
15
+ export type AlphaProjection = {
16
+ revision: "alpha-projection-1";
17
+ resolution: number;
18
+ /** 0 outside projected vertex hull, 1 open ray, 2 alpha-covered ray. */
19
+ cells: Uint8Array;
20
+ bounds: [number, number, number, number];
21
+ footprintArea: number;
22
+ sampledFootprintArea: number;
23
+ sampledCoveredArea: number;
24
+ coveredArea: number;
25
+ gapFraction: number;
26
+ coveredCells: number;
27
+ footprintCells: number;
28
+ pixelTests: number;
29
+ alphaTests: number;
30
+ triangles: number;
31
+ cellArea: number;
32
+ };
33
+ /** Deterministic setup/worker-time orthographic coverage. Triangle-local raster
34
+ * bounds and union early-outs avoid a ray/triangle cartesian product. The convex
35
+ * projected vertex hull defines the explicit reference area. Center sampling is
36
+ * approximate; qualify resolution convergence. No water, transmission or wind. */
37
+ export declare function projectAlphaCoverage(input: AlphaProjectionInput): AlphaProjection;
@@ -0,0 +1,188 @@
1
+ /** Deterministic setup/worker-time orthographic coverage. Triangle-local raster
2
+ * bounds and union early-outs avoid a ray/triangle cartesian product. The convex
3
+ * projected vertex hull defines the explicit reference area. Center sampling is
4
+ * approximate; qualify resolution convergence. No water, transmission or wind. */
5
+ export function projectAlphaCoverage(input) {
6
+ const { positions: p, uv, indices, alpha, textureWidth: w, textureHeight: h, cutoff, } = input;
7
+ const n = input.resolution ?? 256, limit = input.maxPixelTests ?? 50000000;
8
+ const wrapS = input.wrapS ?? "clamp", wrapT = input.wrapT ?? "clamp";
9
+ if (!(p instanceof Float32Array || p instanceof Float64Array) ||
10
+ !(uv instanceof Float32Array || uv instanceof Float64Array) ||
11
+ !(indices instanceof Uint16Array || indices instanceof Uint32Array) ||
12
+ !(alpha instanceof Uint8Array) ||
13
+ p.length % 3 ||
14
+ p.length < 9 ||
15
+ p.length > 900000 ||
16
+ uv.length !== (p.length / 3) * 2 ||
17
+ indices.length % 3 ||
18
+ indices.length < 3 ||
19
+ indices.length > 600000 ||
20
+ !Number.isInteger(w) ||
21
+ !Number.isInteger(h) ||
22
+ w < 1 ||
23
+ h < 1 ||
24
+ w * h > 4194304 ||
25
+ alpha.length !== w * h ||
26
+ !Number.isFinite(cutoff) ||
27
+ cutoff <= 0 ||
28
+ cutoff >= 1 ||
29
+ !Number.isInteger(n) ||
30
+ n < 8 ||
31
+ n > 1024 ||
32
+ !Number.isSafeInteger(limit) ||
33
+ limit < 1 ||
34
+ limit > 100000000 ||
35
+ !["clamp", "repeat", "mirror"].includes(wrapS) ||
36
+ !["clamp", "repeat", "mirror"].includes(wrapT))
37
+ throw Error("Invalid alpha projection input");
38
+ const count = p.length / 3;
39
+ let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;
40
+ const used = new Uint8Array(count);
41
+ for (const id of indices) {
42
+ if (id >= count)
43
+ throw Error("Invalid projection index");
44
+ used[id] = 1;
45
+ }
46
+ for (let i = 0; i < count; i++) {
47
+ if (![p[i * 3], p[i * 3 + 1], p[i * 3 + 2], uv[i * 2], uv[i * 2 + 1]].every(Number.isFinite) ||
48
+ Math.abs(uv[i * 2]) > 1e6 ||
49
+ Math.abs(uv[i * 2 + 1]) > 1e6)
50
+ throw Error("Nonfinite or unbounded projection vertex");
51
+ if (!used[i])
52
+ continue;
53
+ minX = Math.min(minX, p[i * 3]);
54
+ maxX = Math.max(maxX, p[i * 3]);
55
+ minZ = Math.min(minZ, p[i * 3 + 2]);
56
+ maxZ = Math.max(maxZ, p[i * 3 + 2]);
57
+ }
58
+ const spanX = maxX - minX, spanZ = maxZ - minZ;
59
+ if (!Number.isFinite(spanX * spanZ) || spanX <= 0 || spanZ <= 0)
60
+ throw Error("Degenerate projection footprint");
61
+ const coordinates = new Float64Array(count * 2), points = [];
62
+ for (let i = 0; i < count; i++) {
63
+ coordinates[i * 2] = p[i * 3] - minX;
64
+ coordinates[i * 2 + 1] = p[i * 3 + 2] - minZ;
65
+ if (used[i])
66
+ points.push(i);
67
+ }
68
+ points.sort((a, b) => coordinates[a * 2] - coordinates[b * 2] ||
69
+ coordinates[a * 2 + 1] - coordinates[b * 2 + 1]);
70
+ const cross = (a, b, c) => (coordinates[b * 2] - coordinates[a * 2]) *
71
+ (coordinates[c * 2 + 1] - coordinates[a * 2 + 1]) -
72
+ (coordinates[b * 2 + 1] - coordinates[a * 2 + 1]) *
73
+ (coordinates[c * 2] - coordinates[a * 2]);
74
+ const lower = [], upper = [];
75
+ for (const id of points) {
76
+ while (lower.length > 1 && cross(lower.at(-2), lower.at(-1), id) <= 0)
77
+ lower.pop();
78
+ lower.push(id);
79
+ }
80
+ for (let i = points.length - 1; i >= 0; i--) {
81
+ const id = points[i];
82
+ while (upper.length > 1 && cross(upper.at(-2), upper.at(-1), id) <= 0)
83
+ upper.pop();
84
+ upper.push(id);
85
+ }
86
+ const hull = lower.slice(0, -1).concat(upper.slice(0, -1));
87
+ let area = 0;
88
+ for (let i = 0; i < hull.length; i++) {
89
+ const a = hull[i], b = hull[(i + 1) % hull.length];
90
+ area +=
91
+ coordinates[a * 2] * coordinates[b * 2 + 1] -
92
+ coordinates[b * 2] * coordinates[a * 2 + 1];
93
+ }
94
+ area = Math.abs(area) / 2;
95
+ if (!Number.isFinite(area) || !(area > 0))
96
+ throw Error("Degenerate projected vertex hull");
97
+ const dx = spanX / n, dz = spanZ / n, cells = new Uint8Array(n * n);
98
+ let footprintCells = 0;
99
+ // Walk hull edges once. A convex hull has at most two crossings per row,
100
+ // avoiding resolution × hull-vertex work on highly detailed assets.
101
+ const lefts = new Float64Array(n).fill(Infinity);
102
+ const rights = new Float64Array(n).fill(-Infinity);
103
+ for (let i = 0; i < hull.length; i++) {
104
+ const a = hull[i], b = hull[(i + 1) % hull.length];
105
+ const az = coordinates[a * 2 + 1], bz = coordinates[b * 2 + 1];
106
+ if (az === bz)
107
+ continue;
108
+ const firstRow = Math.max(0, Math.ceil(Math.min(az, bz) / dz - 0.5));
109
+ const endRow = Math.min(n, Math.ceil(Math.max(az, bz) / dz - 0.5));
110
+ for (let row = firstRow; row < endRow; row++) {
111
+ const z = (row + 0.5) * dz;
112
+ const x = coordinates[a * 2] +
113
+ ((z - az) / (bz - az)) * (coordinates[b * 2] - coordinates[a * 2]);
114
+ lefts[row] = Math.min(lefts[row], x);
115
+ rights[row] = Math.max(rights[row], x);
116
+ }
117
+ }
118
+ for (let row = 0; row < n; row++) {
119
+ const left = lefts[row], right = rights[row];
120
+ const first = Math.max(0, Math.ceil(left / dx - 0.5)), last = Math.min(n - 1, Math.floor(right / dx - 0.5));
121
+ for (let col = first; col <= last; col++) {
122
+ cells[row * n + col] = 1;
123
+ footprintCells++;
124
+ }
125
+ }
126
+ if (!footprintCells)
127
+ throw Error("Projection footprint is unresolved");
128
+ const wrap = (index, size, mode) => {
129
+ if (mode === "clamp")
130
+ return Math.max(0, Math.min(size - 1, index));
131
+ const period = mode === "mirror" ? size * 2 : size, t = ((index % period) + period) % period;
132
+ return t < size ? t : period - 1 - t;
133
+ };
134
+ const sample = (u, v) => {
135
+ const x = u * w - 0.5, y = v * h - 0.5, ix = Math.floor(x), iy = Math.floor(y), fx = x - ix, fy = y - iy;
136
+ const x0 = wrap(ix, w, wrapS), x1 = wrap(ix + 1, w, wrapS), y0 = wrap(iy, h, wrapT), y1 = wrap(iy + 1, h, wrapT);
137
+ return ((alpha[y0 * w + x0] * (1 - fx) + alpha[y0 * w + x1] * fx) * (1 - fy) +
138
+ (alpha[y1 * w + x0] * (1 - fx) + alpha[y1 * w + x1] * fx) * fy);
139
+ };
140
+ let pixelTests = 0, alphaTests = 0, coveredCells = 0;
141
+ for (let t = 0; t < indices.length; t += 3) {
142
+ const a = indices[t], b = indices[t + 1], c = indices[t + 2];
143
+ const ax = coordinates[a * 2], az = coordinates[a * 2 + 1], bx = coordinates[b * 2], bz = coordinates[b * 2 + 1], cx = coordinates[c * 2], cz = coordinates[c * 2 + 1];
144
+ const den = (bx - ax) * (cz - az) - (bz - az) * (cx - ax);
145
+ if (Math.abs(den) <= Number.EPSILON * (spanX * spanZ))
146
+ continue;
147
+ const firstX = Math.max(0, Math.ceil(Math.min(ax, bx, cx) / dx - 0.5)), lastX = Math.min(n - 1, Math.floor(Math.max(ax, bx, cx) / dx - 0.5));
148
+ const firstZ = Math.max(0, Math.ceil(Math.min(az, bz, cz) / dz - 0.5)), lastZ = Math.min(n - 1, Math.floor(Math.max(az, bz, cz) / dz - 0.5));
149
+ const work = Math.max(0, lastX - firstX + 1) * Math.max(0, lastZ - firstZ + 1);
150
+ if (pixelTests + work > limit)
151
+ throw Error("Alpha projection pixel-test budget exceeded");
152
+ pixelTests += work;
153
+ for (let row = firstZ; row <= lastZ; row++)
154
+ for (let col = firstX; col <= lastX; col++) {
155
+ const cell = row * n + col;
156
+ if (cells[cell] !== 1)
157
+ continue;
158
+ const rx = (col + 0.5) * dx - ax, rz = (row + 0.5) * dz - az;
159
+ const b1 = (rx * (cz - az) - rz * (cx - ax)) / den, b2 = ((bx - ax) * rz - (bz - az) * rx) / den, b0 = 1 - b1 - b2;
160
+ if (b0 < -1e-10 || b1 < -1e-10 || b2 < -1e-10)
161
+ continue;
162
+ alphaTests++;
163
+ if (sample(uv[a * 2] * b0 + uv[b * 2] * b1 + uv[c * 2] * b2, uv[a * 2 + 1] * b0 + uv[b * 2 + 1] * b1 + uv[c * 2 + 1] * b2) >=
164
+ cutoff * 255) {
165
+ cells[cell] = 2;
166
+ coveredCells++;
167
+ }
168
+ }
169
+ }
170
+ const cellArea = dx * dz;
171
+ return {
172
+ revision: "alpha-projection-1",
173
+ resolution: n,
174
+ cells,
175
+ bounds: [minX, minZ, maxX, maxZ],
176
+ footprintArea: area,
177
+ sampledFootprintArea: footprintCells * cellArea,
178
+ sampledCoveredArea: coveredCells * cellArea,
179
+ coveredArea: (area * coveredCells) / footprintCells,
180
+ gapFraction: 1 - coveredCells / footprintCells,
181
+ coveredCells,
182
+ footprintCells,
183
+ pixelTests,
184
+ alphaTests,
185
+ triangles: indices.length / 3,
186
+ cellArea,
187
+ };
188
+ }
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@absolutejs/geometry",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Bounded alpha-aware geometry projection for worker-side asset measurement and web 3D tooling.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "sideEffects": false,
21
+ "license": "BSL-1.1",
22
+ "author": "Alex Kahn",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/absolutejs/geometry.git"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test": "bun test ./tests",
33
+ "check:package": "bun run build && bun test ./tests"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "5.9.3",
37
+ "@types/bun": "1.4.2"
38
+ }
39
+ }