libtess-ts 0.0.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,28 @@
1
+ SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
2
+
3
+ Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
4
+ Copyright (C) 2012 Google Inc. All Rights Reserved.
5
+ Copyright (C) 2026 Jeremy Tribby, Countertype LLC. All Rights Reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice including the dates of first publication and either
15
+ this permission notice or a reference to http://oss.sgi.com/projects/FreeB/
16
+ shall be included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21
+ SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Except as contained in this notice, the name of Silicon Graphics, Inc. shall
26
+ not be used in advertising or otherwise to promote the sale, use or other
27
+ dealings in this Software without prior written authorization from Silicon
28
+ Graphics, Inc.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # libtess-ts
2
+
3
+ Optimized TypeScript port of [libtess.js](https://github.com/brendankenny/libtess.js) by Brendan Kenny, itself a port of the SGI GLU tessellator by Eric Veach
4
+
5
+ [![npm](https://img.shields.io/npm/v/libtess-ts)](https://www.npmjs.com/package/libtess-ts)
6
+ [![License](https://img.shields.io/badge/license-SGI--B--2.0-blue)](./LICENSE)
7
+
8
+ ## Performance vs libtess.js
9
+
10
+ This library builds on libtess.js, which faithfully ported the GLU tessellator to JavaScript. The core sweep-line tessellation algorithm is unchanged here; the performance difference (typically 15-30% faster, sometimes more on large inputs) comes from implementation-level changes:
11
+
12
+ - Direct monotone rendering: triangulates monotone faces inline during the sweep, avoiding a separate mesh-modification pass
13
+ - 2D fast path: when you call `gluTessNormal(0, 0, 1)`, vertex projection is folded into `gluTessVertex` instead of requiring a separate loop over all vertices
14
+ - Reduced allocation: scratch buffers, priority queue storage, and temporary objects are reused across calls rather than re-allocated each time
15
+ - Typed arrays: the priority queue heap uses `Int32Array` instead of plain arrays
16
+
17
+ See [changes from libtess.js](#changes-from-libtessjs) for the full list of API differences.
18
+
19
+ ### vs tess2-ts
20
+
21
+ [libtess2](https://github.com/memononen/libtess2) by Mikko Mononen is a performance oriented adaptation of the original SGI GLU tessellator with a simpler API. It reports a 15-50x speedup in C. [tess2-ts](https://github.com/eXponenta/tess2.js) is its JavaScript port
22
+
23
+ In JavaScript, though, the performance picture is different: libtess-ts is typically 40-90% faster than tess2-ts on the same inputs. libtess2's key optimization is a bucketed memory allocator that replaces many small `malloc` calls; a significant win in C, but in JavaScript there is no `malloc` to avoid as the GC manages the heap, so the optimization doesn't carry over
24
+
25
+ See [benchmarks](#benchmarks) for numbers
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install libtess-ts
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```typescript
36
+ import { GluTesselator, GLU_TESS } from 'libtess-ts'
37
+
38
+ const tess = new GluTesselator()
39
+
40
+ // Collect triangle indices
41
+ const indices: number[] = []
42
+ tess.gluTessCallback(GLU_TESS.VERTEX_DATA, (data: number) => {
43
+ indices.push(data)
44
+ })
45
+
46
+ // If your input is 2D (and it probably is), set this
47
+ // Without it the tessellator auto-detects the projection
48
+ // plane, which costs an extra pass over the vertices
49
+ tess.gluTessNormal(0, 0, 1)
50
+
51
+ tess.gluTessBeginPolygon()
52
+
53
+ tess.gluTessBeginContour()
54
+ tess.gluTessVertex([0, 0], 0)
55
+ tess.gluTessVertex([10, 0], 1)
56
+ tess.gluTessVertex([10, 10], 2)
57
+ tess.gluTessVertex([0, 10], 3)
58
+ tess.gluTessEndContour()
59
+
60
+ // Holes are additional contours with opposite winding
61
+ tess.gluTessBeginContour()
62
+ tess.gluTessVertex([2, 2], 4)
63
+ tess.gluTessVertex([2, 8], 5)
64
+ tess.gluTessVertex([8, 8], 6)
65
+ tess.gluTessVertex([8, 2], 7)
66
+ tess.gluTessEndContour()
67
+
68
+ tess.gluTessEndPolygon()
69
+
70
+ // indices now contains triangle vertex indices
71
+ ```
72
+
73
+ ## API
74
+
75
+ ### Core methods
76
+
77
+ | Method | Description |
78
+ |---|---|
79
+ | `gluTessBeginPolygon(data?)` | Start a new polygon. Optional data is passed to `_DATA` callbacks |
80
+ | `gluTessBeginContour()` | Start a new contour (outer boundary or hole) |
81
+ | `gluTessVertex(coords, data?)` | Add a vertex. `coords` is `[x, y]` or `[x, y, z]` |
82
+ | `gluTessEndContour()` | Close the current contour |
83
+ | `gluTessEndPolygon()` | Tessellate and emit results via callbacks |
84
+ | `gluTessNormal(x, y, z)` | Set the projection plane normal. Call `(0, 0, 1)` for 2D input, it's faster |
85
+ | `gluTessProperty(which, value)` | Set a tessellation property (winding rule, boundary-only mode) |
86
+
87
+ ### Properties via `gluTessProperty`
88
+
89
+ | Enum | Value | Description |
90
+ |---|---|---|
91
+ | `GLU_TESS.WINDING_RULE` | 0=ODD, 1=NONZERO, 2=POSITIVE, 3=NEGATIVE, 4=ABS_GEQ_TWO | Winding rule for interior detection |
92
+ | `GLU_TESS.BOUNDARY_ONLY` | 0 or 1 | If 1, emit boundary contours instead of triangles |
93
+ | `GLU_TESS.TOLERANCE` | number | Accepted but ignored (for compatibility) |
94
+
95
+ ### Callbacks
96
+
97
+ Register via `gluTessCallback(type, fn)`:
98
+
99
+ | Type | Callback signature | Description |
100
+ |---|---|---|
101
+ | `GLU_TESS.BEGIN` | `(type: number) => void` | Start of a primitive (`GL_TRIANGLES`) |
102
+ | `GLU_TESS.VERTEX` | `(data: unknown) => void` | Vertex data (the value passed to `gluTessVertex`) |
103
+ | `GLU_TESS.END` | `() => void` | End of a primitive |
104
+ | `GLU_TESS.COMBINE` | `(coords, data, weights) => unknown` | Vertex interpolation at intersections (see below) |
105
+ | `GLU_TESS.EDGE_FLAG` | `(flag: boolean) => void` | Edge boundary flag. Registering this forces individual triangle output |
106
+ | `GLU_TESS.ERROR` | `(errorNumber: number) => void` | Tessellation error (e.g. missing combine callback) |
107
+ | `GLU_TESS.BEGIN_DATA` | `(type: number, polygonData: unknown) => void` | Like `BEGIN`, with polygon data from `gluTessBeginPolygon` |
108
+ | `GLU_TESS.VERTEX_DATA` | `(data: unknown, polygonData: unknown) => void` | Like `VERTEX`, with polygon data |
109
+ | `GLU_TESS.END_DATA` | `(polygonData: unknown) => void` | Like `END`, with polygon data |
110
+ | `GLU_TESS.COMBINE_DATA` | `(coords, data, weights, polygonData) => unknown` | Like `COMBINE`, with polygon data |
111
+ | `GLU_TESS.EDGE_FLAG_DATA` | `(flag: boolean, polygonData: unknown) => void` | Like `EDGE_FLAG`, with polygon data |
112
+ | `GLU_TESS.ERROR_DATA` | `(errorNumber: number, polygonData: unknown) => void` | Like `ERROR`, with polygon data |
113
+
114
+ ### Separate sweep and render
115
+
116
+ If you need both triangles and boundary contours from the same input, use `compute()` to run the sweep once, then call the render methods separately:
117
+
118
+ ```typescript
119
+ import { GluTesselator, WINDING } from 'libtess-ts'
120
+
121
+ const tess = new GluTesselator()
122
+
123
+ tess.gluTessBeginPolygon()
124
+ // ... add contours ...
125
+ // Don't call gluTessEndPolygon() -- use compute() instead
126
+
127
+ tess.compute(WINDING.NONZERO)
128
+
129
+ // Extract triangles first
130
+ tess.renderTriangles()
131
+
132
+ // Then extract boundary contours (destructive -- call after renderTriangles)
133
+ tess.renderBoundary()
134
+ ```
135
+
136
+ ### Combine callback
137
+
138
+ When contours self-intersect, the tessellator needs to create new vertices at the intersection points. It knows where they go, but not what data to put there - that's your job. Supply a combine callback to interpolate vertex attributes:
139
+
140
+ ```typescript
141
+ tess.gluTessCallback(GLU_TESS.COMBINE, (coords, data, weights) => {
142
+ // coords: [x, y, z] of the new vertex
143
+ // data: [v0, v1, v2, v3] -- up to 4 neighboring vertex data values
144
+ // weights: [w0, w1, w2, w3] -- interpolation weights (sum to 1.0)
145
+ return interpolatedData
146
+ })
147
+ ```
148
+
149
+ ### Changes from libtess.js
150
+
151
+ The core algorithm and `gluTess*` method signatures are identical. If you have working libtess.js code, migration is mostly import changes. Here's what's different:
152
+
153
+ **Imports and enums** -- ESM only, shorter enum names:
154
+
155
+ ```javascript
156
+ // libtess.js
157
+ const libtess = require('libtess');
158
+ const tess = new libtess.GluTesselator();
159
+ tess.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX, fn);
160
+
161
+ // libtess-ts
162
+ import { GluTesselator, GLU_TESS } from 'libtess-ts';
163
+ const tess = new GluTesselator();
164
+ tess.gluTessCallback(GLU_TESS.VERTEX, fn);
165
+ ```
166
+
167
+ **New exports:**
168
+
169
+ | Export | Purpose |
170
+ |---|---|
171
+ | `WINDING` | Type-safe winding rule values (`WINDING.ODD`, `WINDING.NONZERO`, etc.) |
172
+ | `GLU_TESS_ERROR` | Error codes passed to the `ERROR` callback (`NEED_COMBINE_CALLBACK`, `COORD_TOO_LARGE`, etc.) |
173
+ | `GL_TRIANGLES`, `GL_LINE_LOOP` | Primitive type constants passed to the `BEGIN` callback |
174
+
175
+ **New capabilities:**
176
+
177
+ - `gluTessVertex` accepts `[x, y]` in addition to `[x, y, z]` (z defaults to 0)
178
+ - `compute()`, `renderTriangles()`, and `renderBoundary()` let you run the sweep once and extract multiple output formats (see [separate sweep and render](#separate-sweep-and-render))
179
+
180
+ **Not supported:**
181
+
182
+ - CommonJS `require()` -- use `import` or a bundler
183
+ - `libtess.meshUtils` -- internal mesh utilities are not exported
184
+
185
+ ## Benchmarks
186
+
187
+ All measurements use paired testing with 200 warmup iterations, 500 samples, and Welch's t-test for significance. All runners accumulate output arrays (vertices + elements) for a fair comparison. Run `node benchmarks/benchmark.mjs` to reproduce
188
+
189
+ ### Cold path (new instance per call)
190
+
191
+ | Workload | libtess.js | libtess-ts | tess2-ts | JS vs TS | JS vs T2 |
192
+ |---|---|---|---|---|---|
193
+ | Glyph, 60v | 24 us | 23 us | 30 us | -5% | +23% *** |
194
+ | Self-intersecting glyph, 224v | 86 us | 78 us | 114 us | -9% *** | +34% *** |
195
+ | Star, 1K vertices | 412 us | 399 us | 625 us | -3% | +52% *** |
196
+ | Star, 7K vertices | 3451 us | 3028 us | 5350 us | -12% *** | +55% *** |
197
+ | poly2tri dude, 104v | 44 us | 38 us | 49 us | -14% *** | +13% *** |
198
+ | OSM building, 22v | 16 us | 10 us | 15 us | -36% *** | -3% |
199
+ | OSM NYC z14, 475v | 341 us | 289 us | 550 us | -15% *** | +61% *** |
200
+ | Dense intersections, 40v | 1070 us | 919 us | 1287 us | -14% *** | +20% *** |
201
+
202
+ ### Warm path (reused instance, libtess.js vs libtess-ts)
203
+
204
+ | Workload | libtess.js | libtess-ts | Diff | Significance |
205
+ |---|---|---|---|---|
206
+ | Glyph, 60v | 28 us | 19 us | -33% | p < 0.0001 |
207
+ | Self-intersecting glyph, 224v | 92 us | 82 us | -10% | p < 0.0001 |
208
+ | Star, 1K vertices | 434 us | 391 us | -10% | p < 0.0001 |
209
+ | Star, 7K vertices | 4651 us | 3960 us | -15% | p < 0.0001 |
210
+ | poly2tri dude, 104v | 44 us | 33 us | -25% | p < 0.0001 |
211
+ | OSM NYC z14, 475v | 218 us | 187 us | -15% | p < 0.0001 |
212
+ | Dense intersections, 40v | 831 us | 706 us | -15% | p < 0.0001 |
213
+
214
+ Negative percentages mean the second library is faster. `***` = p < 0.001
215
+
216
+ ## License
217
+
218
+ [SGI Free Software License B (Version 2.0)](./LICENSE)
219
+
220
+ Some test data is derived from [poly2tri](https://github.com/jhasse/poly2tri) (BSD-3-Clause). See [LICENSE_THIRD_PARTY](./LICENSE_THIRD_PARTY)
221
+
222
+ Maintained by [@jpt](https://github.com/jpt)