Skip to content

Commit 67aa4f1

Browse files
authored
Merge pull request #362 from xyz-tools/share-ring-trig
Share the precomputed sin/cos ring across ExtrusionGeometry instances
2 parents 3d0482b + 69c8d83 commit 67aa4f1

2 files changed

Lines changed: 54 additions & 11 deletions

File tree

src/__tests__/extrusion-geometry.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,35 @@ test('ExtrusionGeometry produces the same ring for every point on the path', ()
127127
expect(secondRing).toEqual(firstRing);
128128
});
129129

130+
test('ExtrusionGeometry produces identical rings across instances', () => {
131+
// The trig table is cached at module level and shared between instances, so
132+
// two geometries with the same radialSegments must produce identical rings.
133+
const radialSegments = 4;
134+
const ring = (radialSegments + 1) * 3;
135+
const points = [new Vector3(0, 0, 0), new Vector3(1, 0, 0)];
136+
const first = new ExtrusionGeometry(points, 0.6, 0.2, radialSegments);
137+
const second = new ExtrusionGeometry(points, 0.6, 0.2, radialSegments);
138+
139+
const firstRing = Array.from(first.attributes.normal.array.slice(0, ring));
140+
const secondRing = Array.from(second.attributes.normal.array.slice(0, ring));
141+
142+
expect(secondRing).toEqual(firstRing);
143+
});
144+
145+
test('ExtrusionGeometry builds a distinct ring per radialSegments value', () => {
146+
// A different radialSegments takes the cache-miss path and yields its own
147+
// table rather than reusing another entry.
148+
const points = [new Vector3(0, 0, 0), new Vector3(1, 0, 0)];
149+
const coarse = new ExtrusionGeometry(points, 0.6, 0.2, 3);
150+
const fine = new ExtrusionGeometry(points, 0.6, 0.2, 5);
151+
152+
expect(coarse.attributes.normal.count).toBe((points.length + 1) * 4);
153+
expect(fine.attributes.normal.count).toBe((points.length + 1) * 6);
154+
expect(Array.from(coarse.attributes.normal.array.slice(0, 12))).not.toEqual(
155+
Array.from(fine.attributes.normal.array.slice(0, 12))
156+
);
157+
});
158+
130159
test('ExtrusionGeometry orients each corner independently', () => {
131160
// The corner vectors are scratch objects shared between points, so stale
132161
// state from one point leaking into the next would show up as a corner

src/extrusion-geometry.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
import { BufferAttribute, BufferGeometry, Vector2, Vector3 } from 'three';
22

3+
// The ring of sines and cosines depends only on radialSegments, and in
4+
// practice every geometry is built with the same value, so the tables are
5+
// cached at module level and shared across all ExtrusionGeometry instances.
6+
// The cached arrays are shared: they must only ever be read, never mutated.
7+
const ringCache = new Map<number, { sin: Float64Array; cos: Float64Array }>();
8+
9+
function ringTrig(radialSegments: number): { sin: Float64Array; cos: Float64Array } {
10+
let ring = ringCache.get(radialSegments);
11+
if (ring) return ring;
12+
13+
const sin = new Float64Array(radialSegments + 1);
14+
const cos = new Float64Array(radialSegments + 1);
15+
for (let j = 0; j <= radialSegments; j++) {
16+
const angle = (j / radialSegments) * Math.PI * 2;
17+
sin[j] = Math.sin(angle);
18+
cos[j] = -Math.cos(angle);
19+
}
20+
21+
ring = { sin, cos };
22+
ringCache.set(radialSegments, ring);
23+
return ring;
24+
}
25+
326
/**
427
* A geometry class for extruding 3D paths into volumetric shapes
528
*/
@@ -73,17 +96,8 @@ class ExtrusionGeometry extends BufferGeometry {
7396
const uvCount = points.length * ringSize;
7497
const indexCount = Math.max(0, points.length - 1) * radialSegments * 6;
7598

76-
// The ring of sines and cosines depends only on j, which runs from 0 to
77-
// radialSegments, so it is the same for every point on the path. Computing
78-
// it once keeps a large model from making a million trig calls to produce a
79-
// handful of distinct values.
80-
const ringSin = new Float64Array(radialSegments + 1);
81-
const ringCos = new Float64Array(radialSegments + 1);
82-
for (let j = 0; j <= radialSegments; j++) {
83-
const angle = (j / radialSegments) * Math.PI * 2;
84-
ringSin[j] = Math.sin(angle);
85-
ringCos[j] = -Math.cos(angle);
86-
}
99+
// Shared, read-only trig tables (see ringCache above); do not mutate.
100+
const { sin: ringSin, cos: ringCos } = ringTrig(radialSegments);
87101

88102
const vertices = new Float32Array(vertexCount * 3);
89103
const normals = new Float32Array(vertexCount * 3);

0 commit comments

Comments
 (0)