vgpu-render 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +555 -2
- package/dist/assets/fira-mono-medium-subset.d.ts +4 -0
- package/dist/assets/fira-mono-medium-subset.js +889 -0
- package/dist/assets/fira-mono-msdf-atlas.d.ts +13 -0
- package/dist/assets/fira-mono-msdf-atlas.js +6222 -0
- package/dist/camera-2d.d.ts +43 -0
- package/dist/camera-2d.js +75 -0
- package/dist/color-space.d.ts +32 -0
- package/dist/color-space.js +45 -0
- package/dist/curve.d.ts +16 -0
- package/dist/curve.js +46 -0
- package/dist/debug-draw-3d.d.ts +14 -0
- package/dist/debug-draw-3d.js +36 -0
- package/dist/frame-recorder-2d.d.ts +177 -0
- package/dist/frame-recorder-2d.js +378 -0
- package/dist/frame-recorder.d.ts +361 -0
- package/dist/frame-recorder.js +515 -0
- package/dist/geometry-generators.d.ts +101 -0
- package/dist/geometry-generators.js +481 -0
- package/dist/gpu-debug-draw-2d.d.ts +48 -0
- package/dist/gpu-debug-draw-2d.js +450 -0
- package/dist/gpu-debug-draw-3d.d.ts +52 -0
- package/dist/gpu-debug-draw-3d.js +890 -0
- package/dist/gpu-instancing.d.ts +34 -0
- package/dist/gpu-instancing.js +93 -0
- package/dist/gpu-text-3d.d.ts +98 -0
- package/dist/gpu-text-3d.js +467 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/infinite-grid-2d.d.ts +73 -0
- package/dist/infinite-grid-2d.js +253 -0
- package/dist/infinite-grid-3d.d.ts +134 -0
- package/dist/infinite-grid-3d.js +407 -0
- package/dist/layout-3d.d.ts +227 -0
- package/dist/layout-3d.js +226 -0
- package/dist/mesh-batch-3d.d.ts +10 -0
- package/dist/mesh-batch-3d.js +67 -0
- package/dist/scene-examples.d.ts +39 -0
- package/dist/scene-examples.js +200 -0
- package/dist/scenes-2d.d.ts +18 -0
- package/dist/scenes-2d.js +160 -0
- package/dist/scenes-3d.d.ts +46 -0
- package/dist/scenes-3d.js +1994 -0
- package/dist/shape2d-shader.d.ts +14 -0
- package/dist/shape2d-shader.js +287 -0
- package/dist/text-atlas.d.ts +174 -0
- package/dist/text-atlas.js +495 -0
- package/dist/text-font.d.ts +149 -0
- package/dist/text-font.js +209 -0
- package/dist/text-layout.d.ts +116 -0
- package/dist/text-layout.js +340 -0
- package/dist/text-msdf-baked.d.ts +36 -0
- package/dist/text-msdf-baked.js +112 -0
- package/dist/text-msdf-font.d.ts +79 -0
- package/dist/text-msdf-font.js +153 -0
- package/dist/text-msdf.d.ts +70 -0
- package/dist/text-msdf.js +242 -0
- package/dist/text-raster.d.ts +40 -0
- package/dist/text-raster.js +60 -0
- package/dist/text-scenes.d.ts +85 -0
- package/dist/text-scenes.js +338 -0
- package/dist/text-sdf.d.ts +28 -0
- package/dist/text-sdf.js +119 -0
- package/package.json +80 -4
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shader-driven infinite ground grid, ported from the reference reference
|
|
3
|
+
* `the upstream reference`.
|
|
4
|
+
*
|
|
5
|
+
* The port keeps the reference algorithm and parameters:
|
|
6
|
+
* - a finite XZ quad is drawn once and faded near its bounds, so it reads as endless;
|
|
7
|
+
* - lines are computed in the fragment shader from world XZ coordinates;
|
|
8
|
+
* - three decimal LOD layers (finer / current / coarser) are combined, and their
|
|
9
|
+
* alphas are de-duplicated so overlapping layers do not stack brightness;
|
|
10
|
+
* - the LOD distance follows the reference camera-state rules from
|
|
11
|
+
* `infinite_grid_3d.gd::_update_camera_lod_distance`.
|
|
12
|
+
*
|
|
13
|
+
* Everything else (`grid_size`, `cell_size`, `line_width_pixels`, LOD ranges,
|
|
14
|
+
* center/grazing/edge/far fades, stipple discard and line colors) mirrors the
|
|
15
|
+
* reference uniforms one to one. `setOpacity` is the only demo-side extension:
|
|
16
|
+
* it scales the two reference line alphas instead of replacing them.
|
|
17
|
+
*/
|
|
18
|
+
import { draw, geometry } from "vgpu";
|
|
19
|
+
import { prepareShader } from "@vgpu/wgsl/prepare";
|
|
20
|
+
import { linearGridColor } from "./color-space.js";
|
|
21
|
+
const GRID_SHADER = prepareShader(`
|
|
22
|
+
struct GridUniforms {
|
|
23
|
+
viewProjection: mat4x4f,
|
|
24
|
+
cameraPosition: vec3f,
|
|
25
|
+
cellSize: f32,
|
|
26
|
+
viewGridCenter: vec2f,
|
|
27
|
+
gridCenter: vec2f,
|
|
28
|
+
lineWidthPixels: f32,
|
|
29
|
+
gridSize: f32,
|
|
30
|
+
edgeFadeStartRatio: f32,
|
|
31
|
+
edgeFadeEndRatio: f32,
|
|
32
|
+
minLodPower: f32,
|
|
33
|
+
maxLodPower: f32,
|
|
34
|
+
cameraLodDistance: f32,
|
|
35
|
+
cameraFarClip: f32,
|
|
36
|
+
grazingFadeStartFactor: f32,
|
|
37
|
+
grazingFadeEndFactor: f32,
|
|
38
|
+
lodCenterFadeLineCount: f32,
|
|
39
|
+
optionsA: vec4<u32>,
|
|
40
|
+
optionsB: vec4<u32>,
|
|
41
|
+
thinLineColor: vec4f,
|
|
42
|
+
thickLineColor: vec4f,
|
|
43
|
+
}
|
|
44
|
+
@group(0) @binding(0) var<uniform> grid: GridUniforms;
|
|
45
|
+
|
|
46
|
+
// optionsA: x = camera_is_orthogonal, y = debug_lod_colors, z = enable_grazing_opacity,
|
|
47
|
+
// w = enable_stipple_discard.
|
|
48
|
+
// optionsB: x = enable_lod_center_fade.
|
|
49
|
+
|
|
50
|
+
struct In { @location(0) position: vec3f }
|
|
51
|
+
struct Out {
|
|
52
|
+
@builtin(position) clip: vec4f,
|
|
53
|
+
@location(0) world: vec3f,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@vertex fn vs_main(input: In) -> Out {
|
|
57
|
+
var out: Out;
|
|
58
|
+
let world = input.position + vec3f(grid.gridCenter.x, 0.0, grid.gridCenter.y);
|
|
59
|
+
out.world = world;
|
|
60
|
+
out.clip = grid.viewProjection * vec4f(world, 1.0);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Line coverage in pixel units so lines keep a stable screen-space width.
|
|
65
|
+
fn gridLineAlpha(uv: vec2f, currentCellSize: f32) -> f32 {
|
|
66
|
+
let coord = uv / currentCellSize;
|
|
67
|
+
let pixelSize = max(abs(dpdx(coord)) + abs(dpdy(coord)), vec2f(0.00001));
|
|
68
|
+
let distanceToLine = min(fract(coord), 1.0 - fract(coord));
|
|
69
|
+
let distancePixels = distanceToLine / pixelSize;
|
|
70
|
+
// 1 px AA transition avoids hard edges and flicker on thin lines.
|
|
71
|
+
let halfLineWidthPixels = max(grid.lineWidthPixels, 0.25) * 0.5;
|
|
72
|
+
let coverage = 1.0 - smoothstep(
|
|
73
|
+
vec2f(max(halfLineWidthPixels - 0.5, 0.0)),
|
|
74
|
+
vec2f(halfLineWidthPixels + 0.5),
|
|
75
|
+
distancePixels
|
|
76
|
+
);
|
|
77
|
+
return max(coverage.x, coverage.y);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn lodCenterFade(uv: vec2f, currentCellSize: f32) -> f32 {
|
|
81
|
+
if (grid.optionsB.x == 0u) {
|
|
82
|
+
return 1.0;
|
|
83
|
+
}
|
|
84
|
+
let halfLineCount = max(grid.lodCenterFadeLineCount * 0.5, 1.0);
|
|
85
|
+
let snappedCenter = round(grid.viewGridCenter / currentCellSize) * currentCellSize;
|
|
86
|
+
let levelCoord = (uv - snappedCenter) / max(currentCellSize * halfLineCount, 0.00001);
|
|
87
|
+
if (grid.optionsA.x != 0u) {
|
|
88
|
+
let edge = 1.0 - min(1.0, dot(levelCoord, levelCoord));
|
|
89
|
+
return edge * edge;
|
|
90
|
+
}
|
|
91
|
+
return 1.0 - min(1.0, length(levelCoord));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@fragment fn fs_main(input: Out) -> @location(0) vec4f {
|
|
95
|
+
let uv = input.world.xz;
|
|
96
|
+
|
|
97
|
+
let safeCellSize = max(grid.cellSize, 0.00001);
|
|
98
|
+
let minPower = min(grid.minLodPower, grid.maxLodPower - 2.0);
|
|
99
|
+
let maxPower = max(grid.maxLodPower, minPower + 2.0);
|
|
100
|
+
let minLodCellSize = safeCellSize * pow(10.0, minPower);
|
|
101
|
+
let lodDistance = max(grid.cameraLodDistance, minLodCellSize);
|
|
102
|
+
let currentLodPower = clamp(
|
|
103
|
+
floor(log(lodDistance / safeCellSize) / log(10.0)),
|
|
104
|
+
minPower,
|
|
105
|
+
maxPower
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Select the current decimal LOD and fade toward the next coarser cell size.
|
|
109
|
+
let currentLodCellSize = safeCellSize * pow(10.0, currentLodPower);
|
|
110
|
+
let nextLodCellSize = currentLodCellSize * 10.0;
|
|
111
|
+
let lodFade = clamp(
|
|
112
|
+
(lodDistance - currentLodCellSize) / max(nextLodCellSize - currentLodCellSize, 0.00001),
|
|
113
|
+
0.0,
|
|
114
|
+
1.0
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Three neighboring LOD layers: fine lines fade out while major lines remain.
|
|
118
|
+
let lod0Power = clamp(currentLodPower - 1.0, minPower, maxPower);
|
|
119
|
+
let lod1Power = clamp(currentLodPower, minPower, maxPower);
|
|
120
|
+
let lod2Power = clamp(currentLodPower + 1.0, minPower, maxPower);
|
|
121
|
+
let lod0CellSize = safeCellSize * pow(10.0, lod0Power);
|
|
122
|
+
let lod1CellSize = safeCellSize * pow(10.0, lod1Power);
|
|
123
|
+
let lod2CellSize = safeCellSize * pow(10.0, lod2Power);
|
|
124
|
+
|
|
125
|
+
let lod0Alpha = gridLineAlpha(uv, lod0CellSize) * lodCenterFade(uv, lod0CellSize) * (1.0 - lodFade);
|
|
126
|
+
let lod1Alpha = gridLineAlpha(uv, lod1CellSize) * lodCenterFade(uv, lod1CellSize);
|
|
127
|
+
let lod2Alpha = gridLineAlpha(uv, lod2CellSize) * lodCenterFade(uv, lod2CellSize);
|
|
128
|
+
|
|
129
|
+
// Prevent overlapping LOD layers from stacking alpha and causing brightness jumps.
|
|
130
|
+
let lod2VisibleAlpha = lod2Alpha;
|
|
131
|
+
let lod1VisibleAlpha = lod1Alpha * (1.0 - lod2VisibleAlpha);
|
|
132
|
+
let lod0VisibleAlpha = lod0Alpha * (1.0 - lod1Alpha) * (1.0 - lod2VisibleAlpha);
|
|
133
|
+
|
|
134
|
+
// Blend minor/current/major line colors across the active LOD transition.
|
|
135
|
+
var lod0Color = grid.thinLineColor;
|
|
136
|
+
var lod1Color = mix(grid.thickLineColor, grid.thinLineColor, lodFade);
|
|
137
|
+
var lod2Color = grid.thickLineColor;
|
|
138
|
+
if (grid.optionsA.y != 0u) {
|
|
139
|
+
// Debug layers: blue = lower/finer, green = current, red = upper/coarser.
|
|
140
|
+
// Only RGB is replaced so alpha stays directly comparable with debug off.
|
|
141
|
+
lod0Color = vec4f(0.1, 0.35, 1.0, lod0Color.a);
|
|
142
|
+
lod1Color = vec4f(0.1, 1.0, 0.25, lod1Color.a);
|
|
143
|
+
lod2Color = vec4f(1.0, 0.15, 0.1, lod2Color.a);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let lod0LayerAlpha = lod0VisibleAlpha * lod0Color.a;
|
|
147
|
+
let lod1LayerAlpha = lod1VisibleAlpha * lod1Color.a;
|
|
148
|
+
let lod2LayerAlpha = lod2VisibleAlpha * lod2Color.a;
|
|
149
|
+
let lineAlpha = clamp(lod0LayerAlpha + lod1LayerAlpha + lod2LayerAlpha, 0.0, 1.0);
|
|
150
|
+
let lineColor = (
|
|
151
|
+
lod0Color.rgb * lod0LayerAlpha +
|
|
152
|
+
lod1Color.rgb * lod1LayerAlpha +
|
|
153
|
+
lod2Color.rgb * lod2LayerAlpha
|
|
154
|
+
) / max(lineAlpha, 0.00001);
|
|
155
|
+
|
|
156
|
+
// Fade out near the finite mesh bounds so the grid appears endless.
|
|
157
|
+
let halfGridSize = max(grid.gridSize * 0.5, 0.00001);
|
|
158
|
+
let normalizedGridCoord = (uv - grid.gridCenter) / halfGridSize;
|
|
159
|
+
let edgeStart = clamp(grid.edgeFadeStartRatio, 0.0, 1.0);
|
|
160
|
+
let edgeEnd = max(clamp(grid.edgeFadeEndRatio, 0.0, 1.0), edgeStart + 0.00001);
|
|
161
|
+
let edgeProgress = clamp(
|
|
162
|
+
(length(normalizedGridCoord) - edgeStart) / (edgeEnd - edgeStart),
|
|
163
|
+
0.0,
|
|
164
|
+
1.0
|
|
165
|
+
);
|
|
166
|
+
var edgeOpacity = 1.0 - edgeProgress;
|
|
167
|
+
if (grid.optionsA.x != 0u) {
|
|
168
|
+
edgeOpacity = pow(1.0 - edgeProgress * edgeProgress, 2.0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Fade at grazing view angles to reduce distant transparent trails.
|
|
172
|
+
var grazingOpacity = 1.0;
|
|
173
|
+
if (grid.optionsA.z != 0u) {
|
|
174
|
+
let viewDir = normalize(grid.cameraPosition - input.world);
|
|
175
|
+
let viewFloorFactor = abs(dot(viewDir, vec3f(0.0, 1.0, 0.0)));
|
|
176
|
+
let grazingStart = min(grid.grazingFadeStartFactor, grid.grazingFadeEndFactor);
|
|
177
|
+
let grazingEnd = max(grid.grazingFadeStartFactor, grid.grazingFadeEndFactor);
|
|
178
|
+
var grazingProgress = step(grazingStart, viewFloorFactor);
|
|
179
|
+
if (grazingEnd - grazingStart > 0.00001) {
|
|
180
|
+
grazingProgress = clamp(
|
|
181
|
+
(viewFloorFactor - grazingStart) / (grazingEnd - grazingStart),
|
|
182
|
+
0.0,
|
|
183
|
+
1.0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
grazingOpacity = smoothstep(0.0, 1.0, grazingProgress);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Fade near the camera far clip before the grid is clipped by depth.
|
|
190
|
+
let farClip = max(grid.cameraFarClip, 0.00001);
|
|
191
|
+
let cameraDistance = length(grid.cameraPosition - input.world);
|
|
192
|
+
let distanceOpacity = 1.0 - smoothstep(0.0, 0.5 * farClip, cameraDistance - 0.5 * farClip);
|
|
193
|
+
|
|
194
|
+
let alpha = lineAlpha * edgeOpacity * distanceOpacity * grazingOpacity;
|
|
195
|
+
|
|
196
|
+
// Stipple discard below 0.1 alpha avoids muddy trails from transparent depth blending.
|
|
197
|
+
if (grid.optionsA.w != 0u) {
|
|
198
|
+
let dashCoord = uv / lod1CellSize;
|
|
199
|
+
let dashDistanceToLine = min(fract(dashCoord), 1.0 - fract(dashCoord));
|
|
200
|
+
let dashAxis = select(input.clip.x, input.clip.y, dashDistanceToLine.x < dashDistanceToLine.y);
|
|
201
|
+
if (alpha < 0.1 && alpha / 0.1 < fract(dashAxis / 4.0)) {
|
|
202
|
+
discard;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (alpha <= 0.001) {
|
|
207
|
+
discard;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return vec4f(lineColor, alpha);
|
|
211
|
+
}
|
|
212
|
+
`);
|
|
213
|
+
const REFERENCE_THIN_LINE_COLOR = [0.42, 0.42, 0.42, 0.45];
|
|
214
|
+
const REFERENCE_THICK_LINE_COLOR = [0.62, 0.62, 0.62, 0.65];
|
|
215
|
+
const MIN_LOD_CELL_SIZE = 0.0001;
|
|
216
|
+
/** Mirrors the reference property setters, including their clamps. */
|
|
217
|
+
export function resolveGridOptions(options) {
|
|
218
|
+
return {
|
|
219
|
+
size: Math.max(options.size ?? 2000, 1),
|
|
220
|
+
edgeFadeStartRatio: clamp(options.edgeFadeStartRatio ?? 0, 0, 1),
|
|
221
|
+
edgeFadeEndRatio: clamp(options.edgeFadeEndRatio ?? 1, 0, 1),
|
|
222
|
+
cellSize: Math.max(options.cellSize ?? 1, 0.0001),
|
|
223
|
+
lineWidthPixels: Math.max(options.lineWidthPixels ?? 1, 0.25),
|
|
224
|
+
lodFinerLevels: Math.max(Math.trunc(options.lodFinerLevels ?? 3), 0),
|
|
225
|
+
lodTotalLevels: Math.max(Math.trunc(options.lodTotalLevels ?? 8), 3),
|
|
226
|
+
enableGrazingOpacity: options.enableGrazingOpacity ?? true,
|
|
227
|
+
grazingFadeStart: clamp(options.grazingFadeStart ?? 3, 0, 90),
|
|
228
|
+
grazingFadeEnd: clamp(options.grazingFadeEnd ?? 20, 0, 90),
|
|
229
|
+
enableStippleDiscard: options.enableStippleDiscard ?? false,
|
|
230
|
+
enableLodCenterFade: options.enableLodCenterFade ?? true,
|
|
231
|
+
lodCenterFadeLineCount: options.lodCenterFadeLineCount ?? 151,
|
|
232
|
+
thinLineColor: options.thinLineColor ?? REFERENCE_THIN_LINE_COLOR,
|
|
233
|
+
thickLineColor: options.thickLineColor ?? REFERENCE_THICK_LINE_COLOR,
|
|
234
|
+
debugLodColors: options.debugLodColors ?? false,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function clamp(value, min, max) {
|
|
238
|
+
return Math.min(Math.max(value, min), max);
|
|
239
|
+
}
|
|
240
|
+
/** Reference `_get_min_lod_cell_size()`. */
|
|
241
|
+
export function minLodCellSize(options) {
|
|
242
|
+
return Math.max(options.cellSize * 10 ** -options.lodFinerLevels, MIN_LOD_CELL_SIZE);
|
|
243
|
+
}
|
|
244
|
+
/** Reference `_apply_lod_range_parameters()`. */
|
|
245
|
+
export function lodPowers(options) {
|
|
246
|
+
const min = -options.lodFinerLevels;
|
|
247
|
+
return { min, max: min + options.lodTotalLevels - 1 };
|
|
248
|
+
}
|
|
249
|
+
/** Reference `_apply_grazing_fade_parameters()`. */
|
|
250
|
+
export function grazingFactors(options) {
|
|
251
|
+
const toRadians = (degrees) => (degrees * Math.PI) / 180;
|
|
252
|
+
return { start: Math.sin(toRadians(options.grazingFadeStart)), end: Math.sin(toRadians(options.grazingFadeEnd)) };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Reference `_update_camera_lod_distance()` with the grid plane at y = 0: the
|
|
256
|
+
* distance to the view floor drives the LOD, falling back to camera height near
|
|
257
|
+
* grazing angles so the LOD distance cannot blow up.
|
|
258
|
+
*/
|
|
259
|
+
export function gridLodState(camera, options = {}) {
|
|
260
|
+
const resolved = resolveGridOptions(options);
|
|
261
|
+
const minimum = minLodCellSize(resolved);
|
|
262
|
+
const [forwardX, forwardY, forwardZ] = camera.cameraForward;
|
|
263
|
+
const [cameraX, cameraY, cameraZ] = camera.cameraPosition;
|
|
264
|
+
const gridY = 0;
|
|
265
|
+
if (camera.projection === "orthographic") {
|
|
266
|
+
const size = Math.max(camera.orthographicSize ?? 0, minimum);
|
|
267
|
+
let center = [cameraX, cameraZ];
|
|
268
|
+
if (Math.abs(forwardY) > 0.0001) {
|
|
269
|
+
const rayDistance = (gridY - cameraY) / forwardY;
|
|
270
|
+
center = [cameraX + forwardX * rayDistance, cameraZ + forwardZ * rayDistance];
|
|
271
|
+
}
|
|
272
|
+
return { lodDistance: size, viewGridCenter: center, cameraIsOrthogonal: true };
|
|
273
|
+
}
|
|
274
|
+
const height = Math.abs(cameraY - gridY);
|
|
275
|
+
const viewFloorFactor = Math.abs(forwardY);
|
|
276
|
+
const rayFloorDistance = height / Math.max(viewFloorFactor, 0.0001);
|
|
277
|
+
// Match Blender: blend view-to-ground distance toward camera height near grazing angles.
|
|
278
|
+
const lodDistance = Math.max(rayFloorDistance + (height - rayFloorDistance) * (1 - viewFloorFactor), minimum);
|
|
279
|
+
return {
|
|
280
|
+
lodDistance,
|
|
281
|
+
viewGridCenter: [cameraX + forwardX * lodDistance, cameraZ + forwardZ * lodDistance],
|
|
282
|
+
cameraIsOrthogonal: false,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** The quad is centered on the camera XZ, matching `follow_viewport_camera`. */
|
|
286
|
+
export class InfiniteGrid3D {
|
|
287
|
+
#gpu;
|
|
288
|
+
#draw;
|
|
289
|
+
#geometry;
|
|
290
|
+
#options;
|
|
291
|
+
#opacity = 1;
|
|
292
|
+
#camera = {
|
|
293
|
+
viewProjection: new Float32Array(16),
|
|
294
|
+
cameraPosition: [0, 5, 8],
|
|
295
|
+
cameraForward: [0, -1, 0],
|
|
296
|
+
projection: "perspective",
|
|
297
|
+
farClip: 100,
|
|
298
|
+
};
|
|
299
|
+
constructor(gpu, options = {}) {
|
|
300
|
+
this.#gpu = gpu;
|
|
301
|
+
this.#options = resolveGridOptions(options);
|
|
302
|
+
this.#geometry = buildGeometry(gpu, this.#options.size);
|
|
303
|
+
this.#draw = buildDraw(gpu, this.#geometry);
|
|
304
|
+
}
|
|
305
|
+
prepare(camera) {
|
|
306
|
+
this.#camera = camera;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Applies reference-style property updates. Only `size` rebuilds the quad;
|
|
310
|
+
* every other field is a per-frame uniform, exactly like the reference setters.
|
|
311
|
+
*/
|
|
312
|
+
setOptions(options) {
|
|
313
|
+
const previousSize = this.#options.size;
|
|
314
|
+
this.#options = resolveGridOptions({ ...this.#currentOptions(), ...options });
|
|
315
|
+
if (this.#options.size !== previousSize)
|
|
316
|
+
this.#rebuild();
|
|
317
|
+
}
|
|
318
|
+
/** Reference `set_grid_size()`: rebuilds the finite quad. */
|
|
319
|
+
setSize(size) {
|
|
320
|
+
this.#options.size = Math.max(size, 1);
|
|
321
|
+
this.#rebuild();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Demo extension over the reference: scales `thin_line_color.a` and
|
|
325
|
+
* `thick_line_color.a` without discarding their reference ratio.
|
|
326
|
+
*/
|
|
327
|
+
setOpacity(opacity) {
|
|
328
|
+
this.#opacity = clamp(opacity, 0, 1);
|
|
329
|
+
}
|
|
330
|
+
draw(pass) {
|
|
331
|
+
const options = this.#options;
|
|
332
|
+
const lod = gridLodState(this.#camera, options);
|
|
333
|
+
const powers = lodPowers(options);
|
|
334
|
+
const grazing = grazingFactors(options);
|
|
335
|
+
const thinAlpha = options.thinLineColor[3] * this.#opacity;
|
|
336
|
+
const thickAlpha = options.thickLineColor[3] * this.#opacity;
|
|
337
|
+
this.#draw.set({
|
|
338
|
+
grid: {
|
|
339
|
+
viewProjection: this.#camera.viewProjection,
|
|
340
|
+
cameraPosition: this.#camera.cameraPosition,
|
|
341
|
+
cellSize: options.cellSize,
|
|
342
|
+
viewGridCenter: lod.viewGridCenter,
|
|
343
|
+
gridCenter: [this.#camera.cameraPosition[0], this.#camera.cameraPosition[2]],
|
|
344
|
+
lineWidthPixels: options.lineWidthPixels,
|
|
345
|
+
gridSize: options.size,
|
|
346
|
+
edgeFadeStartRatio: options.edgeFadeStartRatio,
|
|
347
|
+
edgeFadeEndRatio: options.edgeFadeEndRatio,
|
|
348
|
+
minLodPower: powers.min,
|
|
349
|
+
maxLodPower: powers.max,
|
|
350
|
+
cameraLodDistance: lod.lodDistance,
|
|
351
|
+
cameraFarClip: this.#camera.farClip,
|
|
352
|
+
grazingFadeStartFactor: grazing.start,
|
|
353
|
+
grazingFadeEndFactor: grazing.end,
|
|
354
|
+
lodCenterFadeLineCount: options.lodCenterFadeLineCount,
|
|
355
|
+
optionsA: [
|
|
356
|
+
lod.cameraIsOrthogonal ? 1 : 0,
|
|
357
|
+
options.debugLodColors ? 1 : 0,
|
|
358
|
+
options.enableGrazingOpacity ? 1 : 0,
|
|
359
|
+
options.enableStippleDiscard ? 1 : 0,
|
|
360
|
+
],
|
|
361
|
+
optionsB: [options.enableLodCenterFade ? 1 : 0, 0, 0, 0],
|
|
362
|
+
thinLineColor: linearGridColor(options.thinLineColor, thinAlpha),
|
|
363
|
+
thickLineColor: linearGridColor(options.thickLineColor, thickAlpha),
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
pass.draw(this.#draw);
|
|
367
|
+
}
|
|
368
|
+
destroy() {
|
|
369
|
+
this.#draw.dispose();
|
|
370
|
+
this.#geometry.destroy();
|
|
371
|
+
}
|
|
372
|
+
#rebuild() {
|
|
373
|
+
this.#draw.dispose();
|
|
374
|
+
this.#geometry.destroy();
|
|
375
|
+
this.#geometry = buildGeometry(this.#gpu, this.#options.size);
|
|
376
|
+
this.#draw = buildDraw(this.#gpu, this.#geometry);
|
|
377
|
+
}
|
|
378
|
+
#currentOptions() {
|
|
379
|
+
return { ...this.#options, thinLineColor: this.#options.thinLineColor, thickLineColor: this.#options.thickLineColor };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function buildGeometry(gpu, size) {
|
|
383
|
+
const half = size * 0.5;
|
|
384
|
+
return geometry(gpu, {
|
|
385
|
+
buffers: [{
|
|
386
|
+
attributes: { position: "float32x3" },
|
|
387
|
+
data: new Float32Array([
|
|
388
|
+
-half, 0, -half, half, 0, -half, half, 0, half,
|
|
389
|
+
-half, 0, -half, half, 0, half, -half, 0, half,
|
|
390
|
+
]),
|
|
391
|
+
}],
|
|
392
|
+
vertexCount: 6,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Reference shader uses `render_mode unshaded, blend_mix, depth_draw_always,
|
|
397
|
+
* cull_disabled`: unlit, straight-alpha blending, two-sided, and depth writes on.
|
|
398
|
+
*/
|
|
399
|
+
function buildDraw(gpu, quad) {
|
|
400
|
+
return draw(gpu, {
|
|
401
|
+
shader: GRID_SHADER,
|
|
402
|
+
geometry: quad,
|
|
403
|
+
blend: "alpha",
|
|
404
|
+
cull: "none",
|
|
405
|
+
depth: { write: true, compare: "less-equal" },
|
|
406
|
+
});
|
|
407
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
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;
|