@d5techs/3dgs-lib 1.4.80 → 1.4.81

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