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