@ifc-lite/renderer 1.16.0 → 1.18.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 (47) hide show
  1. package/README.md +73 -15
  2. package/dist/edl-pass.d.ts +52 -0
  3. package/dist/edl-pass.d.ts.map +1 -0
  4. package/dist/edl-pass.js +204 -0
  5. package/dist/edl-pass.js.map +1 -0
  6. package/dist/index.d.ts +77 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +348 -19
  9. package/dist/index.js.map +1 -1
  10. package/dist/picker.d.ts +22 -3
  11. package/dist/picker.d.ts.map +1 -1
  12. package/dist/picker.js +48 -8
  13. package/dist/picker.js.map +1 -1
  14. package/dist/picking-manager.d.ts +14 -1
  15. package/dist/picking-manager.d.ts.map +1 -1
  16. package/dist/picking-manager.js +13 -1
  17. package/dist/picking-manager.js.map +1 -1
  18. package/dist/pipeline.d.ts.map +1 -1
  19. package/dist/pipeline.js +16 -1
  20. package/dist/pipeline.js.map +1 -1
  21. package/dist/point-picker.d.ts +61 -0
  22. package/dist/point-picker.d.ts.map +1 -0
  23. package/dist/point-picker.js +223 -0
  24. package/dist/point-picker.js.map +1 -0
  25. package/dist/pointcloud/point-cloud-node.d.ts +56 -0
  26. package/dist/pointcloud/point-cloud-node.d.ts.map +1 -0
  27. package/dist/pointcloud/point-cloud-node.js +112 -0
  28. package/dist/pointcloud/point-cloud-node.js.map +1 -0
  29. package/dist/pointcloud/point-cloud-renderer.d.ts +135 -0
  30. package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -0
  31. package/dist/pointcloud/point-cloud-renderer.js +296 -0
  32. package/dist/pointcloud/point-cloud-renderer.js.map +1 -0
  33. package/dist/pointcloud/point-pipeline.d.ts +25 -0
  34. package/dist/pointcloud/point-pipeline.d.ts.map +1 -0
  35. package/dist/pointcloud/point-pipeline.js +111 -0
  36. package/dist/pointcloud/point-pipeline.js.map +1 -0
  37. package/dist/pointcloud/point-shader.wgsl.d.ts +22 -0
  38. package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -0
  39. package/dist/pointcloud/point-shader.wgsl.js +212 -0
  40. package/dist/pointcloud/point-shader.wgsl.js.map +1 -0
  41. package/dist/shaders/main.wgsl.d.ts +1 -1
  42. package/dist/shaders/main.wgsl.d.ts.map +1 -1
  43. package/dist/shaders/main.wgsl.js +15 -3
  44. package/dist/shaders/main.wgsl.js.map +1 -1
  45. package/dist/types.d.ts +20 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/package.json +3 -3
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Manages point cloud assets in the renderer.
3
+ *
4
+ * Supports two ingest modes:
5
+ * - One-shot: `addAsset(asset)` for inline IFCx pointclouds.
6
+ * - Streaming: `beginAsset(meta) → handle`, `appendChunk(handle, chunk)`,
7
+ * `endAsset(handle)` for LAS/LAZ files arriving in chunks.
8
+ *
9
+ * The renderer owns the pipeline, per-asset GPU resources, and the per-frame
10
+ * draw call. Designed to slot into the existing `Renderer.render()` so points
11
+ * share the depth buffer and section-plane state with triangle meshes.
12
+ */
13
+ import type { PointCloudAsset } from '@ifc-lite/geometry';
14
+ import { type PointCloudChunkInput, type PointCloudNodeMeta } from './point-cloud-node.js';
15
+ export interface ResolvedSectionPlane {
16
+ normal: [number, number, number];
17
+ distance: number;
18
+ enabled: boolean;
19
+ flipped?: boolean;
20
+ }
21
+ export type PointColorMode = 'rgb' | 'classification' | 'intensity' | 'height' | 'fixed';
22
+ /**
23
+ * How to size a splat on screen.
24
+ * - `fixed-px` every splat is `pointSize` pixels wide
25
+ * - `adaptive-world` splat covers `worldRadius` metres in source space,
26
+ * projected each frame (closer → bigger)
27
+ * - `attenuated` adaptive but clamped between 1 px and `pointSize`
28
+ * so splats stay visible at the far plane and don't
29
+ * blow up to half the screen when you nose into the
30
+ * cloud — usually the best default for nav.
31
+ */
32
+ export type PointSizeMode = 'fixed-px' | 'adaptive-world' | 'attenuated';
33
+ export interface PointCloudDrawState {
34
+ /** column-major view-projection matrix (16 floats) */
35
+ viewProj: Float32Array;
36
+ /** Section plane already resolved by the main render path. */
37
+ sectionPlane?: ResolvedSectionPlane | null;
38
+ /** Viewport size in pixels — needed by the splat shader to convert
39
+ * pixel sizes into clip-space offsets. */
40
+ viewport?: {
41
+ width: number;
42
+ height: number;
43
+ };
44
+ }
45
+ export interface PointCloudRenderOptions {
46
+ /** How to color points each frame. Defaults to 'rgb'. */
47
+ colorMode?: PointColorMode;
48
+ /** RGBA in 0..1, used when colorMode === 'fixed'. */
49
+ fixedColor?: [number, number, number, number];
50
+ /** Splat size in pixels (mode='fixed-px'/'attenuated') or maximum size cap. */
51
+ pointSize?: number;
52
+ /** Splat sizing strategy. Defaults to `attenuated`. */
53
+ sizeMode?: PointSizeMode;
54
+ /** World-space splat radius in metres for adaptive / attenuated modes.
55
+ * Defaults to 0.02 m which works well for typical 5–20 mm scan spacing. */
56
+ worldRadius?: number;
57
+ /** Render splats as discs instead of squares. Defaults to true. */
58
+ roundShape?: boolean;
59
+ }
60
+ export interface PointCloudAssetHandle {
61
+ readonly id: number;
62
+ }
63
+ export declare class PointCloudRenderer {
64
+ private device;
65
+ private pipeline;
66
+ private nodes;
67
+ private nodeOwners;
68
+ private nextHandleId;
69
+ private uniformScratch;
70
+ private uniformScratchU32;
71
+ private options;
72
+ constructor(device: GPUDevice, colorFormat: GPUTextureFormat, depthFormat: GPUTextureFormat, sampleCount: number);
73
+ setOptions(opts: PointCloudRenderOptions): void;
74
+ getOptions(): Readonly<Required<PointCloudRenderOptions>>;
75
+ /**
76
+ * Replace every IFCx-owned asset with `assets`. Streamed assets are
77
+ * untouched. Use this from the viewer's IFCx sync hook.
78
+ */
79
+ setAssets(assets: ReadonlyArray<PointCloudAsset>): void;
80
+ addAsset(asset: PointCloudAsset): PointCloudAssetHandle;
81
+ /** Open an empty asset that chunks will be appended to. */
82
+ beginAsset(meta: PointCloudNodeMeta): PointCloudAssetHandle;
83
+ appendChunk(handle: PointCloudAssetHandle, chunk: PointCloudChunkInput): void;
84
+ /** Mark streaming complete. No-op for now — kept for symmetry. */
85
+ endAsset(handle: PointCloudAssetHandle): void;
86
+ removeAsset(handle: PointCloudAssetHandle): void;
87
+ /**
88
+ * Reassign a streamed asset's `expressId` after upload — used by
89
+ * `useIfcFederation` when the FederationRegistry hands out an
90
+ * `idOffset` for the model. The shader reads expressId from a
91
+ * per-asset uniform (flags.x), so this is just a metadata update;
92
+ * the next frame writes the new value into the GPU uniform without
93
+ * touching the per-vertex attributes.
94
+ */
95
+ relabelAsset(handle: PointCloudAssetHandle, newExpressId: number): void;
96
+ clear(): void;
97
+ private clearOwner;
98
+ hasAssets(): boolean;
99
+ getNodeCount(): number;
100
+ /** Total number of points currently uploaded across all assets. */
101
+ getPointCount(): number;
102
+ getBounds(): {
103
+ min: [number, number, number];
104
+ max: [number, number, number];
105
+ } | null;
106
+ /**
107
+ * Issue draw calls into an already-open render pass. The caller owns
108
+ * the encoder/pass and is responsible for the depth attachment.
109
+ */
110
+ draw(pass: GPURenderPassEncoder, state: PointCloudDrawState): void;
111
+ private writeUniforms;
112
+ /**
113
+ * Resolve a packed objectId rgba8 sample back to the asset that owns it.
114
+ * Returns null when the sample doesn't match any asset's expressId.
115
+ */
116
+ resolvePick(expressId: number): {
117
+ handle: PointCloudAssetHandle;
118
+ meta: PointCloudNodeMeta;
119
+ } | null;
120
+ /**
121
+ * Snapshot of nodes shaped for the picker — only the data the GPU
122
+ * picking pass actually needs (expressId, modelIndex, chunk vertex
123
+ * buffers + counts). Returns a fresh array; callers may iterate
124
+ * freely without worrying about mutation during a pick.
125
+ */
126
+ getPickNodes(): Array<{
127
+ expressId: number;
128
+ modelIndex?: number;
129
+ chunks: Array<{
130
+ vertexBuffer: GPUBuffer;
131
+ pointCount: number;
132
+ }>;
133
+ }>;
134
+ }
135
+ //# sourceMappingURL=point-cloud-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-cloud-renderer.d.ts","sourceRoot":"","sources":["../../src/pointcloud/point-cloud-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,296 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { PointRenderPipeline, POINT_QUAD_VERTS, POINT_UNIFORM_SIZE } from './point-pipeline.js';
5
+ import { appendChunkToNode, createNode, destroyNode, uploadAssetToGpu, } from './point-cloud-node.js';
6
+ const COLOR_MODE_INDEX = {
7
+ rgb: 0,
8
+ classification: 1,
9
+ intensity: 2,
10
+ height: 3,
11
+ fixed: 4,
12
+ };
13
+ const SIZE_MODE_INDEX = {
14
+ 'fixed-px': 0,
15
+ 'adaptive-world': 1,
16
+ 'attenuated': 2,
17
+ };
18
+ export class PointCloudRenderer {
19
+ device;
20
+ pipeline;
21
+ nodes = new Map();
22
+ nodeOwners = new Map();
23
+ nextHandleId = 1;
24
+ uniformScratch = new Float32Array(POINT_UNIFORM_SIZE / 4);
25
+ uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer);
26
+ options = {
27
+ colorMode: 'rgb',
28
+ fixedColor: [1, 1, 1, 1],
29
+ pointSize: 4,
30
+ sizeMode: 'attenuated',
31
+ worldRadius: 0.02,
32
+ roundShape: true,
33
+ };
34
+ constructor(device, colorFormat, depthFormat, sampleCount) {
35
+ this.device = device;
36
+ this.pipeline = new PointRenderPipeline(device, colorFormat, depthFormat, sampleCount);
37
+ }
38
+ setOptions(opts) {
39
+ if (opts.colorMode !== undefined)
40
+ this.options.colorMode = opts.colorMode;
41
+ if (opts.fixedColor !== undefined)
42
+ this.options.fixedColor = opts.fixedColor;
43
+ if (opts.pointSize !== undefined)
44
+ this.options.pointSize = opts.pointSize;
45
+ if (opts.sizeMode !== undefined)
46
+ this.options.sizeMode = opts.sizeMode;
47
+ if (opts.worldRadius !== undefined)
48
+ this.options.worldRadius = opts.worldRadius;
49
+ if (opts.roundShape !== undefined)
50
+ this.options.roundShape = opts.roundShape;
51
+ }
52
+ getOptions() {
53
+ return this.options;
54
+ }
55
+ // ─── one-shot API (IFCx) ──────────────────────────────────────────────────
56
+ /**
57
+ * Replace every IFCx-owned asset with `assets`. Streamed assets are
58
+ * untouched. Use this from the viewer's IFCx sync hook.
59
+ */
60
+ setAssets(assets) {
61
+ this.clearOwner('ifcx');
62
+ for (const asset of assets) {
63
+ this.addAsset(asset);
64
+ }
65
+ }
66
+ addAsset(asset) {
67
+ const node = uploadAssetToGpu(this.device, this.pipeline, asset);
68
+ const id = this.nextHandleId++;
69
+ this.nodes.set(id, node);
70
+ this.nodeOwners.set(id, 'ifcx');
71
+ return { id };
72
+ }
73
+ // ─── streaming API (LAS / LAZ) ────────────────────────────────────────────
74
+ /** Open an empty asset that chunks will be appended to. */
75
+ beginAsset(meta) {
76
+ const node = createNode(this.device, this.pipeline, meta);
77
+ const id = this.nextHandleId++;
78
+ this.nodes.set(id, node);
79
+ this.nodeOwners.set(id, 'streamed');
80
+ return { id };
81
+ }
82
+ appendChunk(handle, chunk) {
83
+ const node = this.nodes.get(handle.id);
84
+ if (!node) {
85
+ console.warn(`[PointCloudRenderer] appendChunk: no node for handle ${handle.id}`);
86
+ return;
87
+ }
88
+ appendChunkToNode(this.device, node, chunk);
89
+ }
90
+ /** Mark streaming complete. No-op for now — kept for symmetry. */
91
+ endAsset(handle) {
92
+ void handle;
93
+ }
94
+ removeAsset(handle) {
95
+ const node = this.nodes.get(handle.id);
96
+ if (!node)
97
+ return;
98
+ destroyNode(node);
99
+ this.nodes.delete(handle.id);
100
+ this.nodeOwners.delete(handle.id);
101
+ }
102
+ /**
103
+ * Reassign a streamed asset's `expressId` after upload — used by
104
+ * `useIfcFederation` when the FederationRegistry hands out an
105
+ * `idOffset` for the model. The shader reads expressId from a
106
+ * per-asset uniform (flags.x), so this is just a metadata update;
107
+ * the next frame writes the new value into the GPU uniform without
108
+ * touching the per-vertex attributes.
109
+ */
110
+ relabelAsset(handle, newExpressId) {
111
+ const node = this.nodes.get(handle.id);
112
+ if (!node)
113
+ return;
114
+ node.meta.expressId = newExpressId >>> 0;
115
+ }
116
+ // ─── lifecycle / queries ─────────────────────────────────────────────────
117
+ clear() {
118
+ for (const node of this.nodes.values()) {
119
+ destroyNode(node);
120
+ }
121
+ this.nodes.clear();
122
+ this.nodeOwners.clear();
123
+ }
124
+ clearOwner(owner) {
125
+ for (const [id, ownerKind] of this.nodeOwners.entries()) {
126
+ if (ownerKind !== owner)
127
+ continue;
128
+ const node = this.nodes.get(id);
129
+ if (node)
130
+ destroyNode(node);
131
+ this.nodes.delete(id);
132
+ this.nodeOwners.delete(id);
133
+ }
134
+ }
135
+ hasAssets() {
136
+ return this.nodes.size > 0;
137
+ }
138
+ getNodeCount() {
139
+ return this.nodes.size;
140
+ }
141
+ /** Total number of points currently uploaded across all assets. */
142
+ getPointCount() {
143
+ let total = 0;
144
+ for (const node of this.nodes.values()) {
145
+ total += node.pointCount;
146
+ }
147
+ return total;
148
+ }
149
+ getBounds() {
150
+ if (this.nodes.size === 0)
151
+ return null;
152
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
153
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
154
+ let any = false;
155
+ for (const node of this.nodes.values()) {
156
+ if (!Number.isFinite(node.bounds.min[0]))
157
+ continue;
158
+ any = true;
159
+ if (node.bounds.min[0] < minX)
160
+ minX = node.bounds.min[0];
161
+ if (node.bounds.min[1] < minY)
162
+ minY = node.bounds.min[1];
163
+ if (node.bounds.min[2] < minZ)
164
+ minZ = node.bounds.min[2];
165
+ if (node.bounds.max[0] > maxX)
166
+ maxX = node.bounds.max[0];
167
+ if (node.bounds.max[1] > maxY)
168
+ maxY = node.bounds.max[1];
169
+ if (node.bounds.max[2] > maxZ)
170
+ maxZ = node.bounds.max[2];
171
+ }
172
+ if (!any)
173
+ return null;
174
+ return { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] };
175
+ }
176
+ /**
177
+ * Issue draw calls into an already-open render pass. The caller owns
178
+ * the encoder/pass and is responsible for the depth attachment.
179
+ */
180
+ draw(pass, state) {
181
+ if (this.nodes.size === 0)
182
+ return;
183
+ pass.setPipeline(this.pipeline.getPipeline());
184
+ const sp = state.sectionPlane ?? null;
185
+ let normal;
186
+ let distance;
187
+ let enabled;
188
+ if (sp && sp.enabled) {
189
+ enabled = true;
190
+ if (sp.flipped) {
191
+ normal = [-sp.normal[0], -sp.normal[1], -sp.normal[2]];
192
+ distance = -sp.distance;
193
+ }
194
+ else {
195
+ normal = sp.normal;
196
+ distance = sp.distance;
197
+ }
198
+ }
199
+ else {
200
+ enabled = false;
201
+ normal = [0, 1, 0];
202
+ distance = 0;
203
+ }
204
+ const bounds = this.getBounds();
205
+ const heightMin = bounds ? bounds.min[1] : 0;
206
+ const heightMax = bounds ? bounds.max[1] : 1;
207
+ // Default to 1×1 if the caller didn't supply a viewport — keeps the
208
+ // shader from dividing by zero in adaptive-world mode and degrades
209
+ // gracefully to "all points the same fixed-px size".
210
+ const viewportW = Math.max(1, state.viewport?.width ?? 1);
211
+ const viewportH = Math.max(1, state.viewport?.height ?? 1);
212
+ for (const node of this.nodes.values()) {
213
+ this.writeUniforms(node, state.viewProj, normal, distance, enabled, heightMin, heightMax, viewportW, viewportH);
214
+ pass.setBindGroup(0, node.bindGroup);
215
+ for (const chunk of node.chunks) {
216
+ pass.setVertexBuffer(0, chunk.vertexBuffer);
217
+ // Six verts per splat, one instance per source point.
218
+ pass.draw(POINT_QUAD_VERTS, chunk.pointCount, 0, 0);
219
+ }
220
+ }
221
+ }
222
+ writeUniforms(node, viewProj, sectionNormal, sectionDist, sectionEnabled, heightMin, heightMax, viewportW, viewportH) {
223
+ const u = this.uniformScratch;
224
+ const uU32 = this.uniformScratchU32;
225
+ // viewProj — floats 0..15
226
+ u.set(viewProj.subarray(0, 16), 0);
227
+ // model — floats 16..31 (identity for now; per-asset transforms can be added later)
228
+ u.fill(0, 16, 32);
229
+ u[16] = 1;
230
+ u[21] = 1;
231
+ u[26] = 1;
232
+ u[31] = 1;
233
+ // colorOverride — floats 32..35
234
+ u[32] = this.options.fixedColor[0];
235
+ u[33] = this.options.fixedColor[1];
236
+ u[34] = this.options.fixedColor[2];
237
+ u[35] = this.options.fixedColor[3];
238
+ // colorModeAndExtras — floats 36..39 (mode, pointSize, heightMin, heightMax)
239
+ u[36] = COLOR_MODE_INDEX[this.options.colorMode];
240
+ u[37] = this.options.pointSize;
241
+ u[38] = heightMin;
242
+ u[39] = heightMax;
243
+ // sizing — floats 40..43 (sizeMode, worldRadius, viewportW, viewportH)
244
+ u[40] = SIZE_MODE_INDEX[this.options.sizeMode];
245
+ u[41] = this.options.worldRadius;
246
+ u[42] = viewportW;
247
+ u[43] = viewportH;
248
+ // sectionPlane — floats 44..47
249
+ u[44] = sectionNormal[0];
250
+ u[45] = sectionNormal[1];
251
+ u[46] = sectionNormal[2];
252
+ u[47] = sectionDist;
253
+ // flags (u32 view) — bytes 192..207 = u32 indices 48..51
254
+ // flags.x = the asset's CURRENT expressId. The shader uses this
255
+ // when non-zero so the federation registry can relabel a streamed
256
+ // asset post-upload (its per-vertex entityId attribute is baked
257
+ // at upload and would otherwise stay at the synthetic local ID).
258
+ uU32[48] = node.meta.expressId >>> 0;
259
+ uU32[49] = sectionEnabled ? 1 : 0;
260
+ uU32[50] = this.options.roundShape ? 1 : 0;
261
+ uU32[51] = 0;
262
+ this.device.queue.writeBuffer(node.uniformBuffer, 0, u.buffer, u.byteOffset, POINT_UNIFORM_SIZE);
263
+ }
264
+ /**
265
+ * Resolve a packed objectId rgba8 sample back to the asset that owns it.
266
+ * Returns null when the sample doesn't match any asset's expressId.
267
+ */
268
+ resolvePick(expressId) {
269
+ for (const [id, node] of this.nodes.entries()) {
270
+ if ((node.meta.expressId >>> 0) === (expressId >>> 0)) {
271
+ return { handle: { id }, meta: node.meta };
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+ /**
277
+ * Snapshot of nodes shaped for the picker — only the data the GPU
278
+ * picking pass actually needs (expressId, modelIndex, chunk vertex
279
+ * buffers + counts). Returns a fresh array; callers may iterate
280
+ * freely without worrying about mutation during a pick.
281
+ */
282
+ getPickNodes() {
283
+ const out = [];
284
+ for (const node of this.nodes.values()) {
285
+ if (node.pointCount === 0)
286
+ continue;
287
+ out.push({
288
+ expressId: node.meta.expressId,
289
+ modelIndex: node.meta.modelIndex,
290
+ chunks: node.chunks.map((c) => ({ vertexBuffer: c.vertexBuffer, pointCount: c.pointCount })),
291
+ });
292
+ }
293
+ return out;
294
+ }
295
+ }
296
+ //# sourceMappingURL=point-cloud-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-cloud-renderer.js","sourceRoot":"","sources":["../../src/pointcloud/point-cloud-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Uniform block size in bytes. Layout (Float32 indices):
3
+ * [0..15] viewProj (mat4x4)
4
+ * [16..31] model (mat4x4)
5
+ * [32..35] colorOverride (vec4)
6
+ * [36..39] colorModeAndExtras (mode, pointSizePx, heightMin, heightMax)
7
+ * [40..43] sizing (sizeMode, worldRadius, viewportW, viewportH)
8
+ * [44..47] sectionPlane (nx, ny, nz, distance)
9
+ * [48..51] flags (u32 view: x=unused, y=sectionEnabled, z=roundShape, w=unused)
10
+ */
11
+ export declare const POINT_UNIFORM_SIZE = 208;
12
+ export declare const POINT_VERTEX_BYTES = 24;
13
+ /** Number of vertices emitted per splat (two triangles forming a quad). */
14
+ export declare const POINT_QUAD_VERTS = 6;
15
+ export declare class PointRenderPipeline {
16
+ private device;
17
+ private pipeline;
18
+ private bindGroupLayout;
19
+ constructor(device: GPUDevice, colorFormat: GPUTextureFormat, depthFormat: GPUTextureFormat, sampleCount: number);
20
+ getPipeline(): GPURenderPipeline;
21
+ getBindGroupLayout(): GPUBindGroupLayout;
22
+ createUniformBuffer(): GPUBuffer;
23
+ createBindGroup(uniformBuffer: GPUBuffer): GPUBindGroup;
24
+ }
25
+ //# sourceMappingURL=point-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-pipeline.d.ts","sourceRoot":"","sources":["../../src/pointcloud/point-pipeline.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,eAAe,CAAqB;gBAG1C,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,gBAAgB,EAC7B,WAAW,EAAE,gBAAgB,EAC7B,WAAW,EAAE,MAAM;IAwDrB,WAAW,IAAI,iBAAiB;IAIhC,kBAAkB,IAAI,kBAAkB;IAIxC,mBAAmB,IAAI,SAAS;IAOhC,eAAe,CAAC,aAAa,EAAE,SAAS,GAAG,YAAY;CAMxD"}
@@ -0,0 +1,111 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * WebGPU pipeline for point cloud splatting.
6
+ *
7
+ * Each point is drawn as an instanced 6-vertex quad (`triangle-list` with
8
+ * `stepMode: 'instance'` on the vertex buffer). The vertex shader picks
9
+ * a corner offset from `vertex_index` and inflates the clip-space
10
+ * position by the active size mode. Fragment discards out-of-disc
11
+ * corners so splats render as round dots, not squares.
12
+ *
13
+ * Color attachments must match RenderPipeline's main pipeline (color +
14
+ * objectId rgba8unorm) and depth/MSAA must match too — we render into
15
+ * the same render pass.
16
+ *
17
+ * Vertex layout (24 bytes/point, per-instance):
18
+ * 0..11 vec3<f32> position
19
+ * 12..15 unorm8x4 color (r, g, b, classification-as-byte)
20
+ * 16..19 uint32 intensity (low 16 bits, 0..65535)
21
+ * 20..23 uint32 entityId (federation-aware express id)
22
+ */
23
+ import { pointShaderSource } from './point-shader.wgsl.js';
24
+ /**
25
+ * Uniform block size in bytes. Layout (Float32 indices):
26
+ * [0..15] viewProj (mat4x4)
27
+ * [16..31] model (mat4x4)
28
+ * [32..35] colorOverride (vec4)
29
+ * [36..39] colorModeAndExtras (mode, pointSizePx, heightMin, heightMax)
30
+ * [40..43] sizing (sizeMode, worldRadius, viewportW, viewportH)
31
+ * [44..47] sectionPlane (nx, ny, nz, distance)
32
+ * [48..51] flags (u32 view: x=unused, y=sectionEnabled, z=roundShape, w=unused)
33
+ */
34
+ export const POINT_UNIFORM_SIZE = 208;
35
+ export const POINT_VERTEX_BYTES = 24;
36
+ /** Number of vertices emitted per splat (two triangles forming a quad). */
37
+ export const POINT_QUAD_VERTS = 6;
38
+ export class PointRenderPipeline {
39
+ device;
40
+ pipeline;
41
+ bindGroupLayout;
42
+ constructor(device, colorFormat, depthFormat, sampleCount) {
43
+ this.device = device;
44
+ this.bindGroupLayout = device.createBindGroupLayout({
45
+ entries: [
46
+ {
47
+ binding: 0,
48
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
49
+ buffer: { type: 'uniform' },
50
+ },
51
+ ],
52
+ });
53
+ const layout = device.createPipelineLayout({
54
+ bindGroupLayouts: [this.bindGroupLayout],
55
+ });
56
+ const shader = device.createShaderModule({ code: pointShaderSource });
57
+ this.pipeline = device.createRenderPipeline({
58
+ layout,
59
+ vertex: {
60
+ module: shader,
61
+ entryPoint: 'vs_main',
62
+ buffers: [
63
+ {
64
+ arrayStride: POINT_VERTEX_BYTES,
65
+ // One source point feeds all 6 quad vertices: per-instance step.
66
+ stepMode: 'instance',
67
+ attributes: [
68
+ { shaderLocation: 0, offset: 0, format: 'float32x3' },
69
+ { shaderLocation: 1, offset: 12, format: 'unorm8x4' },
70
+ { shaderLocation: 2, offset: 16, format: 'uint32' },
71
+ { shaderLocation: 3, offset: 20, format: 'uint32' },
72
+ ],
73
+ },
74
+ ],
75
+ },
76
+ fragment: {
77
+ module: shader,
78
+ entryPoint: 'fs_main',
79
+ targets: [{ format: colorFormat }, { format: 'rgba8unorm' }],
80
+ },
81
+ primitive: {
82
+ topology: 'triangle-list',
83
+ },
84
+ depthStencil: {
85
+ format: depthFormat,
86
+ depthWriteEnabled: true,
87
+ depthCompare: 'greater', // reverse-Z, matches RenderPipeline main pipeline
88
+ },
89
+ multisample: { count: sampleCount },
90
+ });
91
+ }
92
+ getPipeline() {
93
+ return this.pipeline;
94
+ }
95
+ getBindGroupLayout() {
96
+ return this.bindGroupLayout;
97
+ }
98
+ createUniformBuffer() {
99
+ return this.device.createBuffer({
100
+ size: POINT_UNIFORM_SIZE,
101
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
102
+ });
103
+ }
104
+ createBindGroup(uniformBuffer) {
105
+ return this.device.createBindGroup({
106
+ layout: this.bindGroupLayout,
107
+ entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
108
+ });
109
+ }
110
+ }
111
+ //# sourceMappingURL=point-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-pipeline.js","sourceRoot":"","sources":["../../src/pointcloud/point-pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * WGSL for the point cloud splat pipeline.
3
+ *
4
+ * Draws each point as an instanced quad (6 verts per point, triangle-list
5
+ * topology) so we can give it a real on-screen size — `point-list` has
6
+ * no `gl_PointSize` equivalent in WebGPU, which is why a 1-px point cloud
7
+ * looks like a halftone screen as you zoom in.
8
+ *
9
+ * Three size modes (uniforms.sizing.x):
10
+ * 0 = fixed-px — every point renders at `pointSizePx` pixels
11
+ * 1 = adaptive-world — splat covers `worldRadius` metres, projected
12
+ * 2 = attenuated — adaptive but clamped to [1, pointSizePx] px
13
+ *
14
+ * Color modes (uniforms.colorModeAndExtras.x):
15
+ * 0 = per-vertex RGB, 1 = classification, 2 = intensity ramp,
16
+ * 3 = height ramp, 4 = fixed override.
17
+ *
18
+ * Round shape: fragment discards corners outside the unit disc, so
19
+ * splats render as circles (not squares) at any size > ~3 px.
20
+ */
21
+ export declare const pointShaderSource = "\n struct PointUniforms {\n viewProj: mat4x4<f32>,\n model: mat4x4<f32>,\n colorOverride: vec4<f32>,\n // x = colorMode, y = pointSizePx, z = heightMin, w = heightMax\n colorModeAndExtras: vec4<f32>,\n // x = sizeMode, y = worldRadius (m), z = viewportWidth, w = viewportHeight\n sizing: vec4<f32>,\n sectionPlane: vec4<f32>,\n // x = assetExpressId (federation-aware globalId), y = sectionEnabled,\n // z = roundShape, w = unused\n flags: vec4<u32>,\n }\n @binding(0) @group(0) var<uniform> uniforms: PointUniforms;\n\n struct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) rgbAndClass: vec4<f32>, // unorm8x4 \u2192 0..1 each\n @location(2) intensityPacked: u32, // low 16 bits = intensity\n @location(3) entityId: u32,\n }\n\n struct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) color: vec4<f32>,\n @location(1) worldPos: vec3<f32>,\n @location(2) @interpolate(flat) entityId: u32,\n @location(3) quadUv: vec2<f32>,\n }\n\n fn classification_color(class_id: u32) -> vec3<f32> {\n switch (class_id) {\n case 0u, 1u: { return vec3<f32>(0.65, 0.65, 0.65); }\n case 2u: { return vec3<f32>(0.55, 0.40, 0.25); }\n case 3u: { return vec3<f32>(0.55, 0.85, 0.45); }\n case 4u: { return vec3<f32>(0.30, 0.75, 0.30); }\n case 5u: { return vec3<f32>(0.10, 0.45, 0.15); }\n case 6u: { return vec3<f32>(0.95, 0.55, 0.20); }\n case 7u: { return vec3<f32>(0.95, 0.20, 0.20); }\n case 8u: { return vec3<f32>(0.20, 0.85, 0.95); }\n case 9u: { return vec3<f32>(0.20, 0.40, 0.95); }\n case 10u: { return vec3<f32>(0.55, 0.20, 0.85); }\n case 11u: { return vec3<f32>(0.30, 0.30, 0.30); }\n case 13u: { return vec3<f32>(0.95, 0.85, 0.20); }\n case 14u: { return vec3<f32>(0.95, 0.95, 0.50); }\n case 15u: { return vec3<f32>(0.20, 0.20, 0.55); }\n case 16u: { return vec3<f32>(0.30, 0.65, 0.65); }\n case 17u: { return vec3<f32>(0.85, 0.70, 0.50); }\n case 18u: { return vec3<f32>(0.95, 0.20, 0.20); }\n default: { return vec3<f32>(0.65, 0.65, 0.65); }\n }\n }\n\n fn height_ramp(t: f32) -> vec3<f32> {\n let s = clamp(t, 0.0, 1.0);\n if (s < 0.25) {\n let k = s / 0.25;\n return mix(vec3<f32>(0.10, 0.20, 0.85), vec3<f32>(0.10, 0.85, 0.85), k);\n } else if (s < 0.5) {\n let k = (s - 0.25) / 0.25;\n return mix(vec3<f32>(0.10, 0.85, 0.85), vec3<f32>(0.20, 0.85, 0.20), k);\n } else if (s < 0.75) {\n let k = (s - 0.5) / 0.25;\n return mix(vec3<f32>(0.20, 0.85, 0.20), vec3<f32>(0.95, 0.95, 0.20), k);\n } else {\n let k = (s - 0.75) / 0.25;\n return mix(vec3<f32>(0.95, 0.95, 0.20), vec3<f32>(0.95, 0.20, 0.10), k);\n }\n }\n\n @vertex\n fn vs_main(input: VertexInput, @builtin(vertex_index) vId: u32) -> VertexOutput {\n // Quad corners (two triangles, CCW) in unit disc coords:\n // tri 1: (-1,-1)(1,-1)(1,1)\n // tri 2: (-1,-1)(1, 1)(-1,1)\n var corners = array<vec2<f32>, 6>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>( 1.0, -1.0),\n vec2<f32>( 1.0, 1.0),\n vec2<f32>(-1.0, -1.0),\n vec2<f32>( 1.0, 1.0),\n vec2<f32>(-1.0, 1.0),\n );\n let corner = corners[vId];\n\n let worldPos4 = uniforms.model * vec4<f32>(input.position, 1.0);\n var clipPos = uniforms.viewProj * worldPos4;\n\n // Compute splat half-extent in pixels for the active size mode.\n let sizeMode = u32(uniforms.sizing.x);\n let worldRadius = uniforms.sizing.y;\n let viewport = uniforms.sizing.zw;\n let pointSizePx = uniforms.colorModeAndExtras.y;\n\n // halfPx is the splat RADIUS in pixels. The user-facing\n // pointSizePx is the diameter (\"8 px point\"), so divide by 2\n // when feeding it to the pipeline. Without this the fixed and\n // attenuated branches render splats at ~2x their requested size.\n var halfPx: f32;\n if (sizeMode == 0u) {\n halfPx = max(0.5, pointSizePx * 0.5);\n } else {\n // Project a world-radius offset to clip space, take pixel delta.\n // worldRadius is already a radius \u2014 no /2 needed here.\n let edgePos = uniforms.viewProj * (worldPos4 + vec4<f32>(worldRadius, 0.0, 0.0, 0.0));\n let centerNdcX = clipPos.x / max(abs(clipPos.w), 1e-6);\n let edgeNdcX = edgePos.x / max(abs(edgePos.w), 1e-6);\n let projectedPx = abs(edgeNdcX - centerNdcX) * 0.5 * viewport.x;\n if (sizeMode == 2u) {\n halfPx = clamp(projectedPx, 0.5, max(0.5, pointSizePx * 0.5));\n } else {\n halfPx = max(0.5, projectedPx);\n }\n }\n\n // Convert pixel offset to clip-space offset. Multiply by clipPos.w\n // because the GPU divides by w during the perspective divide.\n let halfClip = vec2<f32>(halfPx) / max(viewport, vec2<f32>(1.0)) * 2.0 * abs(clipPos.w);\n clipPos.x = clipPos.x + corner.x * halfClip.x;\n clipPos.y = clipPos.y + corner.y * halfClip.y;\n\n // Color selection\n let mode = u32(uniforms.colorModeAndExtras.x);\n let intensity01 = f32(input.intensityPacked & 0xffffu) / 65535.0;\n let classId = u32(round(input.rgbAndClass.a * 255.0));\n let heightT =\n (worldPos4.y - uniforms.colorModeAndExtras.z) /\n max(1e-6, uniforms.colorModeAndExtras.w - uniforms.colorModeAndExtras.z);\n\n var rgb: vec3<f32>;\n switch (mode) {\n case 0u: { rgb = input.rgbAndClass.rgb; }\n case 1u: { rgb = classification_color(classId); }\n case 2u: { rgb = vec3<f32>(intensity01, intensity01, intensity01); }\n case 3u: { rgb = height_ramp(heightT); }\n case 4u: { rgb = uniforms.colorOverride.rgb; }\n default: { rgb = input.rgbAndClass.rgb; }\n }\n\n var output: VertexOutput;\n output.position = clipPos;\n output.color = vec4<f32>(rgb, 1.0);\n output.worldPos = worldPos4.xyz;\n output.entityId = input.entityId;\n output.quadUv = corner;\n return output;\n }\n\n struct FragmentOutput {\n @location(0) color: vec4<f32>,\n @location(1) objectId: vec4<f32>,\n }\n\n @fragment\n fn fs_main(input: VertexOutput) -> FragmentOutput {\n // Round shape \u2014 discard corners outside the unit disc.\n if (uniforms.flags.z == 1u) {\n if (dot(input.quadUv, input.quadUv) > 1.0) {\n discard;\n }\n }\n\n // Section-plane clipping\n if (uniforms.flags.y == 1u) {\n let d = dot(uniforms.sectionPlane.xyz, input.worldPos) - uniforms.sectionPlane.w;\n if (d > 0.0) {\n discard;\n }\n }\n\n var output: FragmentOutput;\n output.color = input.color;\n // Prefer the asset-level expressId from the uniform when it's set\n // (federation needs to relabel post-stream, so we can't rely on\n // the per-vertex attribute that was baked at upload time).\n // flags.x == 0 \u2192 fall back to per-vertex value to preserve the\n // legacy contract during the upload-only rendering window.\n let id = select(input.entityId, uniforms.flags.x, uniforms.flags.x != 0u);\n output.objectId = vec4<f32>(\n f32((id >> 0u) & 0xffu) / 255.0,\n f32((id >> 8u) & 0xffu) / 255.0,\n f32((id >> 16u) & 0xffu) / 255.0,\n f32((id >> 24u) & 0xffu) / 255.0,\n );\n return output;\n }\n";
22
+ //# sourceMappingURL=point-shader.wgsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-shader.wgsl.d.ts","sourceRoot":"","sources":["../../src/pointcloud/point-shader.wgsl.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,w/OA2L7B,CAAC"}