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