@d5techs/3dgs-lib 1.4.75 → 1.4.77
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 +570 -238
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +570 -238
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/App.d.ts +7 -0
- package/dist/editor/SelectionVolumeRenderer.d.ts +32 -0
- package/dist/editor/SplatEditor.d.ts +43 -5
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/tools/BoxSelection.d.ts +18 -18
- package/dist/editor/tools/SphereSelection.d.ts +11 -15
- package/dist/index.d.ts +2 -0
- package/package.json +1 -1
package/dist/3dgs-lib.js
CHANGED
|
@@ -17566,213 +17566,212 @@ class EyedropperSelection {
|
|
|
17566
17566
|
}
|
|
17567
17567
|
}
|
|
17568
17568
|
class SphereSelection {
|
|
17569
|
-
constructor(parent,
|
|
17569
|
+
constructor(parent, callbacks) {
|
|
17570
17570
|
__publicField(this, "parent");
|
|
17571
|
-
__publicField(this, "
|
|
17572
|
-
__publicField(this, "
|
|
17573
|
-
__publicField(this, "
|
|
17574
|
-
__publicField(this, "
|
|
17575
|
-
__publicField(this, "radiusPx", 0);
|
|
17576
|
-
__publicField(this, "dragId");
|
|
17571
|
+
__publicField(this, "toolbar");
|
|
17572
|
+
__publicField(this, "callbacks");
|
|
17573
|
+
__publicField(this, "_radius", 1);
|
|
17574
|
+
__publicField(this, "radiusInput");
|
|
17577
17575
|
this.parent = parent;
|
|
17578
|
-
this.
|
|
17579
|
-
this.
|
|
17580
|
-
this.
|
|
17581
|
-
this.
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
this.
|
|
17590
|
-
this.
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17576
|
+
this.callbacks = callbacks;
|
|
17577
|
+
this.toolbar = document.createElement("div");
|
|
17578
|
+
this.toolbar.className = "volume-select-toolbar";
|
|
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;
|
|
17583
|
+
align-items:center; font-size:13px; color:#ddd;
|
|
17584
|
+
backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
|
|
17585
|
+
box-shadow:0 2px 12px rgba(0,0,0,0.4);
|
|
17586
|
+
`;
|
|
17587
|
+
this.toolbar.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
17588
|
+
this.toolbar.addEventListener("wheel", (e) => e.stopPropagation());
|
|
17589
|
+
const mkBtn = (label2, op) => {
|
|
17590
|
+
const btn = document.createElement("button");
|
|
17591
|
+
btn.textContent = label2;
|
|
17592
|
+
btn.style.cssText = `
|
|
17593
|
+
padding:4px 12px; border:1px solid #555; border-radius:4px;
|
|
17594
|
+
background:#333; color:#ddd; cursor:pointer; font-size:13px;
|
|
17595
|
+
transition: background 0.15s;
|
|
17596
|
+
`;
|
|
17597
|
+
btn.addEventListener("mouseenter", () => {
|
|
17598
|
+
btn.style.background = "#555";
|
|
17599
|
+
});
|
|
17600
|
+
btn.addEventListener("mouseleave", () => {
|
|
17601
|
+
btn.style.background = "#333";
|
|
17602
|
+
});
|
|
17603
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
17604
|
+
e.stopPropagation();
|
|
17605
|
+
this.callbacks.onApply(op);
|
|
17606
|
+
});
|
|
17607
|
+
return btn;
|
|
17608
|
+
};
|
|
17609
|
+
const setBtn = mkBtn("Set", "set");
|
|
17610
|
+
const addBtn = mkBtn("Add", "add");
|
|
17611
|
+
const removeBtn = mkBtn("Remove", "remove");
|
|
17612
|
+
const inputWrap = document.createElement("span");
|
|
17613
|
+
inputWrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
|
|
17614
|
+
const input = document.createElement("input");
|
|
17615
|
+
input.type = "number";
|
|
17616
|
+
input.min = "0.01";
|
|
17617
|
+
input.step = "0.1";
|
|
17618
|
+
input.value = this._radius.toFixed(2);
|
|
17619
|
+
input.style.cssText = `
|
|
17620
|
+
width:50px; padding:3px 4px; border:1px solid #555; border-radius:4px;
|
|
17621
|
+
background:#222; color:#ddd; font-size:12px; text-align:center;
|
|
17622
|
+
`;
|
|
17623
|
+
const label = document.createElement("span");
|
|
17624
|
+
label.textContent = "Radius";
|
|
17625
|
+
label.style.cssText = "color:#888; font-size:11px;";
|
|
17626
|
+
input.addEventListener("change", () => {
|
|
17627
|
+
const v = Math.max(0.01, parseFloat(input.value) || 0.01);
|
|
17628
|
+
input.value = v.toFixed(2);
|
|
17629
|
+
this._radius = v;
|
|
17630
|
+
this.callbacks.onRadiusChanged(v);
|
|
17631
|
+
});
|
|
17632
|
+
inputWrap.appendChild(input);
|
|
17633
|
+
inputWrap.appendChild(label);
|
|
17634
|
+
this.radiusInput = input;
|
|
17635
|
+
this.toolbar.appendChild(setBtn);
|
|
17636
|
+
this.toolbar.appendChild(addBtn);
|
|
17637
|
+
this.toolbar.appendChild(removeBtn);
|
|
17638
|
+
this.toolbar.appendChild(inputWrap);
|
|
17639
|
+
parent.appendChild(this.toolbar);
|
|
17616
17640
|
}
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
17620
|
-
e.preventDefault();
|
|
17621
|
-
e.stopPropagation();
|
|
17622
|
-
this.dragId = e.pointerId;
|
|
17623
|
-
this.parent.setPointerCapture(this.dragId);
|
|
17624
|
-
this.center.x = e.offsetX;
|
|
17625
|
-
this.center.y = e.offsetY;
|
|
17626
|
-
this.radiusPx = 0;
|
|
17627
|
-
this.updateCircle();
|
|
17628
|
-
this.circle.style.display = "block";
|
|
17641
|
+
get radius() {
|
|
17642
|
+
return this._radius;
|
|
17629
17643
|
}
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
e.stopPropagation();
|
|
17634
|
-
const dx = e.offsetX - this.center.x;
|
|
17635
|
-
const dy = e.offsetY - this.center.y;
|
|
17636
|
-
this.radiusPx = Math.sqrt(dx * dx + dy * dy);
|
|
17637
|
-
this.updateCircle();
|
|
17644
|
+
setRadius(radius) {
|
|
17645
|
+
this._radius = radius;
|
|
17646
|
+
this.radiusInput.value = radius.toFixed(2);
|
|
17638
17647
|
}
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
this.parent.releasePointerCapture(this.dragId);
|
|
17642
|
-
this.dragId = void 0;
|
|
17643
|
-
}
|
|
17644
|
-
this.circle.style.display = "none";
|
|
17648
|
+
activate() {
|
|
17649
|
+
this.toolbar.style.display = "flex";
|
|
17645
17650
|
}
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
e.preventDefault();
|
|
17649
|
-
e.stopPropagation();
|
|
17650
|
-
const selectOp = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
17651
|
-
if (this.radiusPx < 3) this.radiusPx = 20;
|
|
17652
|
-
this.onSelect(selectOp, { x: this.center.x, y: this.center.y }, this.radiusPx);
|
|
17653
|
-
this.dragEnd();
|
|
17651
|
+
deactivate() {
|
|
17652
|
+
this.toolbar.style.display = "none";
|
|
17654
17653
|
}
|
|
17655
17654
|
}
|
|
17656
17655
|
class BoxSelection {
|
|
17657
|
-
constructor(parent,
|
|
17656
|
+
constructor(parent, callbacks) {
|
|
17658
17657
|
__publicField(this, "parent");
|
|
17659
|
-
__publicField(this, "
|
|
17660
|
-
__publicField(this, "
|
|
17661
|
-
__publicField(this, "
|
|
17662
|
-
__publicField(this, "
|
|
17663
|
-
__publicField(this, "
|
|
17664
|
-
__publicField(this, "
|
|
17665
|
-
__publicField(this, "
|
|
17666
|
-
__publicField(this, "
|
|
17667
|
-
__publicField(this, "dragId");
|
|
17658
|
+
__publicField(this, "toolbar");
|
|
17659
|
+
__publicField(this, "callbacks");
|
|
17660
|
+
__publicField(this, "_lenX", 2);
|
|
17661
|
+
__publicField(this, "_lenY", 2);
|
|
17662
|
+
__publicField(this, "_lenZ", 2);
|
|
17663
|
+
__publicField(this, "inputX");
|
|
17664
|
+
__publicField(this, "inputY");
|
|
17665
|
+
__publicField(this, "inputZ");
|
|
17668
17666
|
this.parent = parent;
|
|
17669
|
-
this.
|
|
17670
|
-
this.
|
|
17671
|
-
this.
|
|
17672
|
-
this.
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
this.
|
|
17681
|
-
this.
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17667
|
+
this.callbacks = callbacks;
|
|
17668
|
+
this.toolbar = document.createElement("div");
|
|
17669
|
+
this.toolbar.className = "volume-select-toolbar";
|
|
17670
|
+
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;
|
|
17674
|
+
align-items:center; font-size:13px; color:#ddd;
|
|
17675
|
+
backdrop-filter:blur(6px); user-select:none; white-space:nowrap;
|
|
17676
|
+
box-shadow:0 2px 12px rgba(0,0,0,0.4);
|
|
17677
|
+
`;
|
|
17678
|
+
this.toolbar.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
17679
|
+
this.toolbar.addEventListener("wheel", (e) => e.stopPropagation());
|
|
17680
|
+
const mkBtn = (label, op) => {
|
|
17681
|
+
const btn = document.createElement("button");
|
|
17682
|
+
btn.textContent = label;
|
|
17683
|
+
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
|
+
transition: background 0.15s;
|
|
17687
|
+
`;
|
|
17688
|
+
btn.addEventListener("mouseenter", () => {
|
|
17689
|
+
btn.style.background = "#555";
|
|
17690
|
+
});
|
|
17691
|
+
btn.addEventListener("mouseleave", () => {
|
|
17692
|
+
btn.style.background = "#333";
|
|
17693
|
+
});
|
|
17694
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
17695
|
+
e.stopPropagation();
|
|
17696
|
+
this.callbacks.onApply(op);
|
|
17697
|
+
});
|
|
17698
|
+
return btn;
|
|
17692
17699
|
};
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17700
|
+
const mkInput = (placeholder, initial, onChange) => {
|
|
17701
|
+
const wrap = document.createElement("span");
|
|
17702
|
+
wrap.style.cssText = "display:inline-flex; align-items:center; gap:2px;";
|
|
17703
|
+
const input = document.createElement("input");
|
|
17704
|
+
input.type = "number";
|
|
17705
|
+
input.min = "0.01";
|
|
17706
|
+
input.step = "0.1";
|
|
17707
|
+
input.value = initial.toFixed(2);
|
|
17708
|
+
input.style.cssText = `
|
|
17709
|
+
width:50px; padding:3px 4px; border:1px solid #555; border-radius:4px;
|
|
17710
|
+
background:#222; color:#ddd; font-size:12px; text-align:center;
|
|
17711
|
+
`;
|
|
17712
|
+
const label = document.createElement("span");
|
|
17713
|
+
label.textContent = placeholder;
|
|
17714
|
+
label.style.cssText = "color:#888; font-size:11px;";
|
|
17715
|
+
input.addEventListener("change", () => {
|
|
17716
|
+
const v = Math.max(0.01, parseFloat(input.value) || 0.01);
|
|
17717
|
+
input.value = v.toFixed(2);
|
|
17718
|
+
onChange(v);
|
|
17719
|
+
});
|
|
17720
|
+
wrap.appendChild(input);
|
|
17721
|
+
wrap.appendChild(label);
|
|
17722
|
+
return { wrap, input };
|
|
17723
|
+
};
|
|
17724
|
+
const setBtn = mkBtn("Set", "set");
|
|
17725
|
+
const addBtn = mkBtn("Add", "add");
|
|
17726
|
+
const removeBtn = mkBtn("Remove", "remove");
|
|
17727
|
+
const xInput = mkInput("LenX", this._lenX, (v) => {
|
|
17728
|
+
this._lenX = v;
|
|
17729
|
+
this.emitDims();
|
|
17730
|
+
});
|
|
17731
|
+
const yInput = mkInput("LenY", this._lenY, (v) => {
|
|
17732
|
+
this._lenY = v;
|
|
17733
|
+
this.emitDims();
|
|
17734
|
+
});
|
|
17735
|
+
const zInput = mkInput("LenZ", this._lenZ, (v) => {
|
|
17736
|
+
this._lenZ = v;
|
|
17737
|
+
this.emitDims();
|
|
17738
|
+
});
|
|
17739
|
+
this.inputX = xInput.input;
|
|
17740
|
+
this.inputY = yInput.input;
|
|
17741
|
+
this.inputZ = zInput.input;
|
|
17742
|
+
this.toolbar.appendChild(setBtn);
|
|
17743
|
+
this.toolbar.appendChild(addBtn);
|
|
17744
|
+
this.toolbar.appendChild(removeBtn);
|
|
17745
|
+
this.toolbar.appendChild(xInput.wrap);
|
|
17746
|
+
this.toolbar.appendChild(yInput.wrap);
|
|
17747
|
+
this.toolbar.appendChild(zInput.wrap);
|
|
17748
|
+
parent.appendChild(this.toolbar);
|
|
17749
|
+
}
|
|
17750
|
+
get lenX() {
|
|
17751
|
+
return this._lenX;
|
|
17752
|
+
}
|
|
17753
|
+
get lenY() {
|
|
17754
|
+
return this._lenY;
|
|
17755
|
+
}
|
|
17756
|
+
get lenZ() {
|
|
17757
|
+
return this._lenZ;
|
|
17758
|
+
}
|
|
17759
|
+
setDimensions(lenX, lenY, lenZ) {
|
|
17760
|
+
this._lenX = lenX;
|
|
17761
|
+
this._lenY = lenY;
|
|
17762
|
+
this._lenZ = lenZ;
|
|
17763
|
+
this.inputX.value = lenX.toFixed(2);
|
|
17764
|
+
this.inputY.value = lenY.toFixed(2);
|
|
17765
|
+
this.inputZ.value = lenZ.toFixed(2);
|
|
17698
17766
|
}
|
|
17699
17767
|
activate() {
|
|
17700
|
-
this.
|
|
17701
|
-
this.parent.style.cursor = "crosshair";
|
|
17702
|
-
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
17703
|
-
this.parent.addEventListener("pointermove", this.pointermove);
|
|
17704
|
-
this.parent.addEventListener("pointerup", this.pointerup);
|
|
17768
|
+
this.toolbar.style.display = "flex";
|
|
17705
17769
|
}
|
|
17706
17770
|
deactivate() {
|
|
17707
|
-
|
|
17708
|
-
this.svg.style.display = "none";
|
|
17709
|
-
this.parent.style.cursor = "";
|
|
17710
|
-
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
17711
|
-
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
17712
|
-
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
17771
|
+
this.toolbar.style.display = "none";
|
|
17713
17772
|
}
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
const y = this.center.y - this.halfH;
|
|
17717
|
-
const w = this.halfW * 2;
|
|
17718
|
-
const h = this.halfH * 2;
|
|
17719
|
-
this.rect.setAttribute("x", x.toString());
|
|
17720
|
-
this.rect.setAttribute("y", y.toString());
|
|
17721
|
-
this.rect.setAttribute("width", Math.max(1, w).toString());
|
|
17722
|
-
this.rect.setAttribute("height", Math.max(1, h).toString());
|
|
17723
|
-
this.crossV.setAttribute("x1", this.center.x.toString());
|
|
17724
|
-
this.crossV.setAttribute("y1", y.toString());
|
|
17725
|
-
this.crossV.setAttribute("x2", this.center.x.toString());
|
|
17726
|
-
this.crossV.setAttribute("y2", (y + h).toString());
|
|
17727
|
-
this.crossH.setAttribute("x1", x.toString());
|
|
17728
|
-
this.crossH.setAttribute("y1", this.center.y.toString());
|
|
17729
|
-
this.crossH.setAttribute("x2", (x + w).toString());
|
|
17730
|
-
this.crossH.setAttribute("y2", this.center.y.toString());
|
|
17731
|
-
}
|
|
17732
|
-
pointerdown(e) {
|
|
17733
|
-
if (this.dragId !== void 0) return;
|
|
17734
|
-
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
17735
|
-
e.preventDefault();
|
|
17736
|
-
e.stopPropagation();
|
|
17737
|
-
this.dragId = e.pointerId;
|
|
17738
|
-
this.parent.setPointerCapture(this.dragId);
|
|
17739
|
-
this.center.x = e.offsetX;
|
|
17740
|
-
this.center.y = e.offsetY;
|
|
17741
|
-
this.halfW = 0;
|
|
17742
|
-
this.halfH = 0;
|
|
17743
|
-
this.updateVisuals();
|
|
17744
|
-
this.rect.style.display = "block";
|
|
17745
|
-
this.crossV.style.display = "block";
|
|
17746
|
-
this.crossH.style.display = "block";
|
|
17747
|
-
}
|
|
17748
|
-
pointermove(e) {
|
|
17749
|
-
if (e.pointerId !== this.dragId) return;
|
|
17750
|
-
e.preventDefault();
|
|
17751
|
-
e.stopPropagation();
|
|
17752
|
-
this.halfW = Math.abs(e.offsetX - this.center.x);
|
|
17753
|
-
this.halfH = Math.abs(e.offsetY - this.center.y);
|
|
17754
|
-
this.updateVisuals();
|
|
17755
|
-
}
|
|
17756
|
-
dragEnd() {
|
|
17757
|
-
if (this.dragId !== void 0) {
|
|
17758
|
-
this.parent.releasePointerCapture(this.dragId);
|
|
17759
|
-
this.dragId = void 0;
|
|
17760
|
-
}
|
|
17761
|
-
this.rect.style.display = "none";
|
|
17762
|
-
this.crossV.style.display = "none";
|
|
17763
|
-
this.crossH.style.display = "none";
|
|
17764
|
-
}
|
|
17765
|
-
pointerup(e) {
|
|
17766
|
-
if (e.pointerId !== this.dragId) return;
|
|
17767
|
-
e.preventDefault();
|
|
17768
|
-
e.stopPropagation();
|
|
17769
|
-
const selectOp = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
17770
|
-
if (this.halfW < 3 && this.halfH < 3) {
|
|
17771
|
-
this.halfW = 20;
|
|
17772
|
-
this.halfH = 20;
|
|
17773
|
-
}
|
|
17774
|
-
this.onSelect(selectOp, { x: this.center.x, y: this.center.y }, this.halfW, this.halfH);
|
|
17775
|
-
this.dragEnd();
|
|
17773
|
+
emitDims() {
|
|
17774
|
+
this.callbacks.onDimensionsChanged(this._lenX, this._lenY, this._lenZ);
|
|
17776
17775
|
}
|
|
17777
17776
|
}
|
|
17778
17777
|
function exportEditedPLY(positions, scales, rotations, colors, opacities, shCoeffs, state) {
|
|
@@ -17876,9 +17875,205 @@ function exportEditedPLY(positions, scales, rotations, colors, opacities, shCoef
|
|
|
17876
17875
|
}
|
|
17877
17876
|
return buffer;
|
|
17878
17877
|
}
|
|
17878
|
+
const SPHERE_SEGMENTS = 64;
|
|
17879
|
+
const SPHERE_RINGS = 3;
|
|
17880
|
+
class SelectionVolumeRenderer {
|
|
17881
|
+
constructor(renderer, camera) {
|
|
17882
|
+
__publicField(this, "renderer");
|
|
17883
|
+
__publicField(this, "camera");
|
|
17884
|
+
__publicField(this, "pipeline", null);
|
|
17885
|
+
__publicField(this, "uniformBuffer", null);
|
|
17886
|
+
__publicField(this, "bindGroup", null);
|
|
17887
|
+
__publicField(this, "vertexBuffer", null);
|
|
17888
|
+
__publicField(this, "_volume", { type: null, center: [0, 0, 0], dimensions: [2, 2, 2] });
|
|
17889
|
+
__publicField(this, "vertexCount", 0);
|
|
17890
|
+
__publicField(this, "maxVertices");
|
|
17891
|
+
__publicField(this, "lineColor", [0.2, 0.8, 1]);
|
|
17892
|
+
this.renderer = renderer;
|
|
17893
|
+
this.camera = camera;
|
|
17894
|
+
this.maxVertices = Math.max(
|
|
17895
|
+
24,
|
|
17896
|
+
SPHERE_SEGMENTS * 2 * SPHERE_RINGS
|
|
17897
|
+
);
|
|
17898
|
+
this.createPipeline();
|
|
17899
|
+
this.createVertexBuffer();
|
|
17900
|
+
}
|
|
17901
|
+
get volume() {
|
|
17902
|
+
return this._volume;
|
|
17903
|
+
}
|
|
17904
|
+
setVolume(type, center, dimensions) {
|
|
17905
|
+
this._volume.type = type;
|
|
17906
|
+
if (center) this._volume.center = center;
|
|
17907
|
+
if (dimensions) this._volume.dimensions = dimensions;
|
|
17908
|
+
}
|
|
17909
|
+
setCenter(x, y, z) {
|
|
17910
|
+
this._volume.center = [x, y, z];
|
|
17911
|
+
}
|
|
17912
|
+
setDimensions(a, b, c) {
|
|
17913
|
+
this._volume.dimensions = [a, b, c];
|
|
17914
|
+
}
|
|
17915
|
+
createPipeline() {
|
|
17916
|
+
const device = this.renderer.device;
|
|
17917
|
+
const shaderCode = (
|
|
17918
|
+
/* wgsl */
|
|
17919
|
+
`
|
|
17920
|
+
struct Uniforms { viewProjection: mat4x4<f32> }
|
|
17921
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
17922
|
+
|
|
17923
|
+
struct VI { @location(0) position: vec3<f32>, @location(1) color: vec3<f32> }
|
|
17924
|
+
struct VO { @builtin(position) position: vec4<f32>, @location(0) color: vec3<f32> }
|
|
17925
|
+
|
|
17926
|
+
@vertex fn vs(i: VI) -> VO {
|
|
17927
|
+
var o: VO;
|
|
17928
|
+
o.position = uniforms.viewProjection * vec4(i.position, 1.0);
|
|
17929
|
+
o.color = i.color;
|
|
17930
|
+
return o;
|
|
17931
|
+
}
|
|
17932
|
+
@fragment fn fs(i: VO) -> @location(0) vec4<f32> {
|
|
17933
|
+
return vec4(i.color, 0.8);
|
|
17934
|
+
}
|
|
17935
|
+
`
|
|
17936
|
+
);
|
|
17937
|
+
const sm = device.createShaderModule({ code: shaderCode });
|
|
17938
|
+
this.uniformBuffer = device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
17939
|
+
const bgl = device.createBindGroupLayout({
|
|
17940
|
+
entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } }]
|
|
17941
|
+
});
|
|
17942
|
+
this.bindGroup = device.createBindGroup({ layout: bgl, entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }] });
|
|
17943
|
+
this.pipeline = device.createRenderPipeline({
|
|
17944
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [bgl] }),
|
|
17945
|
+
vertex: {
|
|
17946
|
+
module: sm,
|
|
17947
|
+
entryPoint: "vs",
|
|
17948
|
+
buffers: [{
|
|
17949
|
+
arrayStride: 24,
|
|
17950
|
+
attributes: [
|
|
17951
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
17952
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" }
|
|
17953
|
+
]
|
|
17954
|
+
}]
|
|
17955
|
+
},
|
|
17956
|
+
fragment: {
|
|
17957
|
+
module: sm,
|
|
17958
|
+
entryPoint: "fs",
|
|
17959
|
+
targets: [{
|
|
17960
|
+
format: this.renderer.format,
|
|
17961
|
+
blend: {
|
|
17962
|
+
color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
17963
|
+
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
|
|
17964
|
+
}
|
|
17965
|
+
}]
|
|
17966
|
+
},
|
|
17967
|
+
primitive: { topology: "line-list", cullMode: "none" },
|
|
17968
|
+
depthStencil: { format: this.renderer.depthFormat, depthWriteEnabled: false, depthCompare: "always" }
|
|
17969
|
+
});
|
|
17970
|
+
}
|
|
17971
|
+
createVertexBuffer() {
|
|
17972
|
+
const size = this.maxVertices * 24;
|
|
17973
|
+
this.vertexBuffer = this.renderer.device.createBuffer({
|
|
17974
|
+
size,
|
|
17975
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
17976
|
+
});
|
|
17977
|
+
}
|
|
17978
|
+
generateBoxVertices() {
|
|
17979
|
+
const [cx, cy, cz] = this._volume.center;
|
|
17980
|
+
const [lx, ly, lz] = this._volume.dimensions;
|
|
17981
|
+
const hx = lx / 2, hy = ly / 2, hz = lz / 2;
|
|
17982
|
+
const [r, g, b] = this.lineColor;
|
|
17983
|
+
const v = [];
|
|
17984
|
+
const addLine = (x12, y12, z12, x2, y2, z2) => {
|
|
17985
|
+
v.push(x12, y12, z12, r, g, b, x2, y2, z2, r, g, b);
|
|
17986
|
+
};
|
|
17987
|
+
const x0 = cx - hx, x1 = cx + hx;
|
|
17988
|
+
const y0 = cy - hy, y1 = cy + hy;
|
|
17989
|
+
const z0 = cz - hz, z1 = cz + hz;
|
|
17990
|
+
addLine(x0, y0, z0, x1, y0, z0);
|
|
17991
|
+
addLine(x1, y0, z0, x1, y0, z1);
|
|
17992
|
+
addLine(x1, y0, z1, x0, y0, z1);
|
|
17993
|
+
addLine(x0, y0, z1, x0, y0, z0);
|
|
17994
|
+
addLine(x0, y1, z0, x1, y1, z0);
|
|
17995
|
+
addLine(x1, y1, z0, x1, y1, z1);
|
|
17996
|
+
addLine(x1, y1, z1, x0, y1, z1);
|
|
17997
|
+
addLine(x0, y1, z1, x0, y1, z0);
|
|
17998
|
+
addLine(x0, y0, z0, x0, y1, z0);
|
|
17999
|
+
addLine(x1, y0, z0, x1, y1, z0);
|
|
18000
|
+
addLine(x1, y0, z1, x1, y1, z1);
|
|
18001
|
+
addLine(x0, y0, z1, x0, y1, z1);
|
|
18002
|
+
return new Float32Array(v);
|
|
18003
|
+
}
|
|
18004
|
+
generateSphereVertices() {
|
|
18005
|
+
const [cx, cy, cz] = this._volume.center;
|
|
18006
|
+
const radius = this._volume.dimensions[0];
|
|
18007
|
+
const [r, g, b] = this.lineColor;
|
|
18008
|
+
const v = [];
|
|
18009
|
+
const seg = SPHERE_SEGMENTS;
|
|
18010
|
+
const addCircle = (axis) => {
|
|
18011
|
+
for (let i = 0; i < seg; i++) {
|
|
18012
|
+
const a0 = i / seg * Math.PI * 2;
|
|
18013
|
+
const a1 = (i + 1) / seg * Math.PI * 2;
|
|
18014
|
+
let x0, y0, z0, x1, y1, z1;
|
|
18015
|
+
if (axis === "y") {
|
|
18016
|
+
x0 = cx + Math.cos(a0) * radius;
|
|
18017
|
+
y0 = cy;
|
|
18018
|
+
z0 = cz + Math.sin(a0) * radius;
|
|
18019
|
+
x1 = cx + Math.cos(a1) * radius;
|
|
18020
|
+
y1 = cy;
|
|
18021
|
+
z1 = cz + Math.sin(a1) * radius;
|
|
18022
|
+
} else if (axis === "x") {
|
|
18023
|
+
x0 = cx;
|
|
18024
|
+
y0 = cy + Math.cos(a0) * radius;
|
|
18025
|
+
z0 = cz + Math.sin(a0) * radius;
|
|
18026
|
+
x1 = cx;
|
|
18027
|
+
y1 = cy + Math.cos(a1) * radius;
|
|
18028
|
+
z1 = cz + Math.sin(a1) * radius;
|
|
18029
|
+
} else {
|
|
18030
|
+
x0 = cx + Math.cos(a0) * radius;
|
|
18031
|
+
y0 = cy + Math.sin(a0) * radius;
|
|
18032
|
+
z0 = cz;
|
|
18033
|
+
x1 = cx + Math.cos(a1) * radius;
|
|
18034
|
+
y1 = cy + Math.sin(a1) * radius;
|
|
18035
|
+
z1 = cz;
|
|
18036
|
+
}
|
|
18037
|
+
v.push(x0, y0, z0, r, g, b, x1, y1, z1, r, g, b);
|
|
18038
|
+
}
|
|
18039
|
+
};
|
|
18040
|
+
addCircle("x");
|
|
18041
|
+
addCircle("y");
|
|
18042
|
+
addCircle("z");
|
|
18043
|
+
return new Float32Array(v);
|
|
18044
|
+
}
|
|
18045
|
+
render(pass) {
|
|
18046
|
+
if (!this._volume.type || !this.pipeline || !this.bindGroup || !this.vertexBuffer || !this.uniformBuffer) return;
|
|
18047
|
+
const device = this.renderer.device;
|
|
18048
|
+
const verts = this._volume.type === "box" ? this.generateBoxVertices() : this.generateSphereVertices();
|
|
18049
|
+
this.vertexCount = verts.length / 6;
|
|
18050
|
+
if (this.vertexCount === 0) return;
|
|
18051
|
+
const needed = this.vertexCount * 24;
|
|
18052
|
+
if (needed > this.vertexBuffer.size) {
|
|
18053
|
+
this.vertexBuffer.destroy();
|
|
18054
|
+
this.vertexBuffer = device.createBuffer({ size: needed, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST });
|
|
18055
|
+
}
|
|
18056
|
+
device.queue.writeBuffer(this.vertexBuffer, 0, verts.buffer);
|
|
18057
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, new Float32Array(this.camera.viewProjectionMatrix));
|
|
18058
|
+
pass.setPipeline(this.pipeline);
|
|
18059
|
+
pass.setBindGroup(0, this.bindGroup);
|
|
18060
|
+
pass.setVertexBuffer(0, this.vertexBuffer);
|
|
18061
|
+
pass.draw(this.vertexCount);
|
|
18062
|
+
}
|
|
18063
|
+
destroy() {
|
|
18064
|
+
var _a2, _b2;
|
|
18065
|
+
(_a2 = this.vertexBuffer) == null ? void 0 : _a2.destroy();
|
|
18066
|
+
(_b2 = this.uniformBuffer) == null ? void 0 : _b2.destroy();
|
|
18067
|
+
this.vertexBuffer = null;
|
|
18068
|
+
this.uniformBuffer = null;
|
|
18069
|
+
this.pipeline = null;
|
|
18070
|
+
this.bindGroup = null;
|
|
18071
|
+
}
|
|
18072
|
+
}
|
|
17879
18073
|
class SplatEditor {
|
|
17880
|
-
constructor(camera, gsRenderer, container, callbacks = {}) {
|
|
18074
|
+
constructor(camera, gsRenderer, container, callbacks = {}, renderer) {
|
|
17881
18075
|
__publicField(this, "camera");
|
|
18076
|
+
__publicField(this, "gpuRenderer");
|
|
17882
18077
|
__publicField(this, "gsRenderer");
|
|
17883
18078
|
__publicField(this, "container");
|
|
17884
18079
|
__publicField(this, "splatState");
|
|
@@ -17893,6 +18088,10 @@ class SplatEditor {
|
|
|
17893
18088
|
__publicField(this, "projDirty", true);
|
|
17894
18089
|
__publicField(this, "_active", false);
|
|
17895
18090
|
__publicField(this, "overlayCleanup", []);
|
|
18091
|
+
/** 体积选择线框渲染器 */
|
|
18092
|
+
__publicField(this, "volumeRenderer", null);
|
|
18093
|
+
__publicField(this, "boxTool", null);
|
|
18094
|
+
__publicField(this, "sphereTool", null);
|
|
17896
18095
|
// ============================================
|
|
17897
18096
|
// 键盘快捷键
|
|
17898
18097
|
// ============================================
|
|
@@ -17901,6 +18100,7 @@ class SplatEditor {
|
|
|
17901
18100
|
this.gsRenderer = gsRenderer;
|
|
17902
18101
|
this.container = container;
|
|
17903
18102
|
this.callbacks = callbacks;
|
|
18103
|
+
this.gpuRenderer = renderer;
|
|
17904
18104
|
}
|
|
17905
18105
|
get active() {
|
|
17906
18106
|
return this._active;
|
|
@@ -17947,9 +18147,28 @@ class SplatEditor {
|
|
|
17947
18147
|
this.container.style.position = "relative";
|
|
17948
18148
|
}
|
|
17949
18149
|
this.toolManager = new ToolManager((name) => {
|
|
17950
|
-
var _a2, _b2;
|
|
17951
|
-
|
|
17952
|
-
|
|
18150
|
+
var _a2, _b2, _c, _d, _e, _f;
|
|
18151
|
+
const isVolumeTool = name === "box" || name === "sphere";
|
|
18152
|
+
this.toolOverlay.style.display = name && !isVolumeTool ? "block" : "none";
|
|
18153
|
+
if (isVolumeTool && this.volumeRenderer) {
|
|
18154
|
+
const volType = name;
|
|
18155
|
+
const bounds = this.getModelBounds();
|
|
18156
|
+
const defaultSize = Math.max(bounds.size[0], bounds.size[1], bounds.size[2]) * 0.3;
|
|
18157
|
+
const safeSize = Math.max(defaultSize, 0.5);
|
|
18158
|
+
if (volType === "box") {
|
|
18159
|
+
this.boxTool.setDimensions(safeSize, safeSize, safeSize);
|
|
18160
|
+
this.volumeRenderer.setVolume("box", bounds.center, [safeSize, safeSize, safeSize]);
|
|
18161
|
+
} else {
|
|
18162
|
+
const r = safeSize / 2;
|
|
18163
|
+
this.sphereTool.setRadius(r);
|
|
18164
|
+
this.volumeRenderer.setVolume("sphere", bounds.center, [r, 0, 0]);
|
|
18165
|
+
}
|
|
18166
|
+
(_b2 = (_a2 = this.callbacks).onVolumeToolActivated) == null ? void 0 : _b2.call(_a2, this.volumeRenderer.volume);
|
|
18167
|
+
} else {
|
|
18168
|
+
if (this.volumeRenderer) this.volumeRenderer.setVolume(null);
|
|
18169
|
+
(_d = (_c = this.callbacks).onVolumeToolDeactivated) == null ? void 0 : _d.call(_c);
|
|
18170
|
+
}
|
|
18171
|
+
(_f = (_e = this.callbacks).onToolChanged) == null ? void 0 : _f.call(_e, name);
|
|
17953
18172
|
});
|
|
17954
18173
|
this.toolManager.register("rect", new RectSelection(
|
|
17955
18174
|
this.toolOverlay,
|
|
@@ -17985,24 +18204,42 @@ class SplatEditor {
|
|
|
17985
18204
|
this.toolOverlay,
|
|
17986
18205
|
(op, pt, threshold) => this.selectByColor(op, pt, threshold)
|
|
17987
18206
|
));
|
|
17988
|
-
this.
|
|
17989
|
-
this.
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
18207
|
+
if (this.gpuRenderer) {
|
|
18208
|
+
this.volumeRenderer = new SelectionVolumeRenderer(this.gpuRenderer, this.camera);
|
|
18209
|
+
}
|
|
18210
|
+
this.sphereTool = new SphereSelection(this.container, {
|
|
18211
|
+
onApply: (op) => this.applyVolumeSelection(op),
|
|
18212
|
+
onRadiusChanged: (radius) => {
|
|
18213
|
+
var _a2;
|
|
18214
|
+
(_a2 = this.volumeRenderer) == null ? void 0 : _a2.setDimensions(radius, 0, 0);
|
|
18215
|
+
}
|
|
18216
|
+
});
|
|
18217
|
+
this.toolManager.register("sphere", this.sphereTool);
|
|
18218
|
+
this.boxTool = new BoxSelection(this.container, {
|
|
18219
|
+
onApply: (op) => this.applyVolumeSelection(op),
|
|
18220
|
+
onDimensionsChanged: (lx, ly, lz) => {
|
|
18221
|
+
var _a2;
|
|
18222
|
+
(_a2 = this.volumeRenderer) == null ? void 0 : _a2.setDimensions(lx, ly, lz);
|
|
18223
|
+
}
|
|
18224
|
+
});
|
|
18225
|
+
this.toolManager.register("box", this.boxTool);
|
|
17996
18226
|
this.gsRenderer.setEditorState(this.splatState.data);
|
|
17997
18227
|
this._keyHandler = this._onKeyDown.bind(this);
|
|
17998
18228
|
window.addEventListener("keydown", this._keyHandler);
|
|
17999
18229
|
this.projDirty = true;
|
|
18000
18230
|
}
|
|
18231
|
+
/**
|
|
18232
|
+
* 渲染体积选择线框(在主渲染 pass 内调用)
|
|
18233
|
+
*/
|
|
18234
|
+
renderVolume(pass) {
|
|
18235
|
+
var _a2;
|
|
18236
|
+
(_a2 = this.volumeRenderer) == null ? void 0 : _a2.render(pass);
|
|
18237
|
+
}
|
|
18001
18238
|
/**
|
|
18002
18239
|
* 退出编辑模式,将编辑结果永久写入渲染数据
|
|
18003
18240
|
*/
|
|
18004
18241
|
exit() {
|
|
18005
|
-
var _a2, _b2;
|
|
18242
|
+
var _a2, _b2, _c;
|
|
18006
18243
|
if (!this._active) return;
|
|
18007
18244
|
this._active = false;
|
|
18008
18245
|
window.removeEventListener("keydown", this._keyHandler);
|
|
@@ -18011,6 +18248,8 @@ class SplatEditor {
|
|
|
18011
18248
|
this.overlayCleanup = [];
|
|
18012
18249
|
(_a2 = this.toolOverlay) == null ? void 0 : _a2.remove();
|
|
18013
18250
|
(_b2 = this.maskCanvas) == null ? void 0 : _b2.remove();
|
|
18251
|
+
(_c = this.volumeRenderer) == null ? void 0 : _c.destroy();
|
|
18252
|
+
this.volumeRenderer = null;
|
|
18014
18253
|
this.applyEditsToRenderer();
|
|
18015
18254
|
this.editHistory.clear();
|
|
18016
18255
|
this.gsRenderer.clearEditorState();
|
|
@@ -18246,52 +18485,131 @@ class SplatEditor {
|
|
|
18246
18485
|
this.editHistory.add(editOp);
|
|
18247
18486
|
}
|
|
18248
18487
|
/**
|
|
18249
|
-
*
|
|
18488
|
+
* 世界空间球体选择
|
|
18250
18489
|
*/
|
|
18251
|
-
|
|
18252
|
-
this.
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
const
|
|
18256
|
-
const
|
|
18257
|
-
const cx = centerPx.x / w;
|
|
18258
|
-
const cy = centerPx.y / h;
|
|
18259
|
-
const rx = radiusPx / w;
|
|
18260
|
-
const ry = radiusPx / h;
|
|
18261
|
-
const rx2 = rx * rx;
|
|
18262
|
-
const ry2 = ry * ry;
|
|
18490
|
+
selectByWorldSphere(op, center, radius) {
|
|
18491
|
+
const cpuPos = this.gsRenderer.getCPUPositions();
|
|
18492
|
+
if (!cpuPos) return;
|
|
18493
|
+
const modelMat = this.gsRenderer.getModelMatrix();
|
|
18494
|
+
const r2 = radius * radius;
|
|
18495
|
+
const [cx, cy, cz] = center;
|
|
18263
18496
|
const editOp = new SelectOp(this.splatState, op, (i) => {
|
|
18264
|
-
|
|
18265
|
-
const
|
|
18266
|
-
|
|
18267
|
-
const
|
|
18268
|
-
|
|
18497
|
+
if (this.splatState.data[i] & (State.hidden | State.deleted)) return false;
|
|
18498
|
+
const i3 = i * 3;
|
|
18499
|
+
const lx = cpuPos[i3], ly = cpuPos[i3 + 1], lz = cpuPos[i3 + 2];
|
|
18500
|
+
const wx = modelMat[0] * lx + modelMat[4] * ly + modelMat[8] * lz + modelMat[12];
|
|
18501
|
+
const wy = modelMat[1] * lx + modelMat[5] * ly + modelMat[9] * lz + modelMat[13];
|
|
18502
|
+
const wz = modelMat[2] * lx + modelMat[6] * ly + modelMat[10] * lz + modelMat[14];
|
|
18503
|
+
const dx = wx - cx, dy = wy - cy, dz = wz - cz;
|
|
18504
|
+
return dx * dx + dy * dy + dz * dz <= r2;
|
|
18269
18505
|
});
|
|
18270
18506
|
this.editHistory.add(editOp);
|
|
18271
18507
|
}
|
|
18272
18508
|
/**
|
|
18273
|
-
*
|
|
18509
|
+
* 世界空间 AABB 选择
|
|
18274
18510
|
*/
|
|
18275
|
-
|
|
18276
|
-
this.
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
const
|
|
18280
|
-
const
|
|
18281
|
-
const cx = centerPx.x / w;
|
|
18282
|
-
const cy = centerPx.y / h;
|
|
18283
|
-
const hw = halfWPx / w;
|
|
18284
|
-
const hh = halfHPx / h;
|
|
18285
|
-
const left = cx - hw, right = cx + hw;
|
|
18286
|
-
const top = cy - hh, bottom = cy + hh;
|
|
18511
|
+
selectByWorldBox(op, center, lenX, lenY, lenZ) {
|
|
18512
|
+
const cpuPos = this.gsRenderer.getCPUPositions();
|
|
18513
|
+
if (!cpuPos) return;
|
|
18514
|
+
const modelMat = this.gsRenderer.getModelMatrix();
|
|
18515
|
+
const [cx, cy, cz] = center;
|
|
18516
|
+
const hx = lenX / 2, hy = lenY / 2, hz = lenZ / 2;
|
|
18287
18517
|
const editOp = new SelectOp(this.splatState, op, (i) => {
|
|
18288
|
-
|
|
18289
|
-
const
|
|
18290
|
-
|
|
18291
|
-
|
|
18518
|
+
if (this.splatState.data[i] & (State.hidden | State.deleted)) return false;
|
|
18519
|
+
const i3 = i * 3;
|
|
18520
|
+
const lx = cpuPos[i3], ly = cpuPos[i3 + 1], lz = cpuPos[i3 + 2];
|
|
18521
|
+
const wx = modelMat[0] * lx + modelMat[4] * ly + modelMat[8] * lz + modelMat[12];
|
|
18522
|
+
const wy = modelMat[1] * lx + modelMat[5] * ly + modelMat[9] * lz + modelMat[13];
|
|
18523
|
+
const wz = modelMat[2] * lx + modelMat[6] * ly + modelMat[10] * lz + modelMat[14];
|
|
18524
|
+
const rx = wx - cx, ry = wy - cy, rz = wz - cz;
|
|
18525
|
+
return rx >= -hx && rx <= hx && ry >= -hy && ry <= hy && rz >= -hz && rz <= hz;
|
|
18292
18526
|
});
|
|
18293
18527
|
this.editHistory.add(editOp);
|
|
18294
18528
|
}
|
|
18529
|
+
/**
|
|
18530
|
+
* 应用当前体积选择
|
|
18531
|
+
*/
|
|
18532
|
+
applyVolumeSelection(op) {
|
|
18533
|
+
if (!this.volumeRenderer) return;
|
|
18534
|
+
const vol = this.volumeRenderer.volume;
|
|
18535
|
+
if (!vol.type) return;
|
|
18536
|
+
if (vol.type === "sphere") {
|
|
18537
|
+
this.selectByWorldSphere(op, vol.center, vol.dimensions[0]);
|
|
18538
|
+
} else {
|
|
18539
|
+
this.selectByWorldBox(op, vol.center, vol.dimensions[0], vol.dimensions[1], vol.dimensions[2]);
|
|
18540
|
+
}
|
|
18541
|
+
}
|
|
18542
|
+
/**
|
|
18543
|
+
* 设置体积选择的中心位置(供外部 gizmo 调用)
|
|
18544
|
+
*/
|
|
18545
|
+
setVolumeCenter(x, y, z) {
|
|
18546
|
+
var _a2;
|
|
18547
|
+
(_a2 = this.volumeRenderer) == null ? void 0 : _a2.setCenter(x, y, z);
|
|
18548
|
+
}
|
|
18549
|
+
/**
|
|
18550
|
+
* 获取当前体积状态
|
|
18551
|
+
*/
|
|
18552
|
+
getVolumeState() {
|
|
18553
|
+
var _a2;
|
|
18554
|
+
return ((_a2 = this.volumeRenderer) == null ? void 0 : _a2.volume) ?? null;
|
|
18555
|
+
}
|
|
18556
|
+
/**
|
|
18557
|
+
* 创建体积位置的 TransformableObject 代理,供 gizmo 控制
|
|
18558
|
+
*/
|
|
18559
|
+
createVolumeTransformProxy() {
|
|
18560
|
+
if (!this.volumeRenderer) return null;
|
|
18561
|
+
const vol = this.volumeRenderer;
|
|
18562
|
+
return {
|
|
18563
|
+
get position() {
|
|
18564
|
+
return vol.volume.center;
|
|
18565
|
+
},
|
|
18566
|
+
get rotation() {
|
|
18567
|
+
return [0, 0, 0];
|
|
18568
|
+
},
|
|
18569
|
+
get scale() {
|
|
18570
|
+
return [1, 1, 1];
|
|
18571
|
+
},
|
|
18572
|
+
setPosition: (x, y, z) => {
|
|
18573
|
+
vol.setCenter(x, y, z);
|
|
18574
|
+
},
|
|
18575
|
+
setRotation: () => {
|
|
18576
|
+
},
|
|
18577
|
+
setScale: () => {
|
|
18578
|
+
}
|
|
18579
|
+
};
|
|
18580
|
+
}
|
|
18581
|
+
/**
|
|
18582
|
+
* 获取模型世界空间包围盒(中心 + 尺寸)
|
|
18583
|
+
*/
|
|
18584
|
+
getModelBounds() {
|
|
18585
|
+
const cpuPos = this.gsRenderer.getCPUPositions();
|
|
18586
|
+
if (!cpuPos || cpuPos.length === 0) return { center: [0, 0, 0], size: [2, 2, 2] };
|
|
18587
|
+
const modelMat = this.gsRenderer.getModelMatrix();
|
|
18588
|
+
const count = this.splatState.count;
|
|
18589
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
18590
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
18591
|
+
const step = Math.max(1, Math.floor(count / 2e3));
|
|
18592
|
+
for (let i = 0; i < count; i += step) {
|
|
18593
|
+
const i3 = i * 3;
|
|
18594
|
+
const lx = cpuPos[i3], ly = cpuPos[i3 + 1], lz = cpuPos[i3 + 2];
|
|
18595
|
+
const wx = modelMat[0] * lx + modelMat[4] * ly + modelMat[8] * lz + modelMat[12];
|
|
18596
|
+
const wy = modelMat[1] * lx + modelMat[5] * ly + modelMat[9] * lz + modelMat[13];
|
|
18597
|
+
const wz = modelMat[2] * lx + modelMat[6] * ly + modelMat[10] * lz + modelMat[14];
|
|
18598
|
+
if (wx < minX) minX = wx;
|
|
18599
|
+
if (wx > maxX) maxX = wx;
|
|
18600
|
+
if (wy < minY) minY = wy;
|
|
18601
|
+
if (wy > maxY) maxY = wy;
|
|
18602
|
+
if (wz < minZ) minZ = wz;
|
|
18603
|
+
if (wz > maxZ) maxZ = wz;
|
|
18604
|
+
}
|
|
18605
|
+
return {
|
|
18606
|
+
center: [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2],
|
|
18607
|
+
size: [maxX - minX, maxY - minY, maxZ - minZ]
|
|
18608
|
+
};
|
|
18609
|
+
}
|
|
18610
|
+
getModelCenter() {
|
|
18611
|
+
return this.getModelBounds().center;
|
|
18612
|
+
}
|
|
18295
18613
|
/**
|
|
18296
18614
|
* 根据屏幕像素坐标,找到最近的可见 splat 并返回其世界坐标及深度换算系数
|
|
18297
18615
|
*/
|
|
@@ -18496,6 +18814,8 @@ class App {
|
|
|
18496
18814
|
__publicField(this, "baseRenderScale", 1);
|
|
18497
18815
|
// 绑定的事件处理函数
|
|
18498
18816
|
__publicField(this, "boundOnResize");
|
|
18817
|
+
/** 额外渲染回调(在 gizmo 之前、场景辅助之后执行) */
|
|
18818
|
+
__publicField(this, "extraRenderCallbacks", []);
|
|
18499
18819
|
this.canvas = canvas;
|
|
18500
18820
|
this.boundOnResize = this.onResize.bind(this);
|
|
18501
18821
|
}
|
|
@@ -18941,6 +19261,7 @@ class App {
|
|
|
18941
19261
|
}
|
|
18942
19262
|
this.meshRenderer.render(pass);
|
|
18943
19263
|
this.sceneAids.render(pass);
|
|
19264
|
+
for (const cb of this.extraRenderCallbacks) cb(pass);
|
|
18944
19265
|
this.gizmoManager.render(pass);
|
|
18945
19266
|
this.renderer.endFrame();
|
|
18946
19267
|
if (this.hotspotManager.isActive()) {
|
|
@@ -19183,6 +19504,16 @@ class App {
|
|
|
19183
19504
|
getRenderer() {
|
|
19184
19505
|
return this.renderer;
|
|
19185
19506
|
}
|
|
19507
|
+
/**
|
|
19508
|
+
* 注册额外的渲染回调(在场景辅助之后、gizmo 之前执行)
|
|
19509
|
+
*/
|
|
19510
|
+
addRenderCallback(cb) {
|
|
19511
|
+
this.extraRenderCallbacks.push(cb);
|
|
19512
|
+
}
|
|
19513
|
+
removeRenderCallback(cb) {
|
|
19514
|
+
const idx = this.extraRenderCallbacks.indexOf(cb);
|
|
19515
|
+
if (idx >= 0) this.extraRenderCallbacks.splice(idx, 1);
|
|
19516
|
+
}
|
|
19186
19517
|
/**
|
|
19187
19518
|
* CPU splat 拾取:在屏幕坐标 (clientX, clientY) 处找最近的 splat,返回世界坐标或 null
|
|
19188
19519
|
*/
|
|
@@ -19582,6 +19913,7 @@ export {
|
|
|
19582
19913
|
SHMode,
|
|
19583
19914
|
SceneAidsRenderer,
|
|
19584
19915
|
SceneManager,
|
|
19916
|
+
SelectionVolumeRenderer,
|
|
19585
19917
|
SkyboxRenderer,
|
|
19586
19918
|
SplatBoundingBoxProvider,
|
|
19587
19919
|
SplatEditor,
|