Skip to content

Commit d04cc56

Browse files
committed
Triangles
1 parent a24de56 commit d04cc56

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ download menu, and the rAF render loop inside `mount`. Returns a controller:
101101
- `bar(key, a, b, radius, color, alpha)` — oriented cylinder a→b.
102102
- `sphere(key, pos, radius, color, alpha)` — a sphere.
103103
- `quad(key, verts, color, alpha)` — a filled quad (`verts`: 4 positions).
104+
- `triangles(key, tris, color, alpha)` — a filled triangle list (soup); `tris` is an array of triangles, each `[a, b, c]` of positions. Non-indexed geometry, DoubleSide; `color` is a `ColorArg` (static or per-frame `(f) => Color`), `alpha` per-frame; sig-rebuilds when any vertex moves. Use instead of `draw` + a hand-built tri mesh.
104105
- `label(key, pos, text, opts?)` — a 3D-anchored, screen-fixed text label with a rounded-rect backdrop pill. Sits on a 3D point (`pos`, resolved each frame so it follows the anchor) and stays a CONSTANT on-screen size by compensating the Sprite scale for the OrbitCam dolly (`baseSize / camera.zoom`). `opts`: `{ color?, backdrop?=true, backdropColor?, size?=0.14, alpha? }`.
105106
- `draw(key, object: THREE.Object3D, alpha)`**custom primitive**: you supply any
106107
THREE object (Group/Mesh/LineSegments you built); the library retains it by key

src/core.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ export function createFigure(
239239
g.setIndex([0, 1, 2, 0, 2, 3]);
240240
return g;
241241
};
242+
const trisGeo = (tris: THREE.Vector3[][]): THREE.BufferGeometry => {
243+
const g = new THREE.BufferGeometry();
244+
const arr = new Float32Array(tris.length * 9);
245+
tris.forEach((t, i) => {
246+
for (let j = 0; j < 3; j++) {
247+
const p = t[j], k = (i * 3 + j) * 3;
248+
arr[k] = p.x; arr[k + 1] = p.y; arr[k + 2] = p.z;
249+
}
250+
});
251+
g.setAttribute("position", new THREE.BufferAttribute(arr, 3));
252+
return g;
253+
};
242254

243255
// ctx BUFFERS during draw(): nodes and draw calls are recorded and only
244256
// reconciled AFTER draw returns, because positions may be node keys
@@ -254,6 +266,7 @@ export function createFigure(
254266
| { kind: "line"; key: string; a: FigPos; b: FigPos; color: ColorArg; alpha: number }
255267
| { kind: "bar"; key: string; a: FigPos; b: FigPos; radius: number; color: ColorArg; alpha: number }
256268
| { kind: "quad"; key: string; verts: FigPos[]; color: ColorArg; alpha: number }
269+
| { kind: "triangles"; key: string; tris: FigPos[][]; color: ColorArg; alpha: number }
257270
| { kind: "draw"; key: string; factory: () => THREE.Object3D; pos: FigPos; alpha: number; colorFn?: (f: Frame) => THREE.Color }
258271
| { kind: "label"; key: string; pos: FigPos; text: string; color: THREE.Color; backdrop: boolean; backdropColor: THREE.Color; size: number; alpha: number };
259272
const nodePlaces = new Map<string, NodePlace>();
@@ -385,6 +398,28 @@ export function createFigure(
385398
const m = e.mat!;
386399
m.color.copy(col); m.opacity = clamp01(dc.alpha); m.transparent = true;
387400
e.obj.visible = dc.alpha > 0.001;
401+
} else if (dc.kind === "triangles") {
402+
const tris = dc.tris.map((t) => t.map(resolveFigPos));
403+
if (tris.some((t) => t.some((v) => !v))) return;
404+
const flat = tris as THREE.Vector3[][];
405+
drawnThisFrame.add(dc.key);
406+
const col = evalColor(dc.color, f);
407+
const s = sig(flat.flat());
408+
let e = retained.get(dc.key);
409+
if (!e) {
410+
const mat = new THREE.MeshBasicMaterial({ color: col.clone(), transparent: true, opacity: 0, side: THREE.DoubleSide });
411+
const obj = new THREE.Mesh(trisGeo(flat), mat);
412+
scene.add(obj);
413+
e = { obj, mat, kind: "triangles", ownsGeo: true, sig: s };
414+
retained.set(dc.key, e);
415+
} else if (e.sig !== s) {
416+
(e.obj as THREE.Mesh).geometry.dispose();
417+
(e.obj as THREE.Mesh).geometry = trisGeo(flat);
418+
e.sig = s;
419+
}
420+
const m = e.mat!;
421+
m.color.copy(col); m.opacity = clamp01(dc.alpha); m.transparent = true;
422+
e.obj.visible = dc.alpha > 0.001;
388423
} else if (dc.kind === "label") {
389424
// 3D-anchored, screen-fixed text label: position is the resolved 3D
390425
// anchor (follows the corner); apparent on-screen SIZE is held
@@ -451,6 +486,7 @@ export function createFigure(
451486
line(key, a, b, color, alpha) { drawCalls.push({ kind: "line", key: full(key), a, b, color, alpha }); },
452487
bar(key, a, b, radius, color, alpha) { drawCalls.push({ kind: "bar", key: full(key), a, b, radius, color, alpha }); },
453488
quad(key, verts, color, alpha) { drawCalls.push({ kind: "quad", key: full(key), verts, color, alpha }); },
489+
triangles(key, tris, color, alpha) { drawCalls.push({ kind: "triangles", key: full(key), tris, color, alpha }); },
454490
draw(key, factory, pos, alpha, colorFn) { drawCalls.push({ kind: "draw", key: full(key), factory, pos, alpha, colorFn }); },
455491
label(key, pos, text, opts) { drawCalls.push({ kind: "label", key: full(key), pos, text, color: opts?.color ?? LABEL_DEFAULT_COLOR, backdrop: opts?.backdrop ?? true, backdropColor: opts?.backdropColor ?? LABEL_DEFAULT_BACKDROP, size: opts?.size ?? LABEL_DEFAULT_SIZE, alpha: opts?.alpha ?? 1 }); },
456492
scope(prefix, fn) { stack.push(prefix); fn(); stack.pop(); },

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ export type FigCtx = {
5555
line(key: string, a: FigPos, b: FigPos, color: ColorArg, alpha: number): void;
5656
bar(key: string, a: FigPos, b: FigPos, radius: number, color: ColorArg, alpha: number): void;
5757
quad(key: string, verts: FigPos[], color: ColorArg, alpha: number): void;
58+
/** Filled triangle list (a triangle soup): each triangle is 3 {@link FigPos}
59+
* (a vector or a node key). One non-indexed BufferGeometry (3 verts/tri, no
60+
* vertex sharing), MeshBasicMaterial DoubleSide transparent; `color` is a
61+
* {@link ColorArg} (static or per-frame `(f) => Color`), `alpha` is per-frame.
62+
* The geometry sig-rebuilds when any vertex moves (like {@link quad}); the
63+
* library disposes the geometry on sig-change/drop. Use this instead of
64+
* `draw` + a hand-built tri mesh for filled triangle lists. */
65+
triangles(key: string, tris: FigPos[][], color: ColorArg, alpha: number): void;
5866
/** Custom primitive: the library calls `factory()` once on first draw to build
5967
* a THREE.Object3D, retains it by `key`, and each frame resolves `pos` (a vector
6068
* or a node key), sets `object.position`, `object.visible = alpha > 0.001`, and

0 commit comments

Comments
 (0)