vgpu-render 0.1.0 → 0.3.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.
Files changed (46) hide show
  1. package/README.md +101 -34
  2. package/dist/curve.d.ts +26 -1
  3. package/dist/curve.js +53 -0
  4. package/dist/debug-draw-3d.d.ts +3 -3
  5. package/dist/debug-draw-3d.js +10 -10
  6. package/dist/frame-draw-list-2d.d.ts +286 -0
  7. package/dist/frame-draw-list-2d.js +668 -0
  8. package/dist/{frame-recorder.d.ts → frame-draw-list.d.ts} +60 -8
  9. package/dist/{frame-recorder.js → frame-draw-list.js} +51 -10
  10. package/dist/geometry-generators.d.ts +11 -2
  11. package/dist/geometry-generators.js +37 -1
  12. package/dist/gpu-debug-draw-2d.d.ts +1 -1
  13. package/dist/gpu-debug-draw-2d.js +10 -2
  14. package/dist/gpu-debug-draw-3d.d.ts +1 -1
  15. package/dist/gpu-debug-draw-3d.js +4 -2
  16. package/dist/gpu-text-3d.d.ts +1 -1
  17. package/dist/gpu-text-3d.js +49 -2
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +2 -3
  20. package/dist/mesh-batch-3d.d.ts +1 -1
  21. package/dist/shape2d-shader.js +295 -7
  22. package/dist/text-atlas.d.ts +6 -4
  23. package/dist/text-atlas.js +9 -6
  24. package/dist/text-font.d.ts +48 -7
  25. package/dist/text-font.js +63 -11
  26. package/dist/text-layout.d.ts +63 -4
  27. package/dist/text-layout.js +58 -8
  28. package/dist/text-msdf-baked.d.ts +9 -4
  29. package/dist/text-msdf-baked.js +9 -4
  30. package/dist/text-msdf-font.d.ts +10 -4
  31. package/dist/text-msdf-font.js +9 -3
  32. package/dist/vector-math.d.ts +19 -0
  33. package/dist/vector-math.js +37 -0
  34. package/package.json +1 -1
  35. package/dist/frame-recorder-2d.d.ts +0 -177
  36. package/dist/frame-recorder-2d.js +0 -378
  37. package/dist/layout-3d.d.ts +0 -227
  38. package/dist/layout-3d.js +0 -226
  39. package/dist/scene-examples.d.ts +0 -39
  40. package/dist/scene-examples.js +0 -200
  41. package/dist/scenes-2d.d.ts +0 -18
  42. package/dist/scenes-2d.js +0 -160
  43. package/dist/scenes-3d.d.ts +0 -46
  44. package/dist/scenes-3d.js +0 -1994
  45. package/dist/text-scenes.d.ts +0 -85
  46. package/dist/text-scenes.js +0 -338
@@ -1,378 +0,0 @@
1
- /**
2
- * 2D debug-draw commands and their recorder.
3
- *
4
- * The 2D layer is the same data flow as the 3D one — `FrameRecorder -> FrameCommand[] -> GPU` —
5
- * reached through `frame.d2` instead of a second entry point, so a frame has one lifecycle, one
6
- * layer mask and one set of defaults. The commands are separate types rather than 3D commands
7
- * with a `space` flag because a 2D point is a `Vec2` and 2D strokes carry no surface normal;
8
- * keeping them apart stops a 3D-only field from silently meaning something different here.
9
- *
10
- * See `camera-2d.ts` for what `world2d` and `screen` mean.
11
- */
12
- const DEFAULT_2D_COLOR = [1, 1, 1, 1];
13
- const DEFAULT_2D_LAYER = 1;
14
- /** An unfilled shape still has to be visible, so a stroke defaults to one pixel. */
15
- const DEFAULT_2D_STROKE_WIDTH = 1;
16
- const TAU = Math.PI * 2;
17
- function finite(value, name) {
18
- if (!Number.isFinite(value)) {
19
- throw new RangeError(`${name} must be finite`);
20
- }
21
- return value;
22
- }
23
- function positive(value, name) {
24
- const resolved = finite(value, name);
25
- if (resolved <= 0)
26
- throw new RangeError(`${name} must be greater than zero`);
27
- return resolved;
28
- }
29
- function nonNegative(value, name) {
30
- const resolved = finite(value, name);
31
- if (resolved < 0)
32
- throw new RangeError(`${name} must not be negative`);
33
- return resolved;
34
- }
35
- function positiveSize(value, name) {
36
- const resolved = vec2(value, name);
37
- if (resolved[0] <= 0 || resolved[1] <= 0)
38
- throw new RangeError(`${name} values must be greater than zero`);
39
- return resolved;
40
- }
41
- /** Start angle and sweep of the angle-parameterised shapes, with the same defaults and bounds. */
42
- function angleRange(options) {
43
- const startAngle = finite(options.startAngle ?? 0, "startAngle");
44
- const sweep = finite(options.sweep ?? TAU, "sweep");
45
- if (sweep === 0 || Math.abs(sweep) > TAU) {
46
- throw new RangeError("sweep must be a non-zero angle of at most one full turn");
47
- }
48
- return { startAngle, sweep };
49
- }
50
- function vec2(value, name) {
51
- if (value.length !== 2) {
52
- throw new RangeError(`${name} must contain two values`);
53
- }
54
- return [finite(value[0], `${name}[0]`), finite(value[1], `${name}[1]`)];
55
- }
56
- function colorValue(value, name) {
57
- if (value.length !== 4) {
58
- throw new RangeError(`${name} must contain four values`);
59
- }
60
- return [
61
- finite(value[0], `${name}[0]`),
62
- finite(value[1], `${name}[1]`),
63
- finite(value[2], `${name}[2]`),
64
- finite(value[3], `${name}[3]`),
65
- ];
66
- }
67
- function color(value) {
68
- return value === undefined ? DEFAULT_2D_COLOR : colorValue(value, "color");
69
- }
70
- function layer(value) {
71
- const resolved = value ?? DEFAULT_2D_LAYER;
72
- if (!Number.isInteger(resolved) || resolved < 0) {
73
- throw new RangeError("layer must be a non-negative integer bitmask");
74
- }
75
- return resolved;
76
- }
77
- function sizeModeValue(value, name) {
78
- if (value !== "screen" && value !== "world") {
79
- throw new RangeError(`${name} must be "screen" or "world"`);
80
- }
81
- return value;
82
- }
83
- function jointValue(value, name) {
84
- if (value !== "none" && value !== "round" && value !== "bevel" && value !== "miter") {
85
- throw new RangeError(`${name} must be "none", "round", "bevel", or "miter"`);
86
- }
87
- return value;
88
- }
89
- /** Resolves the space, defaulting to `world2d` so a command only pays for a camera when it wants one. */
90
- function spaceValue(value) {
91
- if (value === undefined)
92
- return "world2d";
93
- if (value !== "world2d" && value !== "screen") {
94
- throw new RangeError('space must be "world2d" or "screen"');
95
- }
96
- return value;
97
- }
98
- /**
99
- * Records 2D primitives for one frame. Obtained through `FrameRecorder.d2`; it seals with its
100
- * parent, so a caller cannot keep drawing into a finished frame.
101
- */
102
- export class FrameRecorder2D {
103
- #points = [];
104
- #lines = [];
105
- #joints = [];
106
- #shapes = [];
107
- #defaults;
108
- #assertOpen;
109
- constructor(defaults, assertOpen) {
110
- this.#defaults = defaults;
111
- this.#assertOpen = assertOpen;
112
- }
113
- point(position, options = {}) {
114
- this.#assertOpen();
115
- const size = finite(options.size ?? this.#defaults.point.size ?? 1, "size");
116
- if (size <= 0)
117
- throw new RangeError("size must be greater than zero");
118
- this.#points.push({
119
- kind: "point2d",
120
- position: vec2(position, "position"),
121
- color: color(options.color ?? this.#defaults.point.color),
122
- size,
123
- sizeMode: options.sizeMode ?? this.#defaults.point.sizeMode ?? "screen",
124
- space: spaceValue(options.space),
125
- layer: layer(options.layer ?? this.#defaults.point.layer),
126
- ...(options.round === undefined ? {} : { round: options.round }),
127
- });
128
- return this;
129
- }
130
- line(start, end, options = {}) {
131
- this.#assertOpen();
132
- const resolvedStart = vec2(start, "start");
133
- const resolvedEnd = vec2(end, "end");
134
- if (resolvedStart[0] === resolvedEnd[0] && resolvedStart[1] === resolvedEnd[1]) {
135
- return this;
136
- }
137
- const width = finite(options.width ?? this.#defaults.line.width ?? 1, "width");
138
- if (width <= 0)
139
- throw new RangeError("width must be greater than zero");
140
- const dash = options.dash ?? this.#defaults.line.dash;
141
- const joint = options.joint ?? this.#defaults.line.joint;
142
- this.#lines.push({
143
- kind: "line2d",
144
- start: resolvedStart,
145
- end: resolvedEnd,
146
- color: color(options.color ?? this.#defaults.line.color),
147
- colorEnd: color(options.colorEnd ?? options.color ?? this.#defaults.line.colorEnd ?? this.#defaults.line.color),
148
- width,
149
- widthMode: options.widthMode ?? this.#defaults.line.widthMode ?? "screen",
150
- space: spaceValue(options.space),
151
- layer: layer(options.layer ?? this.#defaults.line.layer),
152
- ...(dash === undefined ? {} : { dash }),
153
- ...(joint === undefined ? {} : { joint }),
154
- });
155
- return this;
156
- }
157
- ray(origin, direction, options = {}) {
158
- return this.line(origin, [origin[0] + direction[0], origin[1] + direction[1]], options);
159
- }
160
- /**
161
- * Path of segments, the 2D twin of `FrameRecorder.lineStrip`. Dash distance accumulates over
162
- * the whole path and an open strip joins only its interior vertices, matching the 3D layer.
163
- */
164
- lineStrip(points, options = {}, closed = false) {
165
- this.#assertOpen();
166
- const dash = options.dash ?? this.#defaults.line.dash;
167
- const vertexDistances = [0];
168
- let traveled = 0;
169
- const segment = (a, b) => {
170
- this.line(a, b, {
171
- ...options,
172
- ...(dash === undefined ? {} : { dash: [dash[0], dash[1], dash[2] + traveled] }),
173
- });
174
- traveled += Math.hypot(b[0] - a[0], b[1] - a[1]);
175
- vertexDistances.push(traveled);
176
- };
177
- for (let i = 0; i + 1 < points.length; i++)
178
- segment(points[i], points[i + 1]);
179
- if (closed && points.length > 1)
180
- segment(points[points.length - 1], points[0]);
181
- const joint = options.joint ?? this.#defaults.line.joint;
182
- if (joint === "round" || joint === "bevel" || joint === "miter") {
183
- const count = points.length;
184
- const first = closed ? 0 : 1;
185
- const last = closed ? (count > 2 ? count : 0) : count - 1;
186
- for (let i = first; i < last; i++) {
187
- this.#jointCommand(points[(i - 1 + count) % count], points[i], points[(i + 1) % count], joint, options, dash, vertexDistances[i]);
188
- }
189
- }
190
- return this;
191
- }
192
- polyline(points, options = {}, closed = false) {
193
- return this.lineStrip(points, options, closed);
194
- }
195
- /**
196
- * Axis-aligned grid of lines, the 2D twin of `FrameRecorder.grid` and reference `grid_2d`: `cells`
197
- * are the cells per axis, so the drawn line count is one more on each side.
198
- */
199
- grid(center, cells, spacing, options = {}) {
200
- this.#assertOpen();
201
- const [columns, rows] = cells;
202
- if (!Number.isInteger(columns) || !Number.isInteger(rows) || columns < 1 || rows < 1) {
203
- throw new RangeError("grid cells must be positive integers");
204
- }
205
- const [stepX, stepY] = positiveSize(spacing, "spacing");
206
- const [centerX, centerY] = vec2(center, "center");
207
- const halfWidth = (columns * stepX) / 2;
208
- const halfHeight = (rows * stepY) / 2;
209
- for (let column = 0; column <= columns; column++) {
210
- const x = centerX - halfWidth + column * stepX;
211
- this.line([x, centerY - halfHeight], [x, centerY + halfHeight], options);
212
- }
213
- for (let row = 0; row <= rows; row++) {
214
- const y = centerY - halfHeight + row * stepY;
215
- this.line([centerX - halfWidth, y], [centerX + halfWidth, y], options);
216
- }
217
- return this;
218
- }
219
- /** Rectangle with the given full width and height (reference `Rectangle::new`). */
220
- rect(center, size, options = {}) {
221
- const full = positiveSize(size, "size");
222
- this.#pushShape("rect", center, { size: [full[0] / 2, full[1] / 2] }, options);
223
- return this;
224
- }
225
- /** Rectangle with rounded corners; the shader clamps the radius to half the shorter side. */
226
- roundedRect(center, size, cornerRadius, options = {}) {
227
- const full = positiveSize(size, "size");
228
- this.#pushShape("rounded-rect", center, { size: [full[0] / 2, full[1] / 2], cornerRadius: nonNegative(cornerRadius, "cornerRadius") }, options);
229
- return this;
230
- }
231
- /** Filled disc with the given radius (reference `Circle::new`). */
232
- circle(center, radius, options = {}) {
233
- const r = positive(radius, "radius");
234
- this.#pushShape("circle", center, { size: [r, r] }, options);
235
- return this;
236
- }
237
- /** Ellipse with independent x and y radii (reference `Ellipse::new(x_radius, y_radius)`). */
238
- ellipse(center, radii, options = {}) {
239
- const r = positiveSize(radii, "radii");
240
- this.#pushShape("ellipse", center, { size: r }, options);
241
- return this;
242
- }
243
- /**
244
- * Circular arc from `startAngle` through `sweep`. It is a curve without an interior, so an
245
- * unfilled arc (the default) strokes it with `strokeWidth` while `fill: true` leaves a hairline.
246
- */
247
- arc(center, radius, options = {}) {
248
- const r = positive(radius, "radius");
249
- const angles = angleRange(options);
250
- this.#pushShape("arc", center, { size: [r, r], ...angles }, options);
251
- return this;
252
- }
253
- /** Ring band between two radii (reference `Annulus::new(inner_radius, outer_radius)`). */
254
- annulus(center, innerRadius, outerRadius, options = {}) {
255
- const inner = nonNegative(innerRadius, "innerRadius");
256
- const outer = positive(outerRadius, "outerRadius");
257
- if (inner >= outer)
258
- throw new RangeError("innerRadius must be smaller than outerRadius");
259
- this.#pushShape("annulus", center, { size: [outer, inner] }, options);
260
- return this;
261
- }
262
- /** Capsule whose straight section is `length` long along y (reference `Capsule2d::new(radius, length)`). */
263
- capsule(center, radius, length, options = {}) {
264
- const r = positive(radius, "radius");
265
- const straight = nonNegative(length, "length");
266
- this.#pushShape("capsule", center, { size: [r, straight / 2] }, options);
267
- return this;
268
- }
269
- /** Regular polygon with a vertex on the +x axis (reference `RegularPolygon::new(circumradius, sides)`). */
270
- polygon(center, circumradius, sides, options = {}) {
271
- const r = positive(circumradius, "circumradius");
272
- if (!Number.isInteger(sides) || sides < 3)
273
- throw new RangeError("sides must be an integer of at least 3");
274
- this.#pushShape("polygon", center, { size: [r, r], sides }, options);
275
- return this;
276
- }
277
- /** Arbitrary triangle (reference `Triangle2d::new(a, b, c)`); `rotation` turns about its centroid. */
278
- triangle(a, b, c, options = {}) {
279
- const p0 = vec2(a, "a");
280
- const p1 = vec2(b, "b");
281
- const p2 = vec2(c, "c");
282
- const center = [(p0[0] + p1[0] + p2[0]) / 3, (p0[1] + p1[1] + p2[1]) / 3];
283
- this.#pushShape("triangle", center, {
284
- size: [p0[0] - center[0], p0[1] - center[1]],
285
- vertexB: [p1[0] - center[0], p1[1] - center[1]],
286
- vertexC: [p2[0] - center[0], p2[1] - center[1]],
287
- }, options);
288
- return this;
289
- }
290
- /** Diamond with the given full diagonals (reference `Rhombus::new(horizontal, vertical)`). */
291
- rhombus(center, diagonals, options = {}) {
292
- const d = positiveSize(diagonals, "diagonals");
293
- this.#pushShape("rhombus", center, { size: [d[0] / 2, d[1] / 2] }, options);
294
- return this;
295
- }
296
- /** Disc sector from `startAngle` through `sweep` (reference `CircularSector`). */
297
- sector(center, radius, options = {}) {
298
- const r = positive(radius, "radius");
299
- const angles = angleRange(options);
300
- this.#pushShape("sector", center, { size: [r, r], ...angles }, options);
301
- return this;
302
- }
303
- /** Disc region between a chord and its arc (reference `CircularSegment`). */
304
- segment(center, radius, options = {}) {
305
- const r = positive(radius, "radius");
306
- const angles = angleRange(options);
307
- this.#pushShape("segment", center, { size: [r, r], ...angles }, options);
308
- return this;
309
- }
310
- /**
311
- * One joint instance for a path vertex. Degenerate wedges whose centre coincides with a
312
- * neighbour are dropped, as in the 3D layer and the reference `emit_joint`.
313
- */
314
- #jointCommand(previous, center, next, joint, options, dash, distance) {
315
- const resolvedPrevious = vec2(previous, "previous");
316
- const resolvedCenter = vec2(center, "center");
317
- const resolvedNext = vec2(next, "next");
318
- const same = (a, b) => a[0] === b[0] && a[1] === b[1];
319
- if (same(resolvedPrevious, resolvedCenter) || same(resolvedCenter, resolvedNext))
320
- return;
321
- const width = finite(options.width ?? this.#defaults.line.width ?? 1, "width");
322
- if (width <= 0)
323
- throw new RangeError("width must be greater than zero");
324
- jointValue(joint, "joint");
325
- this.#joints.push({
326
- kind: "joint2d",
327
- previous: resolvedPrevious,
328
- center: resolvedCenter,
329
- next: resolvedNext,
330
- color: color(options.color ?? this.#defaults.line.color),
331
- width,
332
- widthMode: options.widthMode ?? this.#defaults.line.widthMode ?? "screen",
333
- joint,
334
- space: spaceValue(options.space),
335
- layer: layer(options.layer ?? this.#defaults.line.layer),
336
- ...(dash === undefined ? {} : { dash: [dash[0], dash[1], dash[2] + distance] }),
337
- });
338
- }
339
- /** Records one shape once its per-shape arguments have been validated. */
340
- #pushShape(shape, center, fields, options) {
341
- this.#assertOpen();
342
- const thickness = positive(options.strokeWidth ?? DEFAULT_2D_STROKE_WIDTH, "strokeWidth");
343
- const rotation = finite(options.rotation ?? 0, "rotation");
344
- const cornerRadius = fields.cornerRadius ?? 0;
345
- if (cornerRadius < 0)
346
- throw new RangeError("cornerRadius must not be negative");
347
- this.#shapes.push({
348
- kind: "shape2d",
349
- shape,
350
- center: vec2(center, "center"),
351
- size: fields.size ?? [0, 0],
352
- vertexB: fields.vertexB ?? [0, 0],
353
- vertexC: fields.vertexC ?? [0, 0],
354
- cornerRadius,
355
- thickness,
356
- filled: options.fill ?? true,
357
- rotation,
358
- startAngle: fields.startAngle ?? 0,
359
- sweep: fields.sweep ?? 0,
360
- sides: fields.sides ?? 0,
361
- color: color(options.color),
362
- space: spaceValue(options.space),
363
- layer: layer(options.layer),
364
- });
365
- }
366
- get points() {
367
- return this.#points;
368
- }
369
- get lines() {
370
- return this.#lines;
371
- }
372
- get joints() {
373
- return this.#joints;
374
- }
375
- get shapes() {
376
- return this.#shapes;
377
- }
378
- }
@@ -1,227 +0,0 @@
1
- /**
2
- * Layout and drawing specifications ported from `the upstream reference`.
3
- *
4
- * Every reference scene shares the same layout contract, which is why it lives here rather
5
- * than being re-derived per scene:
6
- *
7
- * ```
8
- * column_x(column) = column * COLUMN_SPACING + 0.5 // COLUMN_SPACING is 2.0, axes 2.2
9
- * row_z(row) = SAMPLE_START_Z - row * spacing // START_Z is -0.45, rows go along -Z
10
- * ```
11
- *
12
- * Each scene has its own `SAMPLE_SPACING`, and some columns override it (radius, size,
13
- * angle, height). Coordinates are local to the scene root; the reference capture harness
14
- * translates the whole root by `REFERENCE_OFFSET` without rescaling any geometry, so the
15
- * same translation is applied here.
16
- */
17
- import type { Color, Vec3 } from "./frame-recorder.js";
18
- /** `scene.position = Vector3(-10, 0, 1.5)` from `the reference capture script`. */
19
- export declare const REFERENCE_OFFSET: Vec3;
20
- export declare function referenceOffset(p: Vec3): Vec3;
21
- /** Shared value tables used by the reference scenes. */
22
- export declare const WIDTH_VALUES: readonly [1, 2, 5.5, 10];
23
- export declare const ALPHA_VALUES: readonly [1, 0.65, 0.35, 0.1];
24
- export declare const DASH_VALUES: readonly (readonly [number, number])[];
25
- export declare const JOINT_VALUES: readonly ["none", "round", "bevel", "miter"];
26
- export declare const RADIUS_VALUES: readonly [0.18, 0.28, 0.38, 0.5];
27
- export declare const SEGMENT_VALUES: readonly [8, 16, 32, 64];
28
- export declare const ROTATION_SAMPLE_COUNT = 8;
29
- export declare const NORMAL_SAMPLE_COUNT = 4;
30
- export declare const WHITE: Color;
31
- export declare const CYAN: Color;
32
- export declare const LIME: Color;
33
- export declare const ORANGE: Color;
34
- export declare const ORANGE_SOFT: Color;
35
- export declare const VIOLET: Color;
36
- export declare const TEAL: Color;
37
- export declare const PINK: Color;
38
- export declare const GOLD: Color;
39
- /** Reference `_get_normal_sample_color` / `_get_normal_sample_width`, shared by most scenes. */
40
- export declare function normalSampleColor(row: number): Color;
41
- export declare function normalSampleWidth(row: number, baseWidth: number): number;
42
- /**
43
- * reference `Color.from_hsv(h, s, v, a)`. The reference solid samples use this, so a fixed
44
- * palette color cannot reproduce them. `h` is in `[0, 1]`, matching reference.
45
- */
46
- export declare function fromHsv(h: number, s: number, v: number, a: number): Color;
47
- export declare function normalizeVector(p: Vec3): Vec3;
48
- export declare function crossVector(a: Vec3, b: Vec3): Vec3;
49
- /** reference `Vector3.rotated(axis, angle)` equivalent (right-handed Rodrigues). */
50
- export declare function rotateAroundAxis(p: Vec3, axis: Vec3, angle: number): Vec3;
51
- export type CircleBasis = {
52
- readonly x: Vec3;
53
- readonly y: Vec3;
54
- readonly z: Vec3;
55
- };
56
- /** Port of `_get_circle_rotation(normal, angle)` from `circle.gd`. */
57
- export declare function circleBasis(normal: Vec3, angle: number): CircleBasis;
58
- /** Points on a circle in the plane spanned by a reference rotation basis. */
59
- export declare function basisCirclePoints(centerLocal: Vec3, radius: number, segments: number, basis: CircleBasis): Vec3[];
60
- /**
61
- * Column/row placement shared by every reference scene.
62
- *
63
- * The reference files re-implement the same three lines per scene, differing only in the
64
- * spacing constants. Modelling that here keeps each scene a small table of numbers instead
65
- * of another copy of the coordinate math:
66
- *
67
- * - `columnX(column) = column * columnSpacing + columnOffset`
68
- * - `rowZ(row) = sampleStartZ - row * rowSpacing` (rows advance along **-Z**)
69
- * - a per-column override covers the scenes whose radius/size/angle/height columns use a
70
- * wider spacing than the rest (for example `circle` radius 1.15 vs 0.82).
71
- */
72
- export type SceneLayout = {
73
- /** Distance between adjacent columns; `2.0` for every scene except `axes` (2.2). */
74
- readonly columnSpacing: number;
75
- /** Added to `column * columnSpacing`; `0.5` for every ported scene. */
76
- readonly columnOffset: number;
77
- readonly sampleSpacing: number;
78
- readonly sampleStartZ: number;
79
- /** Row spacing for specific columns, overriding `sampleSpacing`. */
80
- readonly perColumnSpacing?: Readonly<Record<number, number>>;
81
- /** Column-specific Y offset, used by scenes that lift a tunnel or grow by radius. */
82
- readonly columnY?: (column: number, row: number, baseY: number) => number;
83
- };
84
- export declare function createLayout(layout: SceneLayout, baseY: number): {
85
- columnX: (column: number) => number;
86
- rowZ: (column: number, row: number) => number;
87
- rowSpacing: (column: number) => number;
88
- at: (column: number, row: number) => Vec3;
89
- atLocal: (column: number, row: number) => Vec3;
90
- };
91
- export type ScenePositions = ReturnType<typeof createLayout>;
92
- /** Reference `circle.gd` layout: radius column uses 1.15 row spacing, the rest 0.82. */
93
- export declare const CIRCLE_REFERENCE_LAYOUT: SceneLayout;
94
- /**
95
- * Per-scene layouts, copied from the constants at the top of each `examples/*.gd`.
96
- *
97
- * Every reference scene differs only in these numbers, so they are data here rather than
98
- * duplicated coordinate math. `columnSpacing` is 2.0 everywhere except `axes` (2.2).
99
- */
100
- export declare const SCENE_LAYOUTS: {
101
- /** `index.gd`: absolute origin, its own row spacing, samples lifted to y = 0.55. */
102
- readonly index: {
103
- readonly columnSpacing: 2;
104
- readonly columnOffset: 1;
105
- readonly sampleSpacing: 1.25;
106
- readonly sampleStartZ: -1;
107
- readonly baseY: 0.55;
108
- };
109
- readonly arrow: {
110
- readonly columnSpacing: 2;
111
- readonly columnOffset: 0.5;
112
- readonly sampleSpacing: 0.78;
113
- readonly sampleStartZ: -0.45;
114
- readonly baseY: 0.1;
115
- };
116
- readonly axes: {
117
- readonly columnSpacing: 2.2;
118
- readonly columnOffset: 0.5;
119
- readonly sampleSpacing: 0.86;
120
- readonly sampleStartZ: -0.45;
121
- readonly baseY: 0.1;
122
- readonly perColumnSpacing: {
123
- readonly 0: 1.05;
124
- readonly 3: 1.05;
125
- };
126
- };
127
- readonly grid: {
128
- readonly columnSpacing: 2;
129
- readonly columnOffset: 0.5;
130
- readonly sampleSpacing: 0.72;
131
- readonly sampleStartZ: -0.45;
132
- readonly baseY: 0.06;
133
- };
134
- readonly rect: {
135
- readonly columnSpacing: 2;
136
- readonly columnOffset: 0.5;
137
- readonly sampleSpacing: 0.62;
138
- readonly sampleStartZ: -0.45;
139
- readonly baseY: 0.1;
140
- readonly perColumnSpacing: {
141
- readonly 1: 0.9;
142
- };
143
- };
144
- readonly box: {
145
- readonly columnSpacing: 2;
146
- readonly columnOffset: 0.5;
147
- readonly sampleSpacing: 0.82;
148
- readonly sampleStartZ: -0.45;
149
- readonly baseY: 0.08;
150
- readonly perColumnSpacing: {
151
- readonly 1: 1.05;
152
- };
153
- };
154
- readonly arc: {
155
- readonly columnSpacing: 2;
156
- readonly columnOffset: 0.5;
157
- readonly sampleSpacing: 0.92;
158
- readonly sampleStartZ: -0.45;
159
- readonly baseY: 0.1;
160
- readonly perColumnSpacing: {
161
- readonly 1: 1.15;
162
- readonly 2: 1.08;
163
- };
164
- };
165
- readonly curve: {
166
- readonly columnSpacing: 2;
167
- readonly columnOffset: 0.5;
168
- readonly sampleSpacing: 0.82;
169
- readonly sampleStartZ: -2;
170
- readonly baseY: 0.1;
171
- readonly perColumnSpacing: {
172
- readonly 2: number;
173
- readonly 4: number;
174
- };
175
- };
176
- readonly sphere: {
177
- readonly columnSpacing: 2;
178
- readonly columnOffset: 0.5;
179
- readonly sampleSpacing: 0.86;
180
- readonly sampleStartZ: -0.45;
181
- readonly baseY: 0.1;
182
- readonly perColumnSpacing: {
183
- readonly 1: 1.08;
184
- };
185
- };
186
- readonly cone: {
187
- readonly columnSpacing: 2;
188
- readonly columnOffset: 0.5;
189
- readonly sampleSpacing: 0.84;
190
- readonly sampleStartZ: -0.45;
191
- readonly baseY: 0.1;
192
- readonly perColumnSpacing: {
193
- readonly 1: 1.05;
194
- readonly 2: 1.08;
195
- };
196
- };
197
- readonly cylinder: {
198
- readonly columnSpacing: 2;
199
- readonly columnOffset: 0.5;
200
- readonly sampleSpacing: 0.84;
201
- readonly sampleStartZ: -0.45;
202
- readonly baseY: 0.1;
203
- readonly perColumnSpacing: {
204
- readonly 1: 1.05;
205
- readonly 2: 1.08;
206
- };
207
- };
208
- readonly capsule: {
209
- readonly columnSpacing: 2;
210
- readonly columnOffset: 0.5;
211
- readonly sampleSpacing: 0.86;
212
- readonly sampleStartZ: -0.45;
213
- readonly baseY: 0.1;
214
- readonly perColumnSpacing: {
215
- readonly 1: 1.02;
216
- readonly 2: 1.02;
217
- };
218
- };
219
- readonly world_space_width: {
220
- readonly columnSpacing: 2;
221
- readonly columnOffset: 1;
222
- readonly sampleSpacing: 1.25;
223
- readonly sampleStartZ: -1;
224
- readonly baseY: 0.55;
225
- };
226
- };
227
- export type SceneLayoutId = keyof typeof SCENE_LAYOUTS;