@d5techs/3dgs-lib 1.4.80 → 1.4.82

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/dist/3dgs-lib.cjs CHANGED
@@ -17577,11 +17577,12 @@ class SphereSelection {
17577
17577
  this.parent = parent;
17578
17578
  this.callbacks = callbacks;
17579
17579
  this.toolbar = document.createElement("div");
17580
- this.toolbar.className = "volume-select-toolbar";
17580
+ this.toolbar.className = "select-toolbar";
17581
17581
  this.toolbar.style.cssText = `
17582
- position:absolute; bottom:90px; left:50%; transform:translateX(-50%);
17583
- display:none; z-index:20; background:rgba(30,30,30,0.92);
17584
- border-radius:8px; padding:6px 10px; gap:6px;
17582
+ position:absolute; bottom:100px; left:50%; transform:translateX(-50%);
17583
+ height:54px; display:none; z-index:20;
17584
+ padding:0 8px; border-radius:8px;
17585
+ background:rgba(30,30,30,0.92);
17585
17586
  align-items:center; font-size:13px; color:#ddd;
17586
17587
  backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
17587
17588
  box-shadow:0 2px 12px rgba(0,0,0,0.4);
@@ -17592,15 +17593,15 @@ class SphereSelection {
17592
17593
  const btn = document.createElement("button");
17593
17594
  btn.textContent = label2;
17594
17595
  btn.style.cssText = `
17595
- padding:4px 12px; border:1px solid #555; border-radius:4px;
17596
- background:#333; color:#ddd; cursor:pointer; font-size:13px;
17596
+ height:38px; padding:0 16px; border:none; border-radius:2px;
17597
+ background:#444; color:#ddd; cursor:pointer; font-size:13px;
17597
17598
  transition: background 0.15s;
17598
17599
  `;
17599
17600
  btn.addEventListener("mouseenter", () => {
17600
17601
  btn.style.background = "#555";
17601
17602
  });
17602
17603
  btn.addEventListener("mouseleave", () => {
17603
- btn.style.background = "#333";
17604
+ btn.style.background = "#444";
17604
17605
  });
17605
17606
  btn.addEventListener("pointerdown", (e) => {
17606
17607
  e.stopPropagation();
@@ -17612,27 +17613,27 @@ class SphereSelection {
17612
17613
  const addBtn = mkBtn("Add", "add");
17613
17614
  const removeBtn = mkBtn("Remove", "remove");
17614
17615
  const inputWrap = document.createElement("span");
17615
- inputWrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
17616
+ inputWrap.style.cssText = "display:inline-flex; align-items:center; gap:4px; margin-left:4px;";
17617
+ const label = document.createElement("span");
17618
+ label.textContent = "Radius";
17619
+ label.style.cssText = "color:#888; font-size:11px;";
17616
17620
  const input = document.createElement("input");
17617
17621
  input.type = "number";
17618
- input.min = "1";
17619
- input.step = "1";
17620
- input.value = String(this._radius);
17622
+ input.min = "0.01";
17623
+ input.step = "0.01";
17624
+ input.value = this._radius.toFixed(2);
17621
17625
  input.style.cssText = `
17622
- width:40px; padding:3px 4px; border:1px solid #555; border-radius:4px;
17626
+ width:80px; padding:4px 6px; border:1px solid #555; border-radius:4px;
17623
17627
  background:#222; color:#ddd; font-size:12px; text-align:center;
17624
17628
  `;
17625
- const label = document.createElement("span");
17626
- label.textContent = "Radius";
17627
- label.style.cssText = "color:#888; font-size:11px;";
17628
17629
  input.addEventListener("change", () => {
17629
- const v = Math.max(1, Math.round(parseFloat(input.value) || 1));
17630
- input.value = String(v);
17630
+ const v = Math.max(0.01, parseFloat(input.value) || 0.01);
17631
+ input.value = v.toFixed(2);
17631
17632
  this._radius = v;
17632
17633
  this.callbacks.onRadiusChanged(v);
17633
17634
  });
17634
- inputWrap.appendChild(input);
17635
17635
  inputWrap.appendChild(label);
17636
+ inputWrap.appendChild(input);
17636
17637
  this.radiusInput = input;
17637
17638
  this.toolbar.appendChild(setBtn);
17638
17639
  this.toolbar.appendChild(addBtn);
@@ -17644,8 +17645,8 @@ class SphereSelection {
17644
17645
  return this._radius;
17645
17646
  }
17646
17647
  setRadius(radius) {
17647
- this._radius = Math.round(radius);
17648
- this.radiusInput.value = String(this._radius);
17648
+ this._radius = radius;
17649
+ this.radiusInput.value = radius.toFixed(2);
17649
17650
  }
17650
17651
  activate() {
17651
17652
  this.toolbar.style.display = "flex";
@@ -17668,11 +17669,12 @@ class BoxSelection {
17668
17669
  this.parent = parent;
17669
17670
  this.callbacks = callbacks;
17670
17671
  this.toolbar = document.createElement("div");
17671
- this.toolbar.className = "volume-select-toolbar";
17672
+ this.toolbar.className = "select-toolbar";
17672
17673
  this.toolbar.style.cssText = `
17673
- position:absolute; bottom:90px; left:50%; transform:translateX(-50%);
17674
- display:none; z-index:20; background:rgba(30,30,30,0.92);
17675
- border-radius:8px; padding:6px 10px; gap:6px;
17674
+ position:absolute; bottom:100px; left:50%; transform:translateX(-50%);
17675
+ height:54px; display:none; z-index:20;
17676
+ padding:0 8px; border-radius:8px;
17677
+ background:rgba(30,30,30,0.92);
17676
17678
  align-items:center; font-size:13px; color:#ddd;
17677
17679
  backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
17678
17680
  box-shadow:0 2px 12px rgba(0,0,0,0.4);
@@ -17683,15 +17685,15 @@ class BoxSelection {
17683
17685
  const btn = document.createElement("button");
17684
17686
  btn.textContent = label;
17685
17687
  btn.style.cssText = `
17686
- padding:4px 12px; border:1px solid #555; border-radius:4px;
17687
- background:#333; color:#ddd; cursor:pointer; font-size:13px;
17688
+ height:38px; padding:0 16px; border:none; border-radius:2px;
17689
+ background:#444; color:#ddd; cursor:pointer; font-size:13px;
17688
17690
  transition: background 0.15s;
17689
17691
  `;
17690
17692
  btn.addEventListener("mouseenter", () => {
17691
17693
  btn.style.background = "#555";
17692
17694
  });
17693
17695
  btn.addEventListener("mouseleave", () => {
17694
- btn.style.background = "#333";
17696
+ btn.style.background = "#444";
17695
17697
  });
17696
17698
  btn.addEventListener("pointerdown", (e) => {
17697
17699
  e.stopPropagation();
@@ -17701,26 +17703,26 @@ class BoxSelection {
17701
17703
  };
17702
17704
  const mkInput = (placeholder, initial, onChange) => {
17703
17705
  const wrap = document.createElement("span");
17704
- wrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
17706
+ wrap.style.cssText = "display:inline-flex; align-items:center; gap:4px; margin-left:4px;";
17707
+ const label = document.createElement("span");
17708
+ label.textContent = placeholder;
17709
+ label.style.cssText = "color:#888; font-size:11px;";
17705
17710
  const input = document.createElement("input");
17706
17711
  input.type = "number";
17707
- input.min = "1";
17708
- input.step = "1";
17709
- input.value = String(Math.round(initial));
17712
+ input.min = "0.01";
17713
+ input.step = "0.01";
17714
+ input.value = initial.toFixed(2);
17710
17715
  input.style.cssText = `
17711
- width:40px; padding:3px 4px; border:1px solid #555; border-radius:4px;
17716
+ width:80px; padding:4px 6px; border:1px solid #555; border-radius:4px;
17712
17717
  background:#222; color:#ddd; font-size:12px; text-align:center;
17713
17718
  `;
17714
- const label = document.createElement("span");
17715
- label.textContent = placeholder;
17716
- label.style.cssText = "color:#888; font-size:11px;";
17717
17719
  input.addEventListener("change", () => {
17718
- const v = Math.max(1, Math.round(parseFloat(input.value) || 1));
17719
- input.value = String(v);
17720
+ const v = Math.max(0.01, parseFloat(input.value) || 0.01);
17721
+ input.value = v.toFixed(2);
17720
17722
  onChange(v);
17721
17723
  });
17722
- wrap.appendChild(input);
17723
17724
  wrap.appendChild(label);
17725
+ wrap.appendChild(input);
17724
17726
  return { wrap, input };
17725
17727
  };
17726
17728
  const setBtn = mkBtn("Set", "set");
@@ -17759,12 +17761,12 @@ class BoxSelection {
17759
17761
  return this._lenZ;
17760
17762
  }
17761
17763
  setDimensions(lenX, lenY, lenZ) {
17762
- this._lenX = Math.round(lenX);
17763
- this._lenY = Math.round(lenY);
17764
- this._lenZ = Math.round(lenZ);
17765
- this.inputX.value = String(this._lenX);
17766
- this.inputY.value = String(this._lenY);
17767
- this.inputZ.value = String(this._lenZ);
17764
+ this._lenX = lenX;
17765
+ this._lenY = lenY;
17766
+ this._lenZ = lenZ;
17767
+ this.inputX.value = lenX.toFixed(2);
17768
+ this.inputY.value = lenY.toFixed(2);
17769
+ this.inputZ.value = lenZ.toFixed(2);
17768
17770
  }
17769
17771
  activate() {
17770
17772
  this.toolbar.style.display = "flex";
@@ -17877,29 +17879,311 @@ function exportEditedPLY(positions, scales, rotations, colors, opacities, shCoef
17877
17879
  }
17878
17880
  return buffer;
17879
17881
  }
17880
- const SEG = 64;
17881
- const LAT_COUNT = 8;
17882
- const LON_COUNT = 12;
17883
- const BOX_GRID_SUBDIV = 4;
17882
+ const BOX_VERTS = new Float32Array([
17883
+ -0.5,
17884
+ -0.5,
17885
+ 0.5,
17886
+ 0.5,
17887
+ -0.5,
17888
+ 0.5,
17889
+ 0.5,
17890
+ 0.5,
17891
+ 0.5,
17892
+ -0.5,
17893
+ -0.5,
17894
+ 0.5,
17895
+ 0.5,
17896
+ 0.5,
17897
+ 0.5,
17898
+ -0.5,
17899
+ 0.5,
17900
+ 0.5,
17901
+ 0.5,
17902
+ -0.5,
17903
+ -0.5,
17904
+ -0.5,
17905
+ -0.5,
17906
+ -0.5,
17907
+ -0.5,
17908
+ 0.5,
17909
+ -0.5,
17910
+ 0.5,
17911
+ -0.5,
17912
+ -0.5,
17913
+ -0.5,
17914
+ 0.5,
17915
+ -0.5,
17916
+ 0.5,
17917
+ 0.5,
17918
+ -0.5,
17919
+ -0.5,
17920
+ 0.5,
17921
+ 0.5,
17922
+ 0.5,
17923
+ 0.5,
17924
+ 0.5,
17925
+ 0.5,
17926
+ 0.5,
17927
+ -0.5,
17928
+ -0.5,
17929
+ 0.5,
17930
+ 0.5,
17931
+ 0.5,
17932
+ 0.5,
17933
+ -0.5,
17934
+ -0.5,
17935
+ 0.5,
17936
+ -0.5,
17937
+ -0.5,
17938
+ -0.5,
17939
+ -0.5,
17940
+ 0.5,
17941
+ -0.5,
17942
+ -0.5,
17943
+ 0.5,
17944
+ -0.5,
17945
+ 0.5,
17946
+ -0.5,
17947
+ -0.5,
17948
+ -0.5,
17949
+ 0.5,
17950
+ -0.5,
17951
+ 0.5,
17952
+ -0.5,
17953
+ -0.5,
17954
+ 0.5,
17955
+ 0.5,
17956
+ -0.5,
17957
+ 0.5,
17958
+ 0.5,
17959
+ -0.5,
17960
+ -0.5,
17961
+ 0.5,
17962
+ 0.5,
17963
+ -0.5,
17964
+ 0.5,
17965
+ -0.5,
17966
+ 0.5,
17967
+ 0.5,
17968
+ 0.5,
17969
+ -0.5,
17970
+ 0.5,
17971
+ 0.5,
17972
+ 0.5,
17973
+ -0.5,
17974
+ -0.5,
17975
+ -0.5,
17976
+ -0.5,
17977
+ -0.5,
17978
+ 0.5,
17979
+ -0.5,
17980
+ 0.5,
17981
+ 0.5,
17982
+ -0.5,
17983
+ -0.5,
17984
+ -0.5,
17985
+ -0.5,
17986
+ 0.5,
17987
+ 0.5,
17988
+ -0.5,
17989
+ 0.5,
17990
+ -0.5
17991
+ ]);
17992
+ const COMMON_WGSL = (
17993
+ /* wgsl */
17994
+ `
17995
+ struct Uniforms {
17996
+ viewProjMatrix: mat4x4<f32>,
17997
+ modelMatrix: mat4x4<f32>,
17998
+ nearOrigin: vec4<f32>,
17999
+ nearX: vec4<f32>,
18000
+ nearY: vec4<f32>,
18001
+ farOrigin: vec4<f32>,
18002
+ farX: vec4<f32>,
18003
+ farY: vec4<f32>,
18004
+ targetSize: vec4<f32>,
18005
+ shapeP0: vec4<f32>,
18006
+ shapeP1: vec4<f32>,
18007
+ }
18008
+ @group(0) @binding(0) var<uniform> u: Uniforms;
18009
+
18010
+ struct VO { @builtin(position) position: vec4<f32> }
18011
+ struct FO {
18012
+ @location(0) color: vec4<f32>,
18013
+ @builtin(frag_depth) depth: f32,
18014
+ }
18015
+
18016
+ @vertex fn vs(@location(0) pos: vec3<f32>) -> VO {
18017
+ var o: VO;
18018
+ o.position = u.viewProjMatrix * u.modelMatrix * vec4(pos, 1.0);
18019
+ return o;
18020
+ }
18021
+
18022
+ fn interleavedGradientNoise(p: vec2<f32>) -> f32 {
18023
+ return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715))));
18024
+ }
18025
+
18026
+ fn writeDepth(alpha: f32, fc: vec2<f32>) -> bool {
18027
+ return alpha > interleavedGradientNoise(fc);
18028
+ }
18029
+
18030
+ fn calcDepth(wp: vec3<f32>) -> f32 {
18031
+ let v = u.viewProjMatrix * vec4(wp, 1.0);
18032
+ return clamp(v.z / v.w, 0.0, 1.0);
18033
+ }
18034
+
18035
+ fn reconstructRay(fragPos: vec4<f32>) -> array<vec3<f32>, 2> {
18036
+ let clip = vec2(fragPos.x / u.targetSize.x, 1.0 - fragPos.y / u.targetSize.y);
18037
+ let wNear = u.nearOrigin.xyz + u.nearX.xyz * clip.x + u.nearY.xyz * clip.y;
18038
+ let wFar = u.farOrigin.xyz + u.farX.xyz * clip.x + u.farY.xyz * clip.y;
18039
+ return array(wNear, normalize(wFar - wNear));
18040
+ }
18041
+ `
18042
+ );
18043
+ const BOX_FRAG_WGSL = (
18044
+ /* wgsl */
18045
+ `
18046
+ fn intersectBox(pos: vec3<f32>, dir: vec3<f32>, cen: vec3<f32>, halfLen: vec3<f32>) -> vec4<f32> {
18047
+ let vx = select(0.0, 1.0, dir.x != 0.0);
18048
+ let vy = select(0.0, 1.0, dir.y != 0.0);
18049
+ let vz = select(0.0, 1.0, dir.z != 0.0);
18050
+ let valid = vec3<f32>(vx, vy, vz);
18051
+ let m = vec3<f32>(
18052
+ select(0.0, sign(dir.x) / abs(dir.x), dir.x != 0.0),
18053
+ select(0.0, sign(dir.y) / abs(dir.y), dir.y != 0.0),
18054
+ select(0.0, sign(dir.z) / abs(dir.z), dir.z != 0.0),
18055
+ );
18056
+ let n = m * (pos - cen);
18057
+ let k = abs(m) * halfLen;
18058
+ var v0 = -n - k;
18059
+ var v1 = -n + k;
18060
+ v0 = mix(vec3(-1e10), v0, valid);
18061
+ v1 = mix(vec3( 1e10), v1, valid);
18062
+
18063
+ var axis0: i32; var axis1: i32;
18064
+ if v0.x > v0.y { axis0 = select(2, 0, v0.x > v0.z); }
18065
+ else { axis0 = select(2, 1, v0.y > v0.z); }
18066
+ if v1.x < v1.y { axis1 = select(2, 0, v1.x < v1.z); }
18067
+ else { axis1 = select(2, 1, v1.y < v1.z); }
18068
+
18069
+ let t0 = select(select(v0.z, v0.y, axis0==1), v0.x, axis0==0);
18070
+ let t1 = select(select(v1.z, v1.y, axis1==1), v1.x, axis1==0);
18071
+ if t0 > t1 || t1 < 0.0 { return vec4(-1.0); }
18072
+ return vec4(t0, t1, f32(axis0), f32(axis1));
18073
+ }
18074
+
18075
+ fn strips(pos: vec3<f32>, axis: i32) -> bool {
18076
+ let f = fract(pos * 2.0 + vec3(0.015));
18077
+ let b = f < vec3(0.03);
18078
+ if axis == 0 { return b.y || b.z; }
18079
+ if axis == 1 { return b.x || b.z; }
18080
+ return b.x || b.y;
18081
+ }
18082
+
18083
+ @fragment fn fs(i: VO) -> FO {
18084
+ var o: FO;
18085
+ o.color = vec4(0.0);
18086
+ o.depth = 1.0;
18087
+
18088
+ let ray = reconstructRay(i.position);
18089
+ let hit = intersectBox(ray[0], ray[1], u.shapeP0.xyz, u.shapeP1.xyz);
18090
+ if hit.x < 0.0 { discard; return o; }
18091
+
18092
+ let t0 = hit.x; let t1 = hit.y;
18093
+ let a0 = i32(hit.z); let a1 = i32(hit.w);
18094
+ let frontPos = ray[0] + ray[1] * t0;
18095
+ let backPos = ray[0] + ray[1] * t1;
18096
+ let front = t0 > 0.0 && strips(frontPos - u.shapeP0.xyz, a0);
18097
+ let back = strips(backPos - u.shapeP0.xyz, a1);
18098
+
18099
+ if front {
18100
+ o.color = vec4(1.0, 1.0, 1.0, 0.6);
18101
+ o.depth = select(1.0, calcDepth(frontPos), writeDepth(0.6, i.position.xy));
18102
+ return o;
18103
+ }
18104
+ if back {
18105
+ o.color = vec4(0.0, 0.0, 0.0, 0.6);
18106
+ o.depth = select(1.0, calcDepth(backPos), writeDepth(0.6, i.position.xy));
18107
+ return o;
18108
+ }
18109
+ discard;
18110
+ return o;
18111
+ }
18112
+ `
18113
+ );
18114
+ const SPHERE_FRAG_WGSL = (
18115
+ /* wgsl */
18116
+ `
18117
+ fn intersectSphere(pos: vec3<f32>, dir: vec3<f32>, sph: vec4<f32>) -> vec2<f32> {
18118
+ let L = sph.xyz - pos;
18119
+ let tca = dot(L, dir);
18120
+ let d2 = sph.w * sph.w - (dot(L, L) - tca * tca);
18121
+ if d2 <= 0.0 { return vec2(-1.0); }
18122
+ let thc = sqrt(d2);
18123
+ let t0 = tca - thc;
18124
+ let t1 = tca + thc;
18125
+ if t1 <= 0.0 { return vec2(-1.0); }
18126
+ return vec2(t0, t1);
18127
+ }
18128
+
18129
+ fn calcAzimuthElev(dir: vec3<f32>) -> vec2<f32> {
18130
+ let azimuth = atan2(dir.z, dir.x);
18131
+ let elev = asin(clamp(dir.y, -1.0, 1.0));
18132
+ return vec2(azimuth, elev) * 180.0 / 3.14159265;
18133
+ }
18134
+
18135
+ fn sphereStrips(lp: vec3<f32>, radius: f32) -> bool {
18136
+ let ae = calcAzimuthElev(normalize(lp));
18137
+ let spacing = 180.0 / (2.0 * 3.14159265 * radius);
18138
+ let sz = 0.03;
18139
+ return fract(ae.x / spacing) < sz || fract(ae.y / spacing) < sz;
18140
+ }
18141
+
18142
+ @fragment fn fs(i: VO) -> FO {
18143
+ var o: FO;
18144
+ o.color = vec4(0.0);
18145
+ o.depth = 1.0;
18146
+
18147
+ let sph = u.shapeP0;
18148
+ let ray = reconstructRay(i.position);
18149
+ let hit = intersectSphere(ray[0], ray[1], sph);
18150
+ if hit.x < 0.0 && hit.y < 0.0 { discard; return o; }
18151
+
18152
+ let t0 = hit.x; let t1 = hit.y;
18153
+ let frontPos = ray[0] + ray[1] * t0;
18154
+ let backPos = ray[0] + ray[1] * t1;
18155
+ let front = t0 > 0.0 && sphereStrips(frontPos - sph.xyz, sph.w);
18156
+ let back = sphereStrips(backPos - sph.xyz, sph.w);
18157
+
18158
+ if front {
18159
+ o.color = vec4(1.0, 1.0, 1.0, 0.6);
18160
+ o.depth = select(1.0, calcDepth(frontPos), writeDepth(0.6, i.position.xy));
18161
+ return o;
18162
+ }
18163
+ if back {
18164
+ o.color = vec4(0.0, 0.0, 0.0, 0.6);
18165
+ o.depth = select(1.0, calcDepth(backPos), writeDepth(0.6, i.position.xy));
18166
+ return o;
18167
+ }
18168
+ discard;
18169
+ return o;
18170
+ }
18171
+ `
18172
+ );
18173
+ const UNIFORM_SIZE = 272;
17884
18174
  class SelectionVolumeRenderer {
17885
18175
  constructor(renderer, camera) {
17886
18176
  __publicField(this, "renderer");
17887
18177
  __publicField(this, "camera");
17888
- __publicField(this, "frontPipeline", null);
17889
- __publicField(this, "behindPipeline", null);
18178
+ __publicField(this, "boxPipeline", null);
18179
+ __publicField(this, "spherePipeline", null);
17890
18180
  __publicField(this, "uniformBuffer", null);
17891
18181
  __publicField(this, "bindGroup", null);
17892
18182
  __publicField(this, "vertexBuffer", null);
17893
18183
  __publicField(this, "_volume", { type: null, center: [0, 0, 0], dimensions: [2, 2, 2] });
17894
- __publicField(this, "vertexCount", 0);
17895
- __publicField(this, "frontColor", [0.4, 0.9, 1]);
17896
18184
  this.renderer = renderer;
17897
18185
  this.camera = camera;
17898
- this.createPipelines();
17899
- this.vertexBuffer = this.renderer.device.createBuffer({
17900
- size: 65536,
17901
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
17902
- });
18186
+ this.createResources();
17903
18187
  }
17904
18188
  get volume() {
17905
18189
  return this._volume;
@@ -17915,225 +18199,187 @@ class SelectionVolumeRenderer {
17915
18199
  setDimensions(a, b, c) {
17916
18200
  this._volume.dimensions = [a, b, c];
17917
18201
  }
17918
- createPipelines() {
18202
+ createResources() {
17919
18203
  const device = this.renderer.device;
17920
- const shaderCode = (
17921
- /* wgsl */
17922
- `
17923
- struct Uniforms {
17924
- viewMatrix: mat4x4<f32>,
17925
- projMatrix: mat4x4<f32>,
17926
- modelMatrix: mat4x4<f32>,
17927
- }
17928
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
17929
-
17930
- struct VI { @location(0) position: vec3<f32>, @location(1) color: vec3<f32> }
17931
- struct VO { @builtin(position) position: vec4<f32>, @location(0) color: vec3<f32> }
17932
-
17933
- @vertex fn vs(i: VI) -> VO {
17934
- var o: VO;
17935
- let worldPos = uniforms.modelMatrix * vec4(i.position, 1.0);
17936
- let viewPos = uniforms.viewMatrix * worldPos;
17937
- o.position = uniforms.projMatrix * viewPos;
17938
- o.color = i.color;
17939
- return o;
17940
- }
17941
- @fragment fn fs(i: VO) -> @location(0) vec4<f32> {
17942
- return vec4(i.color, 0.7);
17943
- }
17944
- @fragment fn fsBehind(i: VO) -> @location(0) vec4<f32> {
17945
- return vec4(i.color * 0.35, 0.2);
17946
- }
17947
- `
17948
- );
17949
- const sm = device.createShaderModule({ code: shaderCode });
17950
- this.uniformBuffer = device.createBuffer({ size: 192, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
18204
+ this.vertexBuffer = device.createBuffer({
18205
+ size: BOX_VERTS.byteLength,
18206
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
18207
+ });
18208
+ device.queue.writeBuffer(this.vertexBuffer, 0, BOX_VERTS);
18209
+ this.uniformBuffer = device.createBuffer({
18210
+ size: UNIFORM_SIZE,
18211
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
18212
+ });
17951
18213
  const bgl = device.createBindGroupLayout({
17952
- entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
18214
+ entries: [{
18215
+ binding: 0,
18216
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18217
+ buffer: { type: "uniform" }
18218
+ }]
18219
+ });
18220
+ this.bindGroup = device.createBindGroup({
18221
+ layout: bgl,
18222
+ entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }]
17953
18223
  });
17954
- this.bindGroup = device.createBindGroup({ layout: bgl, entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }] });
17955
18224
  const layout = device.createPipelineLayout({ bindGroupLayouts: [bgl] });
17956
- const vertexState = {
17957
- module: sm,
18225
+ const mkVertexState = (mod) => ({
18226
+ module: mod,
17958
18227
  entryPoint: "vs",
17959
18228
  buffers: [{
17960
- arrayStride: 24,
17961
- attributes: [
17962
- { shaderLocation: 0, offset: 0, format: "float32x3" },
17963
- { shaderLocation: 1, offset: 12, format: "float32x3" }
17964
- ]
18229
+ arrayStride: 12,
18230
+ attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
17965
18231
  }]
17966
- };
17967
- const blendState = {
18232
+ });
18233
+ const blend = {
17968
18234
  color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
17969
18235
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
17970
18236
  };
17971
- this.frontPipeline = device.createRenderPipeline({
18237
+ const depthStencil = {
18238
+ format: this.renderer.depthFormat,
18239
+ depthWriteEnabled: true,
18240
+ depthCompare: "less-equal"
18241
+ };
18242
+ const boxMod = device.createShaderModule({ code: COMMON_WGSL + BOX_FRAG_WGSL });
18243
+ this.boxPipeline = device.createRenderPipeline({
17972
18244
  layout,
17973
- vertex: vertexState,
18245
+ vertex: mkVertexState(boxMod),
17974
18246
  fragment: {
17975
- module: sm,
18247
+ module: boxMod,
17976
18248
  entryPoint: "fs",
17977
- targets: [{ format: this.renderer.format, blend: blendState }]
18249
+ targets: [{ format: this.renderer.format, blend }]
17978
18250
  },
17979
- primitive: { topology: "line-list", cullMode: "none" },
17980
- depthStencil: { format: this.renderer.depthFormat, depthWriteEnabled: false, depthCompare: "less-equal" }
18251
+ primitive: { topology: "triangle-list", cullMode: "front" },
18252
+ depthStencil
17981
18253
  });
17982
- this.behindPipeline = device.createRenderPipeline({
18254
+ const sphereMod = device.createShaderModule({ code: COMMON_WGSL + SPHERE_FRAG_WGSL });
18255
+ this.spherePipeline = device.createRenderPipeline({
17983
18256
  layout,
17984
- vertex: vertexState,
18257
+ vertex: mkVertexState(sphereMod),
17985
18258
  fragment: {
17986
- module: sm,
17987
- entryPoint: "fsBehind",
17988
- targets: [{ format: this.renderer.format, blend: blendState }]
18259
+ module: sphereMod,
18260
+ entryPoint: "fs",
18261
+ targets: [{ format: this.renderer.format, blend }]
17989
18262
  },
17990
- primitive: { topology: "line-list", cullMode: "none" },
17991
- depthStencil: { format: this.renderer.depthFormat, depthWriteEnabled: false, depthCompare: "greater" }
18263
+ primitive: { topology: "triangle-list", cullMode: "front" },
18264
+ depthStencil
17992
18265
  });
17993
18266
  }
17994
- // ========== Box: 12 edges + grid lines on all 6 faces ==========
17995
- generateBoxVertices(color) {
17996
- const [lx, ly, lz] = this._volume.dimensions;
17997
- const hx = lx / 2, hy = ly / 2, hz = lz / 2;
17998
- const [r, g, b] = color;
17999
- const v = [];
18000
- const line = (x12, y12, z12, x2, y2, z2) => {
18001
- v.push(x12, y12, z12, r, g, b, x2, y2, z2, r, g, b);
18002
- };
18003
- const x0 = -hx, x1 = hx;
18004
- const y0 = -hy, y1 = hy;
18005
- const z0 = -hz, z1 = hz;
18006
- line(x0, y0, z0, x1, y0, z0);
18007
- line(x1, y0, z0, x1, y0, z1);
18008
- line(x1, y0, z1, x0, y0, z1);
18009
- line(x0, y0, z1, x0, y0, z0);
18010
- line(x0, y1, z0, x1, y1, z0);
18011
- line(x1, y1, z0, x1, y1, z1);
18012
- line(x1, y1, z1, x0, y1, z1);
18013
- line(x0, y1, z1, x0, y1, z0);
18014
- line(x0, y0, z0, x0, y1, z0);
18015
- line(x1, y0, z0, x1, y1, z0);
18016
- line(x1, y0, z1, x1, y1, z1);
18017
- line(x0, y0, z1, x0, y1, z1);
18018
- const n = BOX_GRID_SUBDIV;
18019
- for (let i = 1; i < n; i++) {
18020
- const t = i / n;
18021
- const px = x0 + (x1 - x0) * t;
18022
- const pz = z0 + (z1 - z0) * t;
18023
- line(px, y0, z0, px, y0, z1);
18024
- line(px, y1, z0, px, y1, z1);
18025
- line(x0, y0, pz, x1, y0, pz);
18026
- line(x0, y1, pz, x1, y1, pz);
18027
- }
18028
- for (let i = 1; i < n; i++) {
18029
- const t = i / n;
18030
- const px = x0 + (x1 - x0) * t;
18031
- const py = y0 + (y1 - y0) * t;
18032
- line(px, y0, z0, px, y1, z0);
18033
- line(px, y0, z1, px, y1, z1);
18034
- line(x0, py, z0, x1, py, z0);
18035
- line(x0, py, z1, x1, py, z1);
18036
- }
18037
- for (let i = 1; i < n; i++) {
18038
- const t = i / n;
18039
- const py = y0 + (y1 - y0) * t;
18040
- const pz = z0 + (z1 - z0) * t;
18041
- line(x0, py, z0, x0, py, z1);
18042
- line(x1, py, z0, x1, py, z1);
18043
- line(x0, y0, pz, x0, y1, pz);
18044
- line(x1, y0, pz, x1, y1, pz);
18045
- }
18046
- return new Float32Array(v);
18047
- }
18048
- // ========== Sphere: latitude + longitude rings ==========
18049
- generateSphereVertices(color) {
18050
- const radius = this._volume.dimensions[0];
18051
- const [r, g, b] = color;
18052
- const v = [];
18053
- const addRing = (centerY, ringRadius) => {
18054
- for (let i = 0; i < SEG; i++) {
18055
- const a0 = i / SEG * Math.PI * 2;
18056
- const a1 = (i + 1) / SEG * Math.PI * 2;
18057
- v.push(
18058
- Math.cos(a0) * ringRadius,
18059
- centerY,
18060
- Math.sin(a0) * ringRadius,
18061
- r,
18062
- g,
18063
- b,
18064
- Math.cos(a1) * ringRadius,
18065
- centerY,
18066
- Math.sin(a1) * ringRadius,
18067
- r,
18068
- g,
18069
- b
18070
- );
18071
- }
18072
- };
18073
- for (let i = 0; i <= LAT_COUNT; i++) {
18074
- const phi = -Math.PI / 2 + i / LAT_COUNT * Math.PI;
18075
- const y = Math.sin(phi) * radius;
18076
- const ringR = Math.cos(phi) * radius;
18077
- if (ringR < 1e-3) continue;
18078
- addRing(y, ringR);
18079
- }
18080
- for (let j = 0; j < LON_COUNT; j++) {
18081
- const theta = j / LON_COUNT * Math.PI;
18082
- for (let i = 0; i < SEG; i++) {
18083
- const phi0 = i / SEG * Math.PI * 2;
18084
- const phi1 = (i + 1) / SEG * Math.PI * 2;
18085
- v.push(
18086
- Math.sin(phi0) * Math.sin(theta) * radius,
18087
- Math.cos(phi0) * radius,
18088
- Math.sin(phi0) * Math.cos(theta) * radius,
18089
- r,
18090
- g,
18091
- b,
18092
- Math.sin(phi1) * Math.sin(theta) * radius,
18093
- Math.cos(phi1) * radius,
18094
- Math.sin(phi1) * Math.cos(theta) * radius,
18095
- r,
18096
- g,
18097
- b
18098
- );
18099
- }
18100
- }
18101
- return new Float32Array(v);
18267
+ // ---------- Frustum corners ----------
18268
+ computeFrustumUniforms(buf) {
18269
+ const cam = this.camera;
18270
+ const vm = cam.viewMatrix;
18271
+ const right = [vm[0], vm[4], vm[8]];
18272
+ const up = [vm[1], vm[5], vm[9]];
18273
+ const zAxis = [vm[2], vm[6], vm[10]];
18274
+ const eye = cam.position;
18275
+ const d = 100;
18276
+ const halfH = d * Math.tan(cam.fov / 2);
18277
+ const halfW = halfH * cam.aspect;
18278
+ const fcx = eye[0] - d * zAxis[0];
18279
+ const fcy = eye[1] - d * zAxis[1];
18280
+ const fcz = eye[2] - d * zAxis[2];
18281
+ buf[0] = eye[0];
18282
+ buf[1] = eye[1];
18283
+ buf[2] = eye[2];
18284
+ buf[3] = 0;
18285
+ buf[4] = 0;
18286
+ buf[5] = 0;
18287
+ buf[6] = 0;
18288
+ buf[7] = 0;
18289
+ buf[8] = 0;
18290
+ buf[9] = 0;
18291
+ buf[10] = 0;
18292
+ buf[11] = 0;
18293
+ buf[12] = fcx - halfW * right[0] - halfH * up[0];
18294
+ buf[13] = fcy - halfW * right[1] - halfH * up[1];
18295
+ buf[14] = fcz - halfW * right[2] - halfH * up[2];
18296
+ buf[15] = 0;
18297
+ buf[16] = 2 * halfW * right[0];
18298
+ buf[17] = 2 * halfW * right[1];
18299
+ buf[18] = 2 * halfW * right[2];
18300
+ buf[19] = 0;
18301
+ buf[20] = 2 * halfH * up[0];
18302
+ buf[21] = 2 * halfH * up[1];
18303
+ buf[22] = 2 * halfH * up[2];
18304
+ buf[23] = 0;
18102
18305
  }
18103
18306
  render(pass) {
18104
- if (!this._volume.type || !this.frontPipeline || !this.behindPipeline || !this.bindGroup || !this.vertexBuffer || !this.uniformBuffer) return;
18307
+ const vol = this._volume;
18308
+ if (!vol.type || !this.uniformBuffer || !this.vertexBuffer || !this.bindGroup) return;
18309
+ const pipeline = vol.type === "box" ? this.boxPipeline : this.spherePipeline;
18310
+ if (!pipeline) return;
18105
18311
  const device = this.renderer.device;
18106
- const [cx, cy, cz] = this._volume.center;
18107
- const modelMatrix = new Float32Array(16);
18108
- modelMatrix[0] = 1;
18109
- modelMatrix[5] = 1;
18110
- modelMatrix[10] = 1;
18111
- modelMatrix[15] = 1;
18112
- modelMatrix[12] = cx;
18113
- modelMatrix[13] = cy;
18114
- modelMatrix[14] = cz;
18115
- const uniforms = new Float32Array(48);
18116
- uniforms.set(this.camera.viewMatrix, 0);
18117
- uniforms.set(this.camera.projectionMatrix, 16);
18118
- uniforms.set(modelMatrix, 32);
18119
- device.queue.writeBuffer(this.uniformBuffer, 0, uniforms);
18120
- const frontVerts = this._volume.type === "box" ? this.generateBoxVertices(this.frontColor) : this.generateSphereVertices(this.frontColor);
18121
- this.vertexCount = frontVerts.length / 6;
18122
- if (this.vertexCount === 0) return;
18123
- const needed = this.vertexCount * 24;
18124
- if (needed > this.vertexBuffer.size) {
18125
- this.vertexBuffer.destroy();
18126
- this.vertexBuffer = device.createBuffer({ size: needed, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST });
18127
- }
18128
- device.queue.writeBuffer(this.vertexBuffer, 0, frontVerts.buffer);
18129
- pass.setPipeline(this.frontPipeline);
18130
- pass.setBindGroup(0, this.bindGroup);
18131
- pass.setVertexBuffer(0, this.vertexBuffer);
18132
- pass.draw(this.vertexCount);
18133
- pass.setPipeline(this.behindPipeline);
18312
+ const uniforms = new Float32Array(UNIFORM_SIZE / 4);
18313
+ uniforms.set(this.camera.viewProjectionMatrix, 0);
18314
+ const [cx, cy, cz] = vol.center;
18315
+ if (vol.type === "box") {
18316
+ const [lx, ly, lz] = vol.dimensions;
18317
+ uniforms[16] = lx;
18318
+ uniforms[17] = 0;
18319
+ uniforms[18] = 0;
18320
+ uniforms[19] = 0;
18321
+ uniforms[20] = 0;
18322
+ uniforms[21] = ly;
18323
+ uniforms[22] = 0;
18324
+ uniforms[23] = 0;
18325
+ uniforms[24] = 0;
18326
+ uniforms[25] = 0;
18327
+ uniforms[26] = lz;
18328
+ uniforms[27] = 0;
18329
+ uniforms[28] = cx;
18330
+ uniforms[29] = cy;
18331
+ uniforms[30] = cz;
18332
+ uniforms[31] = 1;
18333
+ } else {
18334
+ const r2 = vol.dimensions[0] * 2;
18335
+ uniforms[16] = r2;
18336
+ uniforms[17] = 0;
18337
+ uniforms[18] = 0;
18338
+ uniforms[19] = 0;
18339
+ uniforms[20] = 0;
18340
+ uniforms[21] = r2;
18341
+ uniforms[22] = 0;
18342
+ uniforms[23] = 0;
18343
+ uniforms[24] = 0;
18344
+ uniforms[25] = 0;
18345
+ uniforms[26] = r2;
18346
+ uniforms[27] = 0;
18347
+ uniforms[28] = cx;
18348
+ uniforms[29] = cy;
18349
+ uniforms[30] = cz;
18350
+ uniforms[31] = 1;
18351
+ }
18352
+ const frustumBuf = new Float32Array(24);
18353
+ this.computeFrustumUniforms(frustumBuf);
18354
+ uniforms.set(frustumBuf, 32);
18355
+ uniforms[56] = this.renderer.width;
18356
+ uniforms[57] = this.renderer.height;
18357
+ uniforms[58] = 0;
18358
+ uniforms[59] = 0;
18359
+ if (vol.type === "box") {
18360
+ uniforms[60] = cx;
18361
+ uniforms[61] = cy;
18362
+ uniforms[62] = cz;
18363
+ uniforms[63] = 0;
18364
+ uniforms[64] = vol.dimensions[0] * 0.5;
18365
+ uniforms[65] = vol.dimensions[1] * 0.5;
18366
+ uniforms[66] = vol.dimensions[2] * 0.5;
18367
+ uniforms[67] = 0;
18368
+ } else {
18369
+ uniforms[60] = cx;
18370
+ uniforms[61] = cy;
18371
+ uniforms[62] = cz;
18372
+ uniforms[63] = vol.dimensions[0];
18373
+ uniforms[64] = 0;
18374
+ uniforms[65] = 0;
18375
+ uniforms[66] = 0;
18376
+ uniforms[67] = 0;
18377
+ }
18378
+ device.queue.writeBuffer(this.uniformBuffer, 0, uniforms.buffer);
18379
+ pass.setPipeline(pipeline);
18134
18380
  pass.setBindGroup(0, this.bindGroup);
18135
18381
  pass.setVertexBuffer(0, this.vertexBuffer);
18136
- pass.draw(this.vertexCount);
18382
+ pass.draw(36);
18137
18383
  }
18138
18384
  destroy() {
18139
18385
  var _a2, _b2;
@@ -18141,8 +18387,8 @@ class SelectionVolumeRenderer {
18141
18387
  (_b2 = this.uniformBuffer) == null ? void 0 : _b2.destroy();
18142
18388
  this.vertexBuffer = null;
18143
18389
  this.uniformBuffer = null;
18144
- this.frontPipeline = null;
18145
- this.behindPipeline = null;
18390
+ this.boxPipeline = null;
18391
+ this.spherePipeline = null;
18146
18392
  this.bindGroup = null;
18147
18393
  }
18148
18394
  }