@d5techs/3dgs-lib 1.4.81 → 1.4.83

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,12 +17575,11 @@ class SphereSelection {
17575
17575
  this.parent = parent;
17576
17576
  this.callbacks = callbacks;
17577
17577
  this.toolbar = document.createElement("div");
17578
- this.toolbar.className = "select-toolbar";
17578
+ this.toolbar.className = "volume-select-toolbar";
17579
17579
  this.toolbar.style.cssText = `
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);
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;
17584
17583
  align-items:center; font-size:13px; color:#ddd;
17585
17584
  backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
17586
17585
  box-shadow:0 2px 12px rgba(0,0,0,0.4);
@@ -17591,15 +17590,15 @@ class SphereSelection {
17591
17590
  const btn = document.createElement("button");
17592
17591
  btn.textContent = label2;
17593
17592
  btn.style.cssText = `
17594
- height:38px; padding:0 16px; border:none; border-radius:2px;
17595
- background:#444; color:#ddd; cursor:pointer; font-size:13px;
17593
+ padding:4px 12px; border:1px solid #555; border-radius:4px;
17594
+ background:#333; color:#ddd; cursor:pointer; font-size:13px;
17596
17595
  transition: background 0.15s;
17597
17596
  `;
17598
17597
  btn.addEventListener("mouseenter", () => {
17599
17598
  btn.style.background = "#555";
17600
17599
  });
17601
17600
  btn.addEventListener("mouseleave", () => {
17602
- btn.style.background = "#444";
17601
+ btn.style.background = "#333";
17603
17602
  });
17604
17603
  btn.addEventListener("pointerdown", (e) => {
17605
17604
  e.stopPropagation();
@@ -17611,27 +17610,27 @@ class SphereSelection {
17611
17610
  const addBtn = mkBtn("Add", "add");
17612
17611
  const removeBtn = mkBtn("Remove", "remove");
17613
17612
  const inputWrap = document.createElement("span");
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;";
17613
+ inputWrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
17618
17614
  const input = document.createElement("input");
17619
17615
  input.type = "number";
17620
- input.min = "0.01";
17621
- input.step = "0.01";
17622
- input.value = this._radius.toFixed(2);
17616
+ input.min = "1";
17617
+ input.step = "1";
17618
+ input.value = String(this._radius);
17623
17619
  input.style.cssText = `
17624
- width:80px; padding:4px 6px; border:1px solid #555; border-radius:4px;
17620
+ width:40px; padding:3px 4px; border:1px solid #555; border-radius:4px;
17625
17621
  background:#222; color:#ddd; font-size:12px; text-align:center;
17626
17622
  `;
17623
+ const label = document.createElement("span");
17624
+ label.textContent = "Radius";
17625
+ label.style.cssText = "color:#888; font-size:11px;";
17627
17626
  input.addEventListener("change", () => {
17628
- const v = Math.max(0.01, parseFloat(input.value) || 0.01);
17629
- input.value = v.toFixed(2);
17627
+ const v = Math.max(1, Math.round(parseFloat(input.value) || 1));
17628
+ input.value = String(v);
17630
17629
  this._radius = v;
17631
17630
  this.callbacks.onRadiusChanged(v);
17632
17631
  });
17633
- inputWrap.appendChild(label);
17634
17632
  inputWrap.appendChild(input);
17633
+ inputWrap.appendChild(label);
17635
17634
  this.radiusInput = input;
17636
17635
  this.toolbar.appendChild(setBtn);
17637
17636
  this.toolbar.appendChild(addBtn);
@@ -17643,8 +17642,8 @@ class SphereSelection {
17643
17642
  return this._radius;
17644
17643
  }
17645
17644
  setRadius(radius) {
17646
- this._radius = radius;
17647
- this.radiusInput.value = radius.toFixed(2);
17645
+ this._radius = Math.round(radius);
17646
+ this.radiusInput.value = String(this._radius);
17648
17647
  }
17649
17648
  activate() {
17650
17649
  this.toolbar.style.display = "flex";
@@ -17667,12 +17666,11 @@ class BoxSelection {
17667
17666
  this.parent = parent;
17668
17667
  this.callbacks = callbacks;
17669
17668
  this.toolbar = document.createElement("div");
17670
- this.toolbar.className = "select-toolbar";
17669
+ this.toolbar.className = "volume-select-toolbar";
17671
17670
  this.toolbar.style.cssText = `
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);
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;
17676
17674
  align-items:center; font-size:13px; color:#ddd;
17677
17675
  backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
17678
17676
  box-shadow:0 2px 12px rgba(0,0,0,0.4);
@@ -17683,15 +17681,15 @@ class BoxSelection {
17683
17681
  const btn = document.createElement("button");
17684
17682
  btn.textContent = label;
17685
17683
  btn.style.cssText = `
17686
- height:38px; padding:0 16px; border:none; border-radius:2px;
17687
- background:#444; color:#ddd; cursor:pointer; font-size:13px;
17684
+ padding:4px 12px; border:1px solid #555; border-radius:4px;
17685
+ background:#333; color:#ddd; cursor:pointer; font-size:13px;
17688
17686
  transition: background 0.15s;
17689
17687
  `;
17690
17688
  btn.addEventListener("mouseenter", () => {
17691
17689
  btn.style.background = "#555";
17692
17690
  });
17693
17691
  btn.addEventListener("mouseleave", () => {
17694
- btn.style.background = "#444";
17692
+ btn.style.background = "#333";
17695
17693
  });
17696
17694
  btn.addEventListener("pointerdown", (e) => {
17697
17695
  e.stopPropagation();
@@ -17701,26 +17699,26 @@ class BoxSelection {
17701
17699
  };
17702
17700
  const mkInput = (placeholder, initial, onChange) => {
17703
17701
  const wrap = document.createElement("span");
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;";
17702
+ wrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
17708
17703
  const input = document.createElement("input");
17709
17704
  input.type = "number";
17710
- input.min = "0.01";
17711
- input.step = "0.01";
17712
- input.value = initial.toFixed(2);
17705
+ input.min = "1";
17706
+ input.step = "1";
17707
+ input.value = String(Math.round(initial));
17713
17708
  input.style.cssText = `
17714
- width:80px; padding:4px 6px; border:1px solid #555; border-radius:4px;
17709
+ width:40px; padding:3px 4px; border:1px solid #555; border-radius:4px;
17715
17710
  background:#222; color:#ddd; font-size:12px; text-align:center;
17716
17711
  `;
17712
+ const label = document.createElement("span");
17713
+ label.textContent = placeholder;
17714
+ label.style.cssText = "color:#888; font-size:11px;";
17717
17715
  input.addEventListener("change", () => {
17718
- const v = Math.max(0.01, parseFloat(input.value) || 0.01);
17719
- input.value = v.toFixed(2);
17716
+ const v = Math.max(1, Math.round(parseFloat(input.value) || 1));
17717
+ input.value = String(v);
17720
17718
  onChange(v);
17721
17719
  });
17722
- wrap.appendChild(label);
17723
17720
  wrap.appendChild(input);
17721
+ wrap.appendChild(label);
17724
17722
  return { wrap, input };
17725
17723
  };
17726
17724
  const setBtn = mkBtn("Set", "set");
@@ -17759,12 +17757,12 @@ class BoxSelection {
17759
17757
  return this._lenZ;
17760
17758
  }
17761
17759
  setDimensions(lenX, lenY, 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);
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);
17768
17766
  }
17769
17767
  activate() {
17770
17768
  this.toolbar.style.display = "flex";
@@ -17877,311 +17875,29 @@ function exportEditedPLY(positions, scales, rotations, colors, opacities, shCoef
17877
17875
  }
17878
17876
  return buffer;
17879
17877
  }
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;
17878
+ const SEG = 64;
17879
+ const LAT_COUNT = 8;
17880
+ const LON_COUNT = 12;
17881
+ const BOX_GRID_SUBDIV = 4;
18172
17882
  class SelectionVolumeRenderer {
18173
17883
  constructor(renderer, camera) {
18174
17884
  __publicField(this, "renderer");
18175
17885
  __publicField(this, "camera");
18176
- __publicField(this, "boxPipeline", null);
18177
- __publicField(this, "spherePipeline", null);
17886
+ __publicField(this, "frontPipeline", null);
17887
+ __publicField(this, "behindPipeline", null);
18178
17888
  __publicField(this, "uniformBuffer", null);
18179
17889
  __publicField(this, "bindGroup", null);
18180
17890
  __publicField(this, "vertexBuffer", null);
18181
17891
  __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]);
18182
17894
  this.renderer = renderer;
18183
17895
  this.camera = camera;
18184
- this.createResources();
17896
+ this.createPipelines();
17897
+ this.vertexBuffer = this.renderer.device.createBuffer({
17898
+ size: 65536,
17899
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
17900
+ });
18185
17901
  }
18186
17902
  get volume() {
18187
17903
  return this._volume;
@@ -18197,187 +17913,225 @@ class SelectionVolumeRenderer {
18197
17913
  setDimensions(a, b, c) {
18198
17914
  this._volume.dimensions = [a, b, c];
18199
17915
  }
18200
- createResources() {
17916
+ createPipelines() {
18201
17917
  const device = this.renderer.device;
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
- });
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 });
18211
17949
  const bgl = device.createBindGroupLayout({
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 } }]
17950
+ entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
18221
17951
  });
17952
+ this.bindGroup = device.createBindGroup({ layout: bgl, entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }] });
18222
17953
  const layout = device.createPipelineLayout({ bindGroupLayouts: [bgl] });
18223
- const mkVertexState = (mod) => ({
18224
- module: mod,
17954
+ const vertexState = {
17955
+ module: sm,
18225
17956
  entryPoint: "vs",
18226
17957
  buffers: [{
18227
- arrayStride: 12,
18228
- attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
17958
+ arrayStride: 24,
17959
+ attributes: [
17960
+ { shaderLocation: 0, offset: 0, format: "float32x3" },
17961
+ { shaderLocation: 1, offset: 12, format: "float32x3" }
17962
+ ]
18229
17963
  }]
18230
- });
18231
- const blend = {
17964
+ };
17965
+ const blendState = {
18232
17966
  color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
18233
17967
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
18234
17968
  };
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({
17969
+ this.frontPipeline = device.createRenderPipeline({
18242
17970
  layout,
18243
- vertex: mkVertexState(boxMod),
17971
+ vertex: vertexState,
18244
17972
  fragment: {
18245
- module: boxMod,
17973
+ module: sm,
18246
17974
  entryPoint: "fs",
18247
- targets: [{ format: this.renderer.format, blend }]
17975
+ targets: [{ format: this.renderer.format, blend: blendState }]
18248
17976
  },
18249
- primitive: { topology: "triangle-list", cullMode: "front" },
18250
- depthStencil
17977
+ primitive: { topology: "line-list", cullMode: "none" },
17978
+ depthStencil: { format: this.renderer.depthFormat, depthWriteEnabled: false, depthCompare: "less-equal" }
18251
17979
  });
18252
- const sphereMod = device.createShaderModule({ code: COMMON_WGSL + SPHERE_FRAG_WGSL });
18253
- this.spherePipeline = device.createRenderPipeline({
17980
+ this.behindPipeline = device.createRenderPipeline({
18254
17981
  layout,
18255
- vertex: mkVertexState(sphereMod),
17982
+ vertex: vertexState,
18256
17983
  fragment: {
18257
- module: sphereMod,
18258
- entryPoint: "fs",
18259
- targets: [{ format: this.renderer.format, blend }]
17984
+ module: sm,
17985
+ entryPoint: "fsBehind",
17986
+ targets: [{ format: this.renderer.format, blend: blendState }]
18260
17987
  },
18261
- primitive: { topology: "triangle-list", cullMode: "front" },
18262
- depthStencil
17988
+ primitive: { topology: "line-list", cullMode: "none" },
17989
+ depthStencil: { format: this.renderer.depthFormat, depthWriteEnabled: false, depthCompare: "greater" }
18263
17990
  });
18264
17991
  }
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;
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);
18303
18100
  }
18304
18101
  render(pass) {
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;
18102
+ if (!this._volume.type || !this.frontPipeline || !this.behindPipeline || !this.bindGroup || !this.vertexBuffer || !this.uniformBuffer) return;
18309
18103
  const device = this.renderer.device;
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);
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);
18378
18132
  pass.setBindGroup(0, this.bindGroup);
18379
18133
  pass.setVertexBuffer(0, this.vertexBuffer);
18380
- pass.draw(36);
18134
+ pass.draw(this.vertexCount);
18381
18135
  }
18382
18136
  destroy() {
18383
18137
  var _a2, _b2;
@@ -18385,8 +18139,8 @@ class SelectionVolumeRenderer {
18385
18139
  (_b2 = this.uniformBuffer) == null ? void 0 : _b2.destroy();
18386
18140
  this.vertexBuffer = null;
18387
18141
  this.uniformBuffer = null;
18388
- this.boxPipeline = null;
18389
- this.spherePipeline = null;
18142
+ this.frontPipeline = null;
18143
+ this.behindPipeline = null;
18390
18144
  this.bindGroup = null;
18391
18145
  }
18392
18146
  }