@ifc-lite/renderer 1.22.1 → 1.23.1

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 (40) hide show
  1. package/README.md +4 -1
  2. package/dist/camera-fit-policy.d.ts +65 -0
  3. package/dist/camera-fit-policy.d.ts.map +1 -0
  4. package/dist/camera-fit-policy.js +124 -0
  5. package/dist/camera-fit-policy.js.map +1 -0
  6. package/dist/camera.d.ts +22 -0
  7. package/dist/camera.d.ts.map +1 -1
  8. package/dist/camera.js +38 -0
  9. package/dist/camera.js.map +1 -1
  10. package/dist/constants.d.ts +0 -50
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +0 -45
  13. package/dist/constants.js.map +1 -1
  14. package/dist/index.d.ts +3 -15
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -187
  17. package/dist/index.js.map +1 -1
  18. package/dist/pipeline.d.ts +0 -57
  19. package/dist/pipeline.d.ts.map +1 -1
  20. package/dist/pipeline.js +0 -377
  21. package/dist/pipeline.js.map +1 -1
  22. package/dist/scene.d.ts +1 -14
  23. package/dist/scene.d.ts.map +1 -1
  24. package/dist/scene.js +0 -27
  25. package/dist/scene.js.map +1 -1
  26. package/dist/shaders/main.wgsl.d.ts +1 -1
  27. package/dist/shaders/main.wgsl.d.ts.map +1 -1
  28. package/dist/shaders/main.wgsl.js +61 -13
  29. package/dist/shaders/main.wgsl.js.map +1 -1
  30. package/dist/shaders/symbolic-overlay.wgsl.d.ts +1 -1
  31. package/dist/shaders/symbolic-overlay.wgsl.d.ts.map +1 -1
  32. package/dist/shaders/symbolic-overlay.wgsl.js +10 -1
  33. package/dist/shaders/symbolic-overlay.wgsl.js.map +1 -1
  34. package/dist/types.d.ts +0 -18
  35. package/dist/types.d.ts.map +1 -1
  36. package/package.json +4 -4
  37. package/dist/zero-copy-uploader.d.ts +0 -145
  38. package/dist/zero-copy-uploader.d.ts.map +0 -1
  39. package/dist/zero-copy-uploader.js +0 -146
  40. package/dist/zero-copy-uploader.js.map +0 -1
package/dist/index.js CHANGED
@@ -5,8 +5,9 @@
5
5
  * @ifc-lite/renderer - WebGPU renderer
6
6
  */
7
7
  export { WebGPUDevice } from './device.js';
8
- export { RenderPipeline, InstancedRenderPipeline } from './pipeline.js';
8
+ export { RenderPipeline } from './pipeline.js';
9
9
  export { Camera } from './camera.js';
10
+ export { pickFitPolicy } from './camera-fit-policy.js';
10
11
  export { Scene } from './scene.js';
11
12
  export { Picker } from './picker.js';
12
13
  export { MathUtils } from './math.js';
@@ -27,8 +28,6 @@ export { SnapDetector, SnapType } from './snap-detector.js';
27
28
  export { BVH } from './bvh.js';
28
29
  export { FederationRegistry, federationRegistry } from './federation-registry.js';
29
30
  export * from './types.js';
30
- // Zero-copy GPU upload (new - faster, less memory)
31
- export { ZeroCopyGpuUploader, createZeroCopyUploader, } from './zero-copy-uploader.js';
32
31
  // Extracted manager classes
33
32
  export { PickingManager } from './picking-manager.js';
34
33
  export { RaycastEngine } from './raycast-engine.js';
@@ -37,13 +36,12 @@ export { PointPicker, decodePickSample } from './point-picker.js';
37
36
  export { PointCloudRenderer } from './pointcloud/point-cloud-renderer.js';
38
37
  export { PointRenderPipeline } from './pointcloud/point-pipeline.js';
39
38
  import { WebGPUDevice } from './device.js';
40
- import { RenderPipeline, InstancedRenderPipeline } from './pipeline.js';
39
+ import { RenderPipeline } from './pipeline.js';
41
40
  import { Camera } from './camera.js';
42
41
  import { Scene } from './scene.js';
43
42
  import { Picker } from './picker.js';
44
43
  import { MathUtils } from './math.js';
45
44
  import { FrustumUtils } from '@ifc-lite/spatial';
46
- import { deduplicateMeshes } from '@ifc-lite/geometry';
47
45
  import { SectionPlaneRenderer } from './section-plane.js';
48
46
  import { Section2DOverlayRenderer } from './section-2d-overlay.js';
49
47
  import { SymbolicFillPipeline, SymbolicTextPipeline, } from './symbolic-overlay-pipelines.js';
@@ -83,7 +81,6 @@ function computeBvhFingerprint(meshes) {
83
81
  export class Renderer {
84
82
  device;
85
83
  pipeline = null;
86
- instancedPipeline = null;
87
84
  camera;
88
85
  scene;
89
86
  picker = null;
@@ -170,7 +167,6 @@ export class Renderer {
170
167
  this.canvas.height = height;
171
168
  }
172
169
  this.pipeline = new RenderPipeline(this.device, width, height);
173
- this.instancedPipeline = new InstancedRenderPipeline(this.device, width, height);
174
170
  this.picker = new Picker(this.device, width, height);
175
171
  this.sectionPlaneRenderer = new SectionPlaneRenderer(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
176
172
  this.section2DOverlayRenderer = new Section2DOverlayRenderer(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
@@ -601,155 +597,6 @@ export class Renderer {
601
597
  }
602
598
  this.scene.addMesh(mesh);
603
599
  }
604
- /**
605
- * Add instanced geometry to scene
606
- * Converts InstancedGeometry from geometry package to InstancedMesh for rendering
607
- */
608
- addInstancedGeometry(geometry) {
609
- if (!this.instancedPipeline || !this.device.isInitialized()) {
610
- throw new Error('Renderer not initialized. Call init() first.');
611
- }
612
- const device = this.device.getDevice();
613
- // Upload positions and normals interleaved
614
- const vertexCount = geometry.positions.length / 3;
615
- const vertexData = new Float32Array(vertexCount * 6);
616
- for (let i = 0; i < vertexCount; i++) {
617
- vertexData[i * 6 + 0] = geometry.positions[i * 3 + 0];
618
- vertexData[i * 6 + 1] = geometry.positions[i * 3 + 1];
619
- vertexData[i * 6 + 2] = geometry.positions[i * 3 + 2];
620
- vertexData[i * 6 + 3] = geometry.normals[i * 3 + 0];
621
- vertexData[i * 6 + 4] = geometry.normals[i * 3 + 1];
622
- vertexData[i * 6 + 5] = geometry.normals[i * 3 + 2];
623
- }
624
- // Create vertex buffer with exact size needed (ensure it matches data size)
625
- const vertexBufferSize = vertexData.byteLength;
626
- const vertexBuffer = device.createBuffer({
627
- size: vertexBufferSize,
628
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
629
- });
630
- device.queue.writeBuffer(vertexBuffer, 0, vertexData);
631
- // Create index buffer
632
- const indexBuffer = device.createBuffer({
633
- size: geometry.indices.byteLength,
634
- usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
635
- });
636
- device.queue.writeBuffer(indexBuffer, 0, geometry.indices);
637
- // Create instance buffer: each instance is 80 bytes (20 floats: 16 for transform + 4 for color)
638
- const instanceCount = geometry.instance_count;
639
- const instanceData = new Float32Array(instanceCount * 20);
640
- const expressIdToInstanceIndex = new Map();
641
- for (let i = 0; i < instanceCount; i++) {
642
- const instance = geometry.get_instance(i);
643
- if (!instance)
644
- continue;
645
- const baseIdx = i * 20;
646
- // Copy transform (16 floats)
647
- instanceData.set(instance.transform, baseIdx);
648
- // Copy color (4 floats)
649
- instanceData[baseIdx + 16] = instance.color[0];
650
- instanceData[baseIdx + 17] = instance.color[1];
651
- instanceData[baseIdx + 18] = instance.color[2];
652
- instanceData[baseIdx + 19] = instance.color[3];
653
- expressIdToInstanceIndex.set(instance.expressId, i);
654
- }
655
- const instanceBuffer = device.createBuffer({
656
- size: instanceData.byteLength,
657
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
658
- });
659
- device.queue.writeBuffer(instanceBuffer, 0, instanceData);
660
- // Create and cache bind group to avoid per-frame allocation
661
- const bindGroup = this.instancedPipeline.createInstanceBindGroup(instanceBuffer);
662
- const instancedMesh = {
663
- geometryId: Number(geometry.geometryId),
664
- vertexBuffer,
665
- indexBuffer,
666
- indexCount: geometry.indices.length,
667
- instanceBuffer,
668
- instanceCount: instanceCount,
669
- expressIdToInstanceIndex,
670
- bindGroup,
671
- };
672
- this.scene.addInstancedMesh(instancedMesh);
673
- }
674
- /**
675
- * Convert MeshData array to instanced meshes for optimized rendering
676
- * Groups identical geometries and creates GPU instanced draw calls
677
- * Call this in background after initial streaming completes
678
- */
679
- convertToInstanced(meshDataArray) {
680
- if (!this.instancedPipeline || !this.device.isInitialized()) {
681
- console.warn('[Renderer] Cannot convert to instanced: renderer not initialized');
682
- return;
683
- }
684
- const instancedData = deduplicateMeshes(meshDataArray);
685
- const device = this.device.getDevice();
686
- let totalInstances = 0;
687
- for (const group of instancedData) {
688
- const vertexCount = group.positions.length / 3;
689
- const vertexData = new Float32Array(vertexCount * 6);
690
- for (let i = 0; i < vertexCount; i++) {
691
- vertexData[i * 6 + 0] = group.positions[i * 3 + 0];
692
- vertexData[i * 6 + 1] = group.positions[i * 3 + 1];
693
- vertexData[i * 6 + 2] = group.positions[i * 3 + 2];
694
- vertexData[i * 6 + 3] = group.normals[i * 3 + 0];
695
- vertexData[i * 6 + 4] = group.normals[i * 3 + 1];
696
- vertexData[i * 6 + 5] = group.normals[i * 3 + 2];
697
- }
698
- const vertexBuffer = device.createBuffer({
699
- size: vertexData.byteLength,
700
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
701
- });
702
- device.queue.writeBuffer(vertexBuffer, 0, vertexData);
703
- const indexBuffer = device.createBuffer({
704
- size: group.indices.byteLength,
705
- usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
706
- });
707
- device.queue.writeBuffer(indexBuffer, 0, group.indices);
708
- const instanceCount = group.instances.length;
709
- const instanceData = new Float32Array(instanceCount * 20);
710
- const expressIdToInstanceIndex = new Map();
711
- const identityTransform = new Float32Array([
712
- 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
713
- ]);
714
- for (let i = 0; i < instanceCount; i++) {
715
- const instance = group.instances[i];
716
- const baseIdx = i * 20;
717
- instanceData.set(identityTransform, baseIdx);
718
- instanceData[baseIdx + 16] = instance.color[0];
719
- instanceData[baseIdx + 17] = instance.color[1];
720
- instanceData[baseIdx + 18] = instance.color[2];
721
- instanceData[baseIdx + 19] = instance.color[3];
722
- expressIdToInstanceIndex.set(instance.expressId, i);
723
- }
724
- const instanceBuffer = device.createBuffer({
725
- size: instanceData.byteLength,
726
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
727
- });
728
- device.queue.writeBuffer(instanceBuffer, 0, instanceData);
729
- const bindGroup = this.instancedPipeline.createInstanceBindGroup(instanceBuffer);
730
- let geometryHash = 0;
731
- for (let i = 0; i < group.geometryHash.length; i++) {
732
- geometryHash = ((geometryHash << 5) - geometryHash) + group.geometryHash.charCodeAt(i);
733
- geometryHash = geometryHash & geometryHash;
734
- }
735
- this.scene.addInstancedMesh({
736
- geometryId: Math.abs(geometryHash),
737
- vertexBuffer,
738
- indexBuffer,
739
- indexCount: group.indices.length,
740
- instanceBuffer,
741
- instanceCount,
742
- expressIdToInstanceIndex,
743
- bindGroup,
744
- });
745
- totalInstances += instanceCount;
746
- }
747
- const regularMeshCount = this.scene.getMeshes().length;
748
- this.scene.clearRegularMeshes();
749
- console.log(`[Renderer] Converted ${meshDataArray.length} meshes to ${instancedData.length} instanced geometries ` +
750
- `(${totalInstances} total instances, ${(totalInstances / instancedData.length).toFixed(1)}x deduplication). ` +
751
- `Cleared ${regularMeshCount} regular meshes.`);
752
- }
753
600
  /**
754
601
  * Ensure all meshes have GPU resources (call after adding meshes if pipeline wasn't ready)
755
602
  */
@@ -946,9 +793,6 @@ export class Renderer {
946
793
  this.device.configureContext();
947
794
  // Also resize the depth texture immediately
948
795
  this.pipeline.resize(width, height);
949
- if (this.instancedPipeline) {
950
- this.instancedPipeline.resize(width, height);
951
- }
952
796
  }
953
797
  // Skip rendering if canvas is invalid
954
798
  if (this.canvas.width === 0 || this.canvas.height === 0) {
@@ -1078,9 +922,6 @@ export class Renderer {
1078
922
  if (this.pipeline.needsResize(this.canvas.width, this.canvas.height)) {
1079
923
  this.pipeline.resize(this.canvas.width, this.canvas.height);
1080
924
  }
1081
- if (this.instancedPipeline?.needsResize(this.canvas.width, this.canvas.height)) {
1082
- this.instancedPipeline.resize(this.canvas.width, this.canvas.height);
1083
- }
1084
925
  // Push a validation error scope to capture the EXACT error (for mobile debugging)
1085
926
  // Only do this for the first few renders to avoid performance overhead
1086
927
  const captureGpuError = this._renderCallCount <= 5;
@@ -1851,29 +1692,6 @@ export class Renderer {
1851
1692
  }
1852
1693
  }
1853
1694
  }
1854
- // Render instanced meshes (much more efficient for repeated geometry)
1855
- if (this.instancedPipeline) {
1856
- const instancedMeshes = this.scene.getInstancedMeshes();
1857
- if (instancedMeshes.length > 0) {
1858
- // Update instanced pipeline uniforms
1859
- this.instancedPipeline.updateUniforms(viewProj, sectionPlaneData
1860
- ? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
1861
- : undefined);
1862
- // Switch to instanced pipeline
1863
- pass.setPipeline(this.instancedPipeline.getPipeline());
1864
- for (const instancedMesh of instancedMeshes) {
1865
- // Use cached bind group (created at mesh upload time)
1866
- // Falls back to creating one if missing (shouldn't happen in normal flow)
1867
- const bindGroup = instancedMesh.bindGroup ??
1868
- this.instancedPipeline.createInstanceBindGroup(instancedMesh.instanceBuffer);
1869
- pass.setBindGroup(0, bindGroup);
1870
- pass.setVertexBuffer(0, instancedMesh.vertexBuffer);
1871
- pass.setIndexBuffer(instancedMesh.indexBuffer, 'uint32');
1872
- // Draw with instancing: indexCount, instanceCount
1873
- pass.drawIndexed(instancedMesh.indexCount, instancedMesh.instanceCount, 0, 0, 0);
1874
- }
1875
- }
1876
- }
1877
1695
  // Draw point clouds (IFCx inline + streamed LAS/LAZ).
1878
1696
  // Shares the depth buffer + section plane state with the mesh pipeline so
1879
1697
  // points occlude triangles and vice versa. The splat shader needs the
@@ -2427,8 +2245,6 @@ export class Renderer {
2427
2245
  // Render pipelines (textures + uniform buffers)
2428
2246
  this.pipeline?.destroy();
2429
2247
  this.pipeline = null;
2430
- this.instancedPipeline?.destroy();
2431
- this.instancedPipeline = null;
2432
2248
  // Picker GPU resources
2433
2249
  this.picker?.destroy();
2434
2250
  this.picker = null;