@d5techs/3dgs-lib 1.4.0 → 1.4.1
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 +1869 -4
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +1869 -4
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/App.d.ts +3 -0
- package/dist/editor/EditHistory.d.ts +13 -0
- package/dist/editor/EditOps.d.ts +56 -0
- package/dist/editor/Events.d.ts +13 -0
- package/dist/editor/SplatEditor.d.ts +80 -0
- package/dist/editor/SplatExporter.d.ts +6 -0
- package/dist/editor/SplatState.d.ts +18 -0
- package/dist/editor/index.d.ts +7 -0
- package/dist/editor/tools/BrushSelection.d.ts +21 -0
- package/dist/editor/tools/EyedropperSelection.d.ts +21 -0
- package/dist/editor/tools/FloodSelection.d.ts +22 -0
- package/dist/editor/tools/LassoSelection.d.ts +24 -0
- package/dist/editor/tools/PolygonSelection.d.ts +22 -0
- package/dist/editor/tools/RectSelection.d.ts +24 -0
- package/dist/editor/tools/ToolManager.d.ts +14 -0
- package/dist/gs/GSSplatRenderer.d.ts +21 -0
- package/dist/index.d.ts +7 -0
- package/package.json +1 -1
package/dist/3dgs-lib.js
CHANGED
|
@@ -7125,6 +7125,12 @@ const _GSSplatRenderer = class _GSSplatRenderer {
|
|
|
7125
7125
|
__publicField(this, "sortStateInitialized", false);
|
|
7126
7126
|
__publicField(this, "sortFrequency", 1);
|
|
7127
7127
|
__publicField(this, "frameCounter", 0);
|
|
7128
|
+
// 编辑器状态
|
|
7129
|
+
__publicField(this, "editorStateBuffer", null);
|
|
7130
|
+
__publicField(this, "editorPipeline", null);
|
|
7131
|
+
__publicField(this, "editorBindGroupLayout", null);
|
|
7132
|
+
__publicField(this, "editorBindGroup", null);
|
|
7133
|
+
__publicField(this, "editorEnabled", false);
|
|
7128
7134
|
// 深度法线Pass依赖资源
|
|
7129
7135
|
__publicField(this, "depthNormalPipeline", null);
|
|
7130
7136
|
__publicField(this, "depthRT", null);
|
|
@@ -7485,6 +7491,7 @@ const _GSSplatRenderer = class _GSSplatRenderer {
|
|
|
7485
7491
|
{ binding: 2, resource: { buffer: this.sorter.getIndicesBuffer() } }
|
|
7486
7492
|
]
|
|
7487
7493
|
});
|
|
7494
|
+
if (this.editorEnabled) this.rebuildEditorBindGroup();
|
|
7488
7495
|
}
|
|
7489
7496
|
setCompactData(compactData) {
|
|
7490
7497
|
const device = this.renderer.device;
|
|
@@ -7531,6 +7538,7 @@ const _GSSplatRenderer = class _GSSplatRenderer {
|
|
|
7531
7538
|
{ binding: 2, resource: { buffer: this.sorter.getIndicesBuffer() } }
|
|
7532
7539
|
]
|
|
7533
7540
|
});
|
|
7541
|
+
if (this.editorEnabled) this.rebuildEditorBindGroup();
|
|
7534
7542
|
}
|
|
7535
7543
|
render(pass) {
|
|
7536
7544
|
if (this.splatCount === 0 || !this.bindGroup || !this.sorter) {
|
|
@@ -7574,8 +7582,13 @@ const _GSSplatRenderer = class _GSSplatRenderer {
|
|
|
7574
7582
|
});
|
|
7575
7583
|
this.sorter.sort();
|
|
7576
7584
|
}
|
|
7577
|
-
|
|
7578
|
-
|
|
7585
|
+
if (this.editorEnabled && this.editorPipeline && this.editorBindGroup) {
|
|
7586
|
+
pass.setPipeline(this.editorPipeline);
|
|
7587
|
+
pass.setBindGroup(0, this.editorBindGroup);
|
|
7588
|
+
} else {
|
|
7589
|
+
pass.setPipeline(this.pipeline);
|
|
7590
|
+
pass.setBindGroup(0, this.bindGroup);
|
|
7591
|
+
}
|
|
7579
7592
|
pass.drawIndirect(this.sorter.getDrawIndirectBuffer(), 0);
|
|
7580
7593
|
}
|
|
7581
7594
|
getSplatCount() {
|
|
@@ -7824,7 +7837,358 @@ const _GSSplatRenderer = class _GSSplatRenderer {
|
|
|
7824
7837
|
maxSplatCount: 0
|
|
7825
7838
|
};
|
|
7826
7839
|
}
|
|
7840
|
+
// ============================================
|
|
7841
|
+
// 编辑器状态管理
|
|
7842
|
+
// ============================================
|
|
7843
|
+
/**
|
|
7844
|
+
* 启用编辑器模式,传入每个 splat 的状态数据
|
|
7845
|
+
*/
|
|
7846
|
+
setEditorState(stateData) {
|
|
7847
|
+
var _a2;
|
|
7848
|
+
if (this.splatCount === 0 || !this.splatBuffer) return;
|
|
7849
|
+
const device = this.renderer.device;
|
|
7850
|
+
const bufSize = Math.ceil(stateData.byteLength / 4) * 4;
|
|
7851
|
+
if (!this.editorStateBuffer || this.editorStateBuffer.size < bufSize) {
|
|
7852
|
+
(_a2 = this.editorStateBuffer) == null ? void 0 : _a2.destroy();
|
|
7853
|
+
this.editorStateBuffer = device.createBuffer({
|
|
7854
|
+
size: bufSize,
|
|
7855
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
7856
|
+
});
|
|
7857
|
+
}
|
|
7858
|
+
const aligned = this.alignStateData(stateData);
|
|
7859
|
+
device.queue.writeBuffer(this.editorStateBuffer, 0, aligned);
|
|
7860
|
+
if (!this.editorPipeline) {
|
|
7861
|
+
this.createEditorPipeline();
|
|
7862
|
+
}
|
|
7863
|
+
this.rebuildEditorBindGroup();
|
|
7864
|
+
this.editorEnabled = true;
|
|
7865
|
+
}
|
|
7866
|
+
/**
|
|
7867
|
+
* 更新编辑器状态数据
|
|
7868
|
+
*/
|
|
7869
|
+
updateEditorState(stateData) {
|
|
7870
|
+
if (!this.editorStateBuffer || !this.editorEnabled) return;
|
|
7871
|
+
const aligned = this.alignStateData(stateData);
|
|
7872
|
+
this.renderer.device.queue.writeBuffer(this.editorStateBuffer, 0, aligned);
|
|
7873
|
+
}
|
|
7874
|
+
alignStateData(data) {
|
|
7875
|
+
const alignedSize = Math.ceil(data.byteLength / 4) * 4;
|
|
7876
|
+
const buf = new ArrayBuffer(alignedSize);
|
|
7877
|
+
new Uint8Array(buf).set(data);
|
|
7878
|
+
return buf;
|
|
7879
|
+
}
|
|
7880
|
+
/**
|
|
7881
|
+
* 禁用编辑器模式
|
|
7882
|
+
*/
|
|
7883
|
+
clearEditorState() {
|
|
7884
|
+
this.editorEnabled = false;
|
|
7885
|
+
this.editorBindGroup = null;
|
|
7886
|
+
if (this.editorStateBuffer) {
|
|
7887
|
+
this.editorStateBuffer.destroy();
|
|
7888
|
+
this.editorStateBuffer = null;
|
|
7889
|
+
}
|
|
7890
|
+
}
|
|
7891
|
+
createEditorPipeline() {
|
|
7892
|
+
const device = this.renderer.device;
|
|
7893
|
+
const editorShaderCode = this.buildEditorShader();
|
|
7894
|
+
const shaderModule = device.createShaderModule({ code: editorShaderCode });
|
|
7895
|
+
this.editorBindGroupLayout = device.createBindGroupLayout({
|
|
7896
|
+
entries: [
|
|
7897
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
7898
|
+
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
|
|
7899
|
+
{ binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
|
|
7900
|
+
{ binding: 3, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } }
|
|
7901
|
+
]
|
|
7902
|
+
});
|
|
7903
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
7904
|
+
bindGroupLayouts: [this.editorBindGroupLayout]
|
|
7905
|
+
});
|
|
7906
|
+
this.editorPipeline = device.createRenderPipeline({
|
|
7907
|
+
layout: pipelineLayout,
|
|
7908
|
+
vertex: { module: shaderModule, entryPoint: "vs_editor" },
|
|
7909
|
+
fragment: {
|
|
7910
|
+
module: shaderModule,
|
|
7911
|
+
entryPoint: "fs_editor",
|
|
7912
|
+
targets: [{
|
|
7913
|
+
format: this.renderer.format,
|
|
7914
|
+
blend: {
|
|
7915
|
+
color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
7916
|
+
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
|
|
7917
|
+
}
|
|
7918
|
+
}]
|
|
7919
|
+
},
|
|
7920
|
+
primitive: { topology: "triangle-strip" },
|
|
7921
|
+
depthStencil: {
|
|
7922
|
+
format: this.renderer.depthFormat,
|
|
7923
|
+
depthWriteEnabled: true,
|
|
7924
|
+
depthCompare: "always"
|
|
7925
|
+
}
|
|
7926
|
+
});
|
|
7927
|
+
}
|
|
7928
|
+
rebuildEditorBindGroup() {
|
|
7929
|
+
if (!this.editorBindGroupLayout || !this.splatBuffer || !this.sorter || !this.editorStateBuffer) return;
|
|
7930
|
+
this.editorBindGroup = this.renderer.device.createBindGroup({
|
|
7931
|
+
layout: this.editorBindGroupLayout,
|
|
7932
|
+
entries: [
|
|
7933
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer } },
|
|
7934
|
+
{ binding: 1, resource: { buffer: this.splatBuffer } },
|
|
7935
|
+
{ binding: 2, resource: { buffer: this.sorter.getIndicesBuffer() } },
|
|
7936
|
+
{ binding: 3, resource: { buffer: this.editorStateBuffer } }
|
|
7937
|
+
]
|
|
7938
|
+
});
|
|
7939
|
+
}
|
|
7940
|
+
buildEditorShader() {
|
|
7941
|
+
return (
|
|
7942
|
+
/* wgsl */
|
|
7943
|
+
`
|
|
7944
|
+
const SQRT_8: f32 = 2.82842712475;
|
|
7945
|
+
const SH_C0: f32 = 0.28209479177387814;
|
|
7946
|
+
const SH_C1: f32 = 0.4886025119029199;
|
|
7947
|
+
const SH_C2_0: f32 = 1.0925484305920792;
|
|
7948
|
+
const SH_C2_1: f32 = -1.0925484305920792;
|
|
7949
|
+
const SH_C2_2: f32 = 0.31539156525252005;
|
|
7950
|
+
const SH_C2_3: f32 = -1.0925484305920792;
|
|
7951
|
+
const SH_C2_4: f32 = 0.5462742152960396;
|
|
7952
|
+
const SH_C3_0: f32 = -0.5900435899266435;
|
|
7953
|
+
const SH_C3_1: f32 = 2.890611442640554;
|
|
7954
|
+
const SH_C3_2: f32 = -0.4570457994644658;
|
|
7955
|
+
const SH_C3_3: f32 = 0.3731763325901154;
|
|
7956
|
+
const SH_C3_4: f32 = -0.4570457994644658;
|
|
7957
|
+
const SH_C3_5: f32 = 1.4453057213202769;
|
|
7958
|
+
const SH_C3_6: f32 = -0.5900435899266435;
|
|
7959
|
+
const LOW_PASS_FILTER: f32 = 0.3;
|
|
7960
|
+
const ALPHA_CULL_THRESHOLD: f32 = 0.00392156863;
|
|
7961
|
+
const GAUSSIAN_K: f32 = 4.0;
|
|
7962
|
+
const EXP_NEG_K: f32 = 0.01831563888873418;
|
|
7963
|
+
const INV_ONE_MINUS_EXP_NEG_K: f32 = 1.01865736036377408;
|
|
7964
|
+
|
|
7965
|
+
const STATE_SELECTED: u32 = 1u;
|
|
7966
|
+
const STATE_HIDDEN: u32 = 2u;
|
|
7967
|
+
const STATE_DELETED: u32 = 4u;
|
|
7968
|
+
|
|
7969
|
+
struct Uniforms {
|
|
7970
|
+
view: mat4x4<f32>,
|
|
7971
|
+
proj: mat4x4<f32>,
|
|
7972
|
+
model: mat4x4<f32>,
|
|
7973
|
+
cameraPos: vec3<f32>,
|
|
7974
|
+
_pad: f32,
|
|
7975
|
+
screenSize: vec2<f32>,
|
|
7976
|
+
_pad2: vec2<f32>,
|
|
7977
|
+
}
|
|
7978
|
+
|
|
7979
|
+
struct Splat {
|
|
7980
|
+
mean: vec3<f32>, _pad0: f32,
|
|
7981
|
+
scale: vec3<f32>, _pad1: f32,
|
|
7982
|
+
rotation: vec4<f32>,
|
|
7983
|
+
colorDC: vec3<f32>,
|
|
7984
|
+
opacity: f32,
|
|
7985
|
+
sh1: array<f32, 9>,
|
|
7986
|
+
sh2: array<f32, 15>,
|
|
7987
|
+
sh3: array<f32, 21>,
|
|
7988
|
+
_pad2: array<f32, 3>,
|
|
7989
|
+
}
|
|
7990
|
+
|
|
7991
|
+
fn evalSH(splat: Splat, dir: vec3<f32>) -> vec3<f32> {
|
|
7992
|
+
let x = dir.x; let y = dir.y; let z = dir.z;
|
|
7993
|
+
var result = vec3<f32>(0.0);
|
|
7994
|
+
result += (-SH_C1 * y) * vec3<f32>(splat.sh1[0], splat.sh1[1], splat.sh1[2]);
|
|
7995
|
+
result += ( SH_C1 * z) * vec3<f32>(splat.sh1[3], splat.sh1[4], splat.sh1[5]);
|
|
7996
|
+
result += (-SH_C1 * x) * vec3<f32>(splat.sh1[6], splat.sh1[7], splat.sh1[8]);
|
|
7997
|
+
let xx = x * x; let yy = y * y; let zz = z * z;
|
|
7998
|
+
let xy = x * y; let yz = y * z; let xz = x * z;
|
|
7999
|
+
result += (SH_C2_0 * xy) * vec3<f32>(splat.sh2[0], splat.sh2[1], splat.sh2[2]);
|
|
8000
|
+
result += (SH_C2_1 * yz) * vec3<f32>(splat.sh2[3], splat.sh2[4], splat.sh2[5]);
|
|
8001
|
+
result += (SH_C2_2 * (2.0 * zz - xx - yy)) * vec3<f32>(splat.sh2[6], splat.sh2[7], splat.sh2[8]);
|
|
8002
|
+
result += (SH_C2_3 * xz) * vec3<f32>(splat.sh2[9], splat.sh2[10], splat.sh2[11]);
|
|
8003
|
+
result += (SH_C2_4 * (xx - yy)) * vec3<f32>(splat.sh2[12], splat.sh2[13], splat.sh2[14]);
|
|
8004
|
+
result += (SH_C3_0 * y * (3.0 * xx - yy)) * vec3<f32>(splat.sh3[0], splat.sh3[1], splat.sh3[2]);
|
|
8005
|
+
result += (SH_C3_1 * xy * z) * vec3<f32>(splat.sh3[3], splat.sh3[4], splat.sh3[5]);
|
|
8006
|
+
result += (SH_C3_2 * y * (4.0 * zz - xx - yy)) * vec3<f32>(splat.sh3[6], splat.sh3[7], splat.sh3[8]);
|
|
8007
|
+
result += (SH_C3_3 * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) * vec3<f32>(splat.sh3[9], splat.sh3[10], splat.sh3[11]);
|
|
8008
|
+
result += (SH_C3_4 * x * (4.0 * zz - xx - yy)) * vec3<f32>(splat.sh3[12], splat.sh3[13], splat.sh3[14]);
|
|
8009
|
+
result += (SH_C3_5 * z * (xx - yy)) * vec3<f32>(splat.sh3[15], splat.sh3[16], splat.sh3[17]);
|
|
8010
|
+
result += (SH_C3_6 * x * (xx - 3.0 * yy)) * vec3<f32>(splat.sh3[18], splat.sh3[19], splat.sh3[20]);
|
|
8011
|
+
return result;
|
|
8012
|
+
}
|
|
8013
|
+
|
|
8014
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
8015
|
+
@group(0) @binding(1) var<storage, read> splats: array<Splat>;
|
|
8016
|
+
@group(0) @binding(2) var<storage, read> sortedIndices: array<u32>;
|
|
8017
|
+
@group(0) @binding(3) var<storage, read> splatState: array<u32>;
|
|
8018
|
+
|
|
8019
|
+
fn getState(splatIndex: u32) -> u32 {
|
|
8020
|
+
let wordIndex = splatIndex / 4u;
|
|
8021
|
+
let byteIndex = splatIndex % 4u;
|
|
8022
|
+
return (splatState[wordIndex] >> (byteIndex * 8u)) & 0xFFu;
|
|
8023
|
+
}
|
|
8024
|
+
|
|
8025
|
+
struct VertexOutput {
|
|
8026
|
+
@builtin(position) position: vec4<f32>,
|
|
8027
|
+
@location(0) fragPos: vec2<f32>,
|
|
8028
|
+
@location(1) color: vec3<f32>,
|
|
8029
|
+
@location(2) opacity: f32,
|
|
8030
|
+
@location(3) @interpolate(flat) stateFlags: u32,
|
|
8031
|
+
}
|
|
8032
|
+
|
|
8033
|
+
const QUAD_POSITIONS = array<vec2<f32>, 4>(
|
|
8034
|
+
vec2<f32>(-1.0, -1.0), vec2<f32>(-1.0, 1.0),
|
|
8035
|
+
vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
|
|
8036
|
+
);
|
|
8037
|
+
|
|
8038
|
+
fn computeClipFactor(alpha: f32) -> f32 {
|
|
8039
|
+
if alpha <= ALPHA_CULL_THRESHOLD { return 0.0; }
|
|
8040
|
+
let threshold = 1.0 / (255.0 * alpha);
|
|
8041
|
+
if threshold >= 1.0 { return 0.0; }
|
|
8042
|
+
let targetExp = threshold * (1.0 - EXP_NEG_K) + EXP_NEG_K;
|
|
8043
|
+
if targetExp >= 1.0 { return 0.0; }
|
|
8044
|
+
let A = -log(targetExp) / GAUSSIAN_K;
|
|
8045
|
+
return min(1.0, sqrt(A));
|
|
8046
|
+
}
|
|
8047
|
+
|
|
8048
|
+
fn quatToMat3(q: vec4<f32>) -> mat3x3<f32> {
|
|
8049
|
+
let r = q.x; let x = q.y; let y = q.z; let z = q.w;
|
|
8050
|
+
return mat3x3<f32>(
|
|
8051
|
+
vec3<f32>(1.0 - 2.0 * (y * y + z * z), 2.0 * (x * y + r * z), 2.0 * (x * z - r * y)),
|
|
8052
|
+
vec3<f32>(2.0 * (x * y - r * z), 1.0 - 2.0 * (x * x + z * z), 2.0 * (y * z + r * x)),
|
|
8053
|
+
vec3<f32>(2.0 * (x * z + r * y), 2.0 * (y * z - r * x), 1.0 - 2.0 * (x * x + y * y))
|
|
8054
|
+
);
|
|
8055
|
+
}
|
|
8056
|
+
|
|
8057
|
+
fn computeCovariance3D(scale: vec3<f32>, rotation: vec4<f32>) -> mat3x3<f32> {
|
|
8058
|
+
let R = quatToMat3(rotation);
|
|
8059
|
+
let S = mat3x3<f32>(vec3<f32>(scale.x, 0.0, 0.0), vec3<f32>(0.0, scale.y, 0.0), vec3<f32>(0.0, 0.0, scale.z));
|
|
8060
|
+
let M = R * S;
|
|
8061
|
+
return M * transpose(M);
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
fn projectCovariance(cov3d: mat3x3<f32>, viewCenter: vec4<f32>, focal: vec2<f32>, modelViewMat: mat4x4<f32>) -> vec3<f32> {
|
|
8065
|
+
let v = viewCenter.xyz;
|
|
8066
|
+
let s = 1.0 / (v.z * v.z);
|
|
8067
|
+
let J = mat3x3<f32>(
|
|
8068
|
+
vec3<f32>(focal.x / v.z, 0.0, 0.0),
|
|
8069
|
+
vec3<f32>(0.0, focal.y / v.z, 0.0),
|
|
8070
|
+
vec3<f32>(-(focal.x * v.x) * s, -(focal.y * v.y) * s, 0.0)
|
|
8071
|
+
);
|
|
8072
|
+
let W = mat3x3<f32>(
|
|
8073
|
+
vec3<f32>(modelViewMat[0][0], modelViewMat[0][1], modelViewMat[0][2]),
|
|
8074
|
+
vec3<f32>(modelViewMat[1][0], modelViewMat[1][1], modelViewMat[1][2]),
|
|
8075
|
+
vec3<f32>(modelViewMat[2][0], modelViewMat[2][1], modelViewMat[2][2])
|
|
8076
|
+
);
|
|
8077
|
+
let T = J * W;
|
|
8078
|
+
let cov2d = T * cov3d * transpose(T);
|
|
8079
|
+
return vec3<f32>(cov2d[0][0], cov2d[0][1], cov2d[1][1]);
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
struct ExtentResult { basis: vec4<f32>, adjustedOpacity: f32, }
|
|
8083
|
+
|
|
8084
|
+
fn computeExtentBasisAA(cov2dIn: vec3<f32>, opacity: f32, viewportSize: vec2<f32>) -> ExtentResult {
|
|
8085
|
+
var result: ExtentResult;
|
|
8086
|
+
var cov2d = cov2dIn;
|
|
8087
|
+
var alpha = opacity;
|
|
8088
|
+
cov2d.x += LOW_PASS_FILTER;
|
|
8089
|
+
cov2d.z += LOW_PASS_FILTER;
|
|
8090
|
+
let a = cov2d.x; let d = cov2d.z; let b = cov2d.y;
|
|
8091
|
+
let mid = 0.5 * (a + d);
|
|
8092
|
+
let radius = length(vec2<f32>((a - d) * 0.5, b));
|
|
8093
|
+
let lambda1 = mid + radius;
|
|
8094
|
+
let lambda2 = mid - radius;
|
|
8095
|
+
if lambda2 <= 0.0 { result.basis = vec4<f32>(0.0); result.adjustedOpacity = 0.0; return result; }
|
|
8096
|
+
let vmin = min(1024.0, min(viewportSize.x, viewportSize.y));
|
|
8097
|
+
let l1 = min(2.0 * sqrt(2.0 * lambda1), vmin);
|
|
8098
|
+
let l2 = min(2.0 * sqrt(2.0 * lambda2), vmin);
|
|
8099
|
+
if l1 < 0.5 && l2 < 0.5 { result.basis = vec4<f32>(0.0); result.adjustedOpacity = 0.0; return result; }
|
|
8100
|
+
let diagVec = normalize(vec2<f32>(b, lambda1 - a));
|
|
8101
|
+
result.basis = vec4<f32>(diagVec * l1, vec2<f32>(diagVec.y, -diagVec.x) * l2);
|
|
8102
|
+
result.adjustedOpacity = alpha;
|
|
8103
|
+
return result;
|
|
8104
|
+
}
|
|
8105
|
+
|
|
8106
|
+
@vertex
|
|
8107
|
+
fn vs_editor(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {
|
|
8108
|
+
var output: VertexOutput;
|
|
8109
|
+
let splatIndex = sortedIndices[instanceIndex];
|
|
8110
|
+
let splat = splats[splatIndex];
|
|
8111
|
+
let quadPos = QUAD_POSITIONS[vertexIndex];
|
|
8112
|
+
|
|
8113
|
+
let state = getState(splatIndex);
|
|
8114
|
+
output.stateFlags = state;
|
|
8115
|
+
|
|
8116
|
+
// 隐藏和删除的 splat 直接剔除
|
|
8117
|
+
if (state & STATE_HIDDEN) != 0u || (state & STATE_DELETED) != 0u {
|
|
8118
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output;
|
|
8119
|
+
}
|
|
8120
|
+
|
|
8121
|
+
if splat.opacity < ALPHA_CULL_THRESHOLD { output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output; }
|
|
8122
|
+
let quatNormSqr = dot(splat.rotation, splat.rotation);
|
|
8123
|
+
if quatNormSqr < 1e-6 { output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output; }
|
|
8124
|
+
|
|
8125
|
+
let worldPos = uniforms.model * vec4<f32>(splat.mean, 1.0);
|
|
8126
|
+
let viewPos = uniforms.view * worldPos;
|
|
8127
|
+
let clipPos = uniforms.proj * viewPos;
|
|
8128
|
+
if viewPos.z >= 0.0 { output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output; }
|
|
8129
|
+
let pW = 1.0 / (clipPos.w + 0.0000001);
|
|
8130
|
+
let ndcPos = clipPos * pW;
|
|
8131
|
+
let clipBound = 1.3;
|
|
8132
|
+
if abs(ndcPos.x) > clipBound || abs(ndcPos.y) > clipBound || ndcPos.z < -0.2 || ndcPos.z > 1.0 {
|
|
8133
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output;
|
|
8134
|
+
}
|
|
8135
|
+
|
|
8136
|
+
let cov3d = computeCovariance3D(splat.scale, splat.rotation);
|
|
8137
|
+
let focal = vec2<f32>(abs(uniforms.proj[0][0]) * 0.5 * uniforms.screenSize.x, abs(uniforms.proj[1][1]) * 0.5 * uniforms.screenSize.y);
|
|
8138
|
+
let modelViewMat = uniforms.view * uniforms.model;
|
|
8139
|
+
let cov2d = projectCovariance(cov3d, viewPos, focal, modelViewMat);
|
|
8140
|
+
let extentResult = computeExtentBasisAA(cov2d, splat.opacity, uniforms.screenSize);
|
|
8141
|
+
let basis = extentResult.basis;
|
|
8142
|
+
let adjustedOpacity = extentResult.adjustedOpacity;
|
|
8143
|
+
if basis.x == 0.0 && basis.y == 0.0 && basis.z == 0.0 && basis.w == 0.0 {
|
|
8144
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output;
|
|
8145
|
+
}
|
|
8146
|
+
let maxExtentPixels = max(length(basis.xy), length(basis.zw));
|
|
8147
|
+
let pixelToClip = 2.0 * vec2<f32>(clipPos.w, clipPos.w) / uniforms.screenSize;
|
|
8148
|
+
let splatExtentClip = vec2<f32>(maxExtentPixels, maxExtentPixels) * pixelToClip;
|
|
8149
|
+
if any((abs(clipPos.xy) - splatExtentClip) > vec2<f32>(clipPos.w, clipPos.w)) {
|
|
8150
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output;
|
|
8151
|
+
}
|
|
8152
|
+
let clipFactor = computeClipFactor(adjustedOpacity);
|
|
8153
|
+
if clipFactor <= 0.0 { output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0); return output; }
|
|
8154
|
+
let basisViewport = vec2<f32>(1.0 / uniforms.screenSize.x, 1.0 / uniforms.screenSize.y);
|
|
8155
|
+
let basisVector1 = basis.xy * clipFactor;
|
|
8156
|
+
let basisVector2 = basis.zw * clipFactor;
|
|
8157
|
+
let ndcOffset = (quadPos.x * basisVector1 + quadPos.y * basisVector2) * basisViewport * 2.0;
|
|
8158
|
+
output.position = vec4<f32>(ndcPos.xy + ndcOffset, ndcPos.z, 1.0);
|
|
8159
|
+
output.fragPos = quadPos * clipFactor;
|
|
8160
|
+
let shDir = normalize(vec3<f32>(
|
|
8161
|
+
dot(worldPos.xyz - uniforms.cameraPos, uniforms.model[0].xyz),
|
|
8162
|
+
dot(worldPos.xyz - uniforms.cameraPos, uniforms.model[1].xyz),
|
|
8163
|
+
dot(worldPos.xyz - uniforms.cameraPos, uniforms.model[2].xyz)
|
|
8164
|
+
));
|
|
8165
|
+
output.color = splat.colorDC + evalSH(splat, shDir);
|
|
8166
|
+
output.opacity = adjustedOpacity;
|
|
8167
|
+
return output;
|
|
8168
|
+
}
|
|
8169
|
+
|
|
8170
|
+
@fragment
|
|
8171
|
+
fn fs_editor(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
8172
|
+
if input.opacity <= 0.0 { discard; }
|
|
8173
|
+
let A = dot(input.fragPos, input.fragPos);
|
|
8174
|
+
if A > 1.0 { discard; }
|
|
8175
|
+
let weight = (exp(-GAUSSIAN_K * A) - EXP_NEG_K) * INV_ONE_MINUS_EXP_NEG_K;
|
|
8176
|
+
let opacity = weight * input.opacity;
|
|
8177
|
+
if opacity < ALPHA_CULL_THRESHOLD { discard; }
|
|
8178
|
+
var color = max(input.color, vec3<f32>(0.0));
|
|
8179
|
+
|
|
8180
|
+
// 选中高亮:叠加橙色调
|
|
8181
|
+
if (input.stateFlags & STATE_SELECTED) != 0u {
|
|
8182
|
+
color = mix(color, vec3<f32>(1.0, 0.5, 0.1), 0.4);
|
|
8183
|
+
}
|
|
8184
|
+
|
|
8185
|
+
return vec4<f32>(color * opacity, opacity);
|
|
8186
|
+
}
|
|
8187
|
+
`
|
|
8188
|
+
);
|
|
8189
|
+
}
|
|
7827
8190
|
destroy() {
|
|
8191
|
+
this.clearEditorState();
|
|
7828
8192
|
if (this.splatBuffer) {
|
|
7829
8193
|
this.splatBuffer.destroy();
|
|
7830
8194
|
this.splatBuffer = null;
|
|
@@ -13594,7 +13958,7 @@ class HotspotManager {
|
|
|
13594
13958
|
const device = this.renderer.device;
|
|
13595
13959
|
const segments = 48;
|
|
13596
13960
|
const outerRadius = this.indicatorBaseRadius;
|
|
13597
|
-
const innerRadius = outerRadius * 0.
|
|
13961
|
+
const innerRadius = outerRadius * 0.78;
|
|
13598
13962
|
const vertexCount = segments * 2;
|
|
13599
13963
|
const vertexData = new Float32Array(vertexCount * 6);
|
|
13600
13964
|
for (let i = 0; i < segments; i++) {
|
|
@@ -13662,7 +14026,7 @@ class HotspotManager {
|
|
|
13662
14026
|
showIndicator() {
|
|
13663
14027
|
if (!this.indicatorMesh || this.indicatorAdded) return;
|
|
13664
14028
|
const material = {
|
|
13665
|
-
baseColorFactor: [
|
|
14029
|
+
baseColorFactor: [0, 0.75, 1, 0.95],
|
|
13666
14030
|
baseColorTexture: null,
|
|
13667
14031
|
metallicFactor: 0,
|
|
13668
14032
|
roughnessFactor: 1,
|
|
@@ -14210,6 +14574,1490 @@ class HotspotManager {
|
|
|
14210
14574
|
this.hotspots = [];
|
|
14211
14575
|
}
|
|
14212
14576
|
}
|
|
14577
|
+
var State = /* @__PURE__ */ ((State2) => {
|
|
14578
|
+
State2[State2["selected"] = 1] = "selected";
|
|
14579
|
+
State2[State2["hidden"] = 2] = "hidden";
|
|
14580
|
+
State2[State2["deleted"] = 4] = "deleted";
|
|
14581
|
+
return State2;
|
|
14582
|
+
})(State || {});
|
|
14583
|
+
class SplatState {
|
|
14584
|
+
constructor(count) {
|
|
14585
|
+
__publicField(this, "count");
|
|
14586
|
+
__publicField(this, "data");
|
|
14587
|
+
__publicField(this, "_numSelected", 0);
|
|
14588
|
+
__publicField(this, "_numHidden", 0);
|
|
14589
|
+
__publicField(this, "_numDeleted", 0);
|
|
14590
|
+
this.count = count;
|
|
14591
|
+
this.data = new Uint8Array(count);
|
|
14592
|
+
}
|
|
14593
|
+
get numSelected() {
|
|
14594
|
+
return this._numSelected;
|
|
14595
|
+
}
|
|
14596
|
+
get numHidden() {
|
|
14597
|
+
return this._numHidden;
|
|
14598
|
+
}
|
|
14599
|
+
get numDeleted() {
|
|
14600
|
+
return this._numDeleted;
|
|
14601
|
+
}
|
|
14602
|
+
recalcCounts() {
|
|
14603
|
+
let sel = 0, hid = 0, del = 0;
|
|
14604
|
+
for (let i = 0; i < this.count; i++) {
|
|
14605
|
+
const v = this.data[i];
|
|
14606
|
+
if (v & 1) sel++;
|
|
14607
|
+
if (v & 2) hid++;
|
|
14608
|
+
if (v & 4) del++;
|
|
14609
|
+
}
|
|
14610
|
+
this._numSelected = sel;
|
|
14611
|
+
this._numHidden = hid;
|
|
14612
|
+
this._numDeleted = del;
|
|
14613
|
+
}
|
|
14614
|
+
clear() {
|
|
14615
|
+
this.data.fill(0);
|
|
14616
|
+
this._numSelected = 0;
|
|
14617
|
+
this._numHidden = 0;
|
|
14618
|
+
this._numDeleted = 0;
|
|
14619
|
+
}
|
|
14620
|
+
}
|
|
14621
|
+
class EditHistory {
|
|
14622
|
+
constructor(onChange) {
|
|
14623
|
+
__publicField(this, "history", []);
|
|
14624
|
+
__publicField(this, "cursor", 0);
|
|
14625
|
+
__publicField(this, "onChange");
|
|
14626
|
+
this.onChange = onChange;
|
|
14627
|
+
}
|
|
14628
|
+
add(op) {
|
|
14629
|
+
var _a2, _b2, _c;
|
|
14630
|
+
while (this.cursor < this.history.length) {
|
|
14631
|
+
(_b2 = (_a2 = this.history.pop()) == null ? void 0 : _a2.destroy) == null ? void 0 : _b2.call(_a2);
|
|
14632
|
+
}
|
|
14633
|
+
this.history.push(op);
|
|
14634
|
+
op.do();
|
|
14635
|
+
this.cursor++;
|
|
14636
|
+
(_c = this.onChange) == null ? void 0 : _c.call(this);
|
|
14637
|
+
}
|
|
14638
|
+
canUndo() {
|
|
14639
|
+
return this.cursor > 0;
|
|
14640
|
+
}
|
|
14641
|
+
canRedo() {
|
|
14642
|
+
return this.cursor < this.history.length;
|
|
14643
|
+
}
|
|
14644
|
+
undo() {
|
|
14645
|
+
var _a2;
|
|
14646
|
+
if (!this.canUndo()) return;
|
|
14647
|
+
this.history[--this.cursor].undo();
|
|
14648
|
+
(_a2 = this.onChange) == null ? void 0 : _a2.call(this);
|
|
14649
|
+
}
|
|
14650
|
+
redo() {
|
|
14651
|
+
var _a2;
|
|
14652
|
+
if (!this.canRedo()) return;
|
|
14653
|
+
this.history[this.cursor++].do();
|
|
14654
|
+
(_a2 = this.onChange) == null ? void 0 : _a2.call(this);
|
|
14655
|
+
}
|
|
14656
|
+
clear() {
|
|
14657
|
+
var _a2;
|
|
14658
|
+
this.history.forEach((op) => {
|
|
14659
|
+
var _a3;
|
|
14660
|
+
return (_a3 = op.destroy) == null ? void 0 : _a3.call(op);
|
|
14661
|
+
});
|
|
14662
|
+
this.history = [];
|
|
14663
|
+
this.cursor = 0;
|
|
14664
|
+
(_a2 = this.onChange) == null ? void 0 : _a2.call(this);
|
|
14665
|
+
}
|
|
14666
|
+
}
|
|
14667
|
+
class StateOp {
|
|
14668
|
+
constructor(name, state, indices, mask, op) {
|
|
14669
|
+
__publicField(this, "name");
|
|
14670
|
+
__publicField(this, "state");
|
|
14671
|
+
__publicField(this, "indices");
|
|
14672
|
+
__publicField(this, "mask");
|
|
14673
|
+
__publicField(this, "op");
|
|
14674
|
+
this.name = name;
|
|
14675
|
+
this.state = state;
|
|
14676
|
+
this.indices = indices;
|
|
14677
|
+
this.mask = mask;
|
|
14678
|
+
this.op = op;
|
|
14679
|
+
}
|
|
14680
|
+
apply(op) {
|
|
14681
|
+
const d = this.state.data;
|
|
14682
|
+
const { mask, indices } = this;
|
|
14683
|
+
switch (op) {
|
|
14684
|
+
case 0:
|
|
14685
|
+
for (let i = 0; i < indices.length; i++) d[indices[i]] |= mask;
|
|
14686
|
+
break;
|
|
14687
|
+
case 1:
|
|
14688
|
+
for (let i = 0; i < indices.length; i++) d[indices[i]] &= ~mask;
|
|
14689
|
+
break;
|
|
14690
|
+
case 2:
|
|
14691
|
+
for (let i = 0; i < indices.length; i++) d[indices[i]] ^= mask;
|
|
14692
|
+
break;
|
|
14693
|
+
}
|
|
14694
|
+
}
|
|
14695
|
+
do() {
|
|
14696
|
+
this.apply(this.op);
|
|
14697
|
+
this.state.recalcCounts();
|
|
14698
|
+
}
|
|
14699
|
+
undo() {
|
|
14700
|
+
const undoOp = this.op === 2 ? 2 : this.op === 0 ? 1 : 0;
|
|
14701
|
+
this.apply(undoOp);
|
|
14702
|
+
this.state.recalcCounts();
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
class SelectOp {
|
|
14706
|
+
constructor(state, op, predicate) {
|
|
14707
|
+
__publicField(this, "name", "select");
|
|
14708
|
+
__publicField(this, "inner");
|
|
14709
|
+
const d = state.data;
|
|
14710
|
+
const indices = [];
|
|
14711
|
+
if (op === "add") {
|
|
14712
|
+
for (let i = 0; i < state.count; i++) {
|
|
14713
|
+
if (predicate(i) && d[i] === 0) indices.push(i);
|
|
14714
|
+
}
|
|
14715
|
+
this.inner = new StateOp(
|
|
14716
|
+
"selectAdd",
|
|
14717
|
+
state,
|
|
14718
|
+
new Uint32Array(indices),
|
|
14719
|
+
State.selected,
|
|
14720
|
+
0
|
|
14721
|
+
/* SET */
|
|
14722
|
+
);
|
|
14723
|
+
} else if (op === "remove") {
|
|
14724
|
+
for (let i = 0; i < state.count; i++) {
|
|
14725
|
+
if (predicate(i) && d[i] === State.selected) indices.push(i);
|
|
14726
|
+
}
|
|
14727
|
+
this.inner = new StateOp(
|
|
14728
|
+
"selectRemove",
|
|
14729
|
+
state,
|
|
14730
|
+
new Uint32Array(indices),
|
|
14731
|
+
State.selected,
|
|
14732
|
+
1
|
|
14733
|
+
/* CLEAR */
|
|
14734
|
+
);
|
|
14735
|
+
} else {
|
|
14736
|
+
for (let i = 0; i < state.count; i++) {
|
|
14737
|
+
if (d[i] === State.selected !== predicate(i)) indices.push(i);
|
|
14738
|
+
}
|
|
14739
|
+
this.inner = new StateOp(
|
|
14740
|
+
"selectSet",
|
|
14741
|
+
state,
|
|
14742
|
+
new Uint32Array(indices),
|
|
14743
|
+
State.selected,
|
|
14744
|
+
2
|
|
14745
|
+
/* TOGGLE */
|
|
14746
|
+
);
|
|
14747
|
+
}
|
|
14748
|
+
}
|
|
14749
|
+
do() {
|
|
14750
|
+
this.inner.do();
|
|
14751
|
+
}
|
|
14752
|
+
undo() {
|
|
14753
|
+
this.inner.undo();
|
|
14754
|
+
}
|
|
14755
|
+
}
|
|
14756
|
+
class SelectAllOp {
|
|
14757
|
+
constructor(state) {
|
|
14758
|
+
__publicField(this, "name", "selectAll");
|
|
14759
|
+
__publicField(this, "inner");
|
|
14760
|
+
const indices = [];
|
|
14761
|
+
for (let i = 0; i < state.count; i++) {
|
|
14762
|
+
if (state.data[i] === 0) indices.push(i);
|
|
14763
|
+
}
|
|
14764
|
+
this.inner = new StateOp(
|
|
14765
|
+
"selectAll",
|
|
14766
|
+
state,
|
|
14767
|
+
new Uint32Array(indices),
|
|
14768
|
+
State.selected,
|
|
14769
|
+
0
|
|
14770
|
+
/* SET */
|
|
14771
|
+
);
|
|
14772
|
+
}
|
|
14773
|
+
do() {
|
|
14774
|
+
this.inner.do();
|
|
14775
|
+
}
|
|
14776
|
+
undo() {
|
|
14777
|
+
this.inner.undo();
|
|
14778
|
+
}
|
|
14779
|
+
}
|
|
14780
|
+
class SelectNoneOp {
|
|
14781
|
+
constructor(state) {
|
|
14782
|
+
__publicField(this, "name", "selectNone");
|
|
14783
|
+
__publicField(this, "inner");
|
|
14784
|
+
const indices = [];
|
|
14785
|
+
for (let i = 0; i < state.count; i++) {
|
|
14786
|
+
if (state.data[i] === State.selected) indices.push(i);
|
|
14787
|
+
}
|
|
14788
|
+
this.inner = new StateOp(
|
|
14789
|
+
"selectNone",
|
|
14790
|
+
state,
|
|
14791
|
+
new Uint32Array(indices),
|
|
14792
|
+
State.selected,
|
|
14793
|
+
1
|
|
14794
|
+
/* CLEAR */
|
|
14795
|
+
);
|
|
14796
|
+
}
|
|
14797
|
+
do() {
|
|
14798
|
+
this.inner.do();
|
|
14799
|
+
}
|
|
14800
|
+
undo() {
|
|
14801
|
+
this.inner.undo();
|
|
14802
|
+
}
|
|
14803
|
+
}
|
|
14804
|
+
class SelectInvertOp {
|
|
14805
|
+
constructor(state) {
|
|
14806
|
+
__publicField(this, "name", "selectInvert");
|
|
14807
|
+
__publicField(this, "inner");
|
|
14808
|
+
const indices = [];
|
|
14809
|
+
for (let i = 0; i < state.count; i++) {
|
|
14810
|
+
if ((state.data[i] & (State.hidden | State.deleted)) === 0) indices.push(i);
|
|
14811
|
+
}
|
|
14812
|
+
this.inner = new StateOp(
|
|
14813
|
+
"selectInvert",
|
|
14814
|
+
state,
|
|
14815
|
+
new Uint32Array(indices),
|
|
14816
|
+
State.selected,
|
|
14817
|
+
2
|
|
14818
|
+
/* TOGGLE */
|
|
14819
|
+
);
|
|
14820
|
+
}
|
|
14821
|
+
do() {
|
|
14822
|
+
this.inner.do();
|
|
14823
|
+
}
|
|
14824
|
+
undo() {
|
|
14825
|
+
this.inner.undo();
|
|
14826
|
+
}
|
|
14827
|
+
}
|
|
14828
|
+
class DeleteSelectionOp {
|
|
14829
|
+
constructor(state) {
|
|
14830
|
+
__publicField(this, "name", "deleteSelection");
|
|
14831
|
+
__publicField(this, "inner");
|
|
14832
|
+
const indices = [];
|
|
14833
|
+
for (let i = 0; i < state.count; i++) {
|
|
14834
|
+
if (state.data[i] === State.selected) indices.push(i);
|
|
14835
|
+
}
|
|
14836
|
+
this.inner = new StateOp(
|
|
14837
|
+
"deleteSelection",
|
|
14838
|
+
state,
|
|
14839
|
+
new Uint32Array(indices),
|
|
14840
|
+
State.deleted,
|
|
14841
|
+
0
|
|
14842
|
+
/* SET */
|
|
14843
|
+
);
|
|
14844
|
+
}
|
|
14845
|
+
do() {
|
|
14846
|
+
this.inner.do();
|
|
14847
|
+
}
|
|
14848
|
+
undo() {
|
|
14849
|
+
this.inner.undo();
|
|
14850
|
+
}
|
|
14851
|
+
}
|
|
14852
|
+
class HideSelectionOp {
|
|
14853
|
+
constructor(state) {
|
|
14854
|
+
__publicField(this, "name", "hideSelection");
|
|
14855
|
+
__publicField(this, "inner");
|
|
14856
|
+
const indices = [];
|
|
14857
|
+
for (let i = 0; i < state.count; i++) {
|
|
14858
|
+
if (state.data[i] === State.selected) indices.push(i);
|
|
14859
|
+
}
|
|
14860
|
+
this.inner = new StateOp(
|
|
14861
|
+
"hideSelection",
|
|
14862
|
+
state,
|
|
14863
|
+
new Uint32Array(indices),
|
|
14864
|
+
State.hidden,
|
|
14865
|
+
0
|
|
14866
|
+
/* SET */
|
|
14867
|
+
);
|
|
14868
|
+
}
|
|
14869
|
+
do() {
|
|
14870
|
+
this.inner.do();
|
|
14871
|
+
}
|
|
14872
|
+
undo() {
|
|
14873
|
+
this.inner.undo();
|
|
14874
|
+
}
|
|
14875
|
+
}
|
|
14876
|
+
class UnhideAllOp {
|
|
14877
|
+
constructor(state) {
|
|
14878
|
+
__publicField(this, "name", "unhideAll");
|
|
14879
|
+
__publicField(this, "inner");
|
|
14880
|
+
const indices = [];
|
|
14881
|
+
for (let i = 0; i < state.count; i++) {
|
|
14882
|
+
if ((state.data[i] & (State.hidden | State.deleted)) === State.hidden) indices.push(i);
|
|
14883
|
+
}
|
|
14884
|
+
this.inner = new StateOp(
|
|
14885
|
+
"unhideAll",
|
|
14886
|
+
state,
|
|
14887
|
+
new Uint32Array(indices),
|
|
14888
|
+
State.hidden,
|
|
14889
|
+
1
|
|
14890
|
+
/* CLEAR */
|
|
14891
|
+
);
|
|
14892
|
+
}
|
|
14893
|
+
do() {
|
|
14894
|
+
this.inner.do();
|
|
14895
|
+
}
|
|
14896
|
+
undo() {
|
|
14897
|
+
this.inner.undo();
|
|
14898
|
+
}
|
|
14899
|
+
}
|
|
14900
|
+
class ToolManager {
|
|
14901
|
+
constructor(onToolChanged) {
|
|
14902
|
+
__publicField(this, "tools", /* @__PURE__ */ new Map());
|
|
14903
|
+
__publicField(this, "_active", null);
|
|
14904
|
+
__publicField(this, "onToolChanged");
|
|
14905
|
+
this.onToolChanged = onToolChanged;
|
|
14906
|
+
}
|
|
14907
|
+
get active() {
|
|
14908
|
+
return this._active;
|
|
14909
|
+
}
|
|
14910
|
+
register(name, tool) {
|
|
14911
|
+
this.tools.set(name, tool);
|
|
14912
|
+
}
|
|
14913
|
+
activate(name) {
|
|
14914
|
+
var _a2, _b2, _c;
|
|
14915
|
+
if (name === this._active) {
|
|
14916
|
+
if (name) this.activate(null);
|
|
14917
|
+
return;
|
|
14918
|
+
}
|
|
14919
|
+
if (this._active) {
|
|
14920
|
+
(_a2 = this.tools.get(this._active)) == null ? void 0 : _a2.deactivate();
|
|
14921
|
+
}
|
|
14922
|
+
this._active = name;
|
|
14923
|
+
if (this._active) {
|
|
14924
|
+
(_b2 = this.tools.get(this._active)) == null ? void 0 : _b2.activate();
|
|
14925
|
+
}
|
|
14926
|
+
(_c = this.onToolChanged) == null ? void 0 : _c.call(this, this._active);
|
|
14927
|
+
}
|
|
14928
|
+
deactivateAll() {
|
|
14929
|
+
this.activate(null);
|
|
14930
|
+
}
|
|
14931
|
+
}
|
|
14932
|
+
class RectSelection {
|
|
14933
|
+
constructor(parent, onSelect) {
|
|
14934
|
+
__publicField(this, "parent");
|
|
14935
|
+
__publicField(this, "svg");
|
|
14936
|
+
__publicField(this, "rect");
|
|
14937
|
+
__publicField(this, "onSelect");
|
|
14938
|
+
__publicField(this, "start", { x: 0, y: 0 });
|
|
14939
|
+
__publicField(this, "end", { x: 0, y: 0 });
|
|
14940
|
+
__publicField(this, "dragId");
|
|
14941
|
+
__publicField(this, "dragMoved", false);
|
|
14942
|
+
this.parent = parent;
|
|
14943
|
+
this.onSelect = onSelect;
|
|
14944
|
+
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
14945
|
+
this.svg.classList.add("tool-svg");
|
|
14946
|
+
this.svg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none;z-index:10;";
|
|
14947
|
+
parent.appendChild(this.svg);
|
|
14948
|
+
this.rect = document.createElementNS(this.svg.namespaceURI, "rect");
|
|
14949
|
+
this.rect.setAttribute("fill", "rgba(255,102,0,0.15)");
|
|
14950
|
+
this.rect.setAttribute("stroke", "#f60");
|
|
14951
|
+
this.rect.setAttribute("stroke-width", "1.5");
|
|
14952
|
+
this.svg.appendChild(this.rect);
|
|
14953
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
14954
|
+
this.pointermove = this.pointermove.bind(this);
|
|
14955
|
+
this.pointerup = this.pointerup.bind(this);
|
|
14956
|
+
}
|
|
14957
|
+
activate() {
|
|
14958
|
+
this.svg.style.display = "block";
|
|
14959
|
+
this.parent.style.cursor = "crosshair";
|
|
14960
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
14961
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
14962
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
14963
|
+
}
|
|
14964
|
+
deactivate() {
|
|
14965
|
+
if (this.dragId !== void 0) this.dragEnd();
|
|
14966
|
+
this.svg.style.display = "none";
|
|
14967
|
+
this.parent.style.cursor = "";
|
|
14968
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
14969
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
14970
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
14971
|
+
}
|
|
14972
|
+
updateRect() {
|
|
14973
|
+
const x = Math.min(this.start.x, this.end.x);
|
|
14974
|
+
const y = Math.min(this.start.y, this.end.y);
|
|
14975
|
+
this.rect.setAttribute("x", x.toString());
|
|
14976
|
+
this.rect.setAttribute("y", y.toString());
|
|
14977
|
+
this.rect.setAttribute("width", Math.abs(this.start.x - this.end.x).toString());
|
|
14978
|
+
this.rect.setAttribute("height", Math.abs(this.start.y - this.end.y).toString());
|
|
14979
|
+
}
|
|
14980
|
+
pointerdown(e) {
|
|
14981
|
+
if (this.dragId !== void 0) return;
|
|
14982
|
+
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
14983
|
+
e.preventDefault();
|
|
14984
|
+
e.stopPropagation();
|
|
14985
|
+
this.dragId = e.pointerId;
|
|
14986
|
+
this.dragMoved = false;
|
|
14987
|
+
this.parent.setPointerCapture(this.dragId);
|
|
14988
|
+
this.start.x = this.end.x = e.offsetX;
|
|
14989
|
+
this.start.y = this.end.y = e.offsetY;
|
|
14990
|
+
this.updateRect();
|
|
14991
|
+
this.svg.style.pointerEvents = "none";
|
|
14992
|
+
this.rect.style.display = "block";
|
|
14993
|
+
}
|
|
14994
|
+
pointermove(e) {
|
|
14995
|
+
if (e.pointerId !== this.dragId) return;
|
|
14996
|
+
e.preventDefault();
|
|
14997
|
+
e.stopPropagation();
|
|
14998
|
+
this.dragMoved = true;
|
|
14999
|
+
this.end.x = e.offsetX;
|
|
15000
|
+
this.end.y = e.offsetY;
|
|
15001
|
+
this.updateRect();
|
|
15002
|
+
}
|
|
15003
|
+
dragEnd() {
|
|
15004
|
+
if (this.dragId !== void 0) {
|
|
15005
|
+
this.parent.releasePointerCapture(this.dragId);
|
|
15006
|
+
this.dragId = void 0;
|
|
15007
|
+
}
|
|
15008
|
+
this.rect.style.display = "none";
|
|
15009
|
+
}
|
|
15010
|
+
pointerup(e) {
|
|
15011
|
+
if (e.pointerId !== this.dragId) return;
|
|
15012
|
+
e.preventDefault();
|
|
15013
|
+
e.stopPropagation();
|
|
15014
|
+
const w = this.parent.clientWidth;
|
|
15015
|
+
const h = this.parent.clientHeight;
|
|
15016
|
+
const selectOp = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15017
|
+
if (this.dragMoved) {
|
|
15018
|
+
this.onSelect(selectOp, {
|
|
15019
|
+
startX: Math.min(this.start.x, this.end.x) / w,
|
|
15020
|
+
startY: Math.min(this.start.y, this.end.y) / h,
|
|
15021
|
+
endX: Math.max(this.start.x, this.end.x) / w,
|
|
15022
|
+
endY: Math.max(this.start.y, this.end.y) / h
|
|
15023
|
+
});
|
|
15024
|
+
} else {
|
|
15025
|
+
const px = e.offsetX / w;
|
|
15026
|
+
const py = e.offsetY / h;
|
|
15027
|
+
const r = 4 / Math.max(w, h);
|
|
15028
|
+
this.onSelect(selectOp, {
|
|
15029
|
+
startX: px - r,
|
|
15030
|
+
startY: py - r,
|
|
15031
|
+
endX: px + r,
|
|
15032
|
+
endY: py + r
|
|
15033
|
+
});
|
|
15034
|
+
}
|
|
15035
|
+
this.dragEnd();
|
|
15036
|
+
}
|
|
15037
|
+
}
|
|
15038
|
+
class LassoSelection {
|
|
15039
|
+
constructor(parent, maskCanvas, maskCtx, onMaskSelect) {
|
|
15040
|
+
__publicField(this, "parent");
|
|
15041
|
+
__publicField(this, "svg");
|
|
15042
|
+
__publicField(this, "polygon");
|
|
15043
|
+
__publicField(this, "maskCanvas");
|
|
15044
|
+
__publicField(this, "maskCtx");
|
|
15045
|
+
__publicField(this, "onMaskSelect");
|
|
15046
|
+
__publicField(this, "points", []);
|
|
15047
|
+
__publicField(this, "currentPoint", null);
|
|
15048
|
+
__publicField(this, "lastPointTime", 0);
|
|
15049
|
+
__publicField(this, "dragId");
|
|
15050
|
+
this.parent = parent;
|
|
15051
|
+
this.maskCanvas = maskCanvas;
|
|
15052
|
+
this.maskCtx = maskCtx;
|
|
15053
|
+
this.onMaskSelect = onMaskSelect;
|
|
15054
|
+
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
15055
|
+
this.svg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none;z-index:10;";
|
|
15056
|
+
parent.appendChild(this.svg);
|
|
15057
|
+
this.polygon = document.createElementNS(this.svg.namespaceURI, "polygon");
|
|
15058
|
+
this.polygon.setAttribute("fill", "rgba(255,102,0,0.15)");
|
|
15059
|
+
this.polygon.setAttribute("stroke", "#f60");
|
|
15060
|
+
this.polygon.setAttribute("stroke-width", "1.5");
|
|
15061
|
+
this.svg.appendChild(this.polygon);
|
|
15062
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
15063
|
+
this.pointermove = this.pointermove.bind(this);
|
|
15064
|
+
this.pointerup = this.pointerup.bind(this);
|
|
15065
|
+
}
|
|
15066
|
+
activate() {
|
|
15067
|
+
this.svg.style.display = "block";
|
|
15068
|
+
this.parent.style.cursor = "crosshair";
|
|
15069
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
15070
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
15071
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
15072
|
+
}
|
|
15073
|
+
deactivate() {
|
|
15074
|
+
if (this.dragId !== void 0) this.dragEnd();
|
|
15075
|
+
this.svg.style.display = "none";
|
|
15076
|
+
this.parent.style.cursor = "";
|
|
15077
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
15078
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
15079
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
15080
|
+
this.points = [];
|
|
15081
|
+
this.paint();
|
|
15082
|
+
}
|
|
15083
|
+
dist(a, b) {
|
|
15084
|
+
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
15085
|
+
}
|
|
15086
|
+
paint() {
|
|
15087
|
+
const all = this.currentPoint ? [...this.points, this.currentPoint] : this.points;
|
|
15088
|
+
this.polygon.setAttribute("points", all.map((p) => `${p.x},${p.y}`).join(" "));
|
|
15089
|
+
}
|
|
15090
|
+
update(e) {
|
|
15091
|
+
this.currentPoint = { x: e.offsetX, y: e.offsetY };
|
|
15092
|
+
const last = this.points[this.points.length - 1];
|
|
15093
|
+
const distance = last ? this.dist(this.currentPoint, last) : 0;
|
|
15094
|
+
const millis = Date.now() - this.lastPointTime;
|
|
15095
|
+
if (this.dragId !== void 0 && (this.points.length === 0 || distance > 20 || millis > 500 && distance > 2 || millis > 200 && distance > 10)) {
|
|
15096
|
+
this.points.push(this.currentPoint);
|
|
15097
|
+
this.lastPointTime = Date.now();
|
|
15098
|
+
}
|
|
15099
|
+
this.paint();
|
|
15100
|
+
}
|
|
15101
|
+
commitSelection(e) {
|
|
15102
|
+
const { maskCanvas: canvas, maskCtx: ctx, parent } = this;
|
|
15103
|
+
if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
|
|
15104
|
+
canvas.width = parent.clientWidth;
|
|
15105
|
+
canvas.height = parent.clientHeight;
|
|
15106
|
+
}
|
|
15107
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
15108
|
+
ctx.beginPath();
|
|
15109
|
+
ctx.fillStyle = "#f60";
|
|
15110
|
+
this.points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
15111
|
+
ctx.closePath();
|
|
15112
|
+
ctx.fill();
|
|
15113
|
+
const op = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15114
|
+
this.onMaskSelect(op, canvas, ctx);
|
|
15115
|
+
}
|
|
15116
|
+
pointerdown(e) {
|
|
15117
|
+
if (this.dragId !== void 0) return;
|
|
15118
|
+
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
15119
|
+
e.preventDefault();
|
|
15120
|
+
e.stopPropagation();
|
|
15121
|
+
this.dragId = e.pointerId;
|
|
15122
|
+
this.parent.setPointerCapture(this.dragId);
|
|
15123
|
+
this.update(e);
|
|
15124
|
+
}
|
|
15125
|
+
pointermove(e) {
|
|
15126
|
+
if (this.dragId !== void 0) {
|
|
15127
|
+
e.preventDefault();
|
|
15128
|
+
e.stopPropagation();
|
|
15129
|
+
}
|
|
15130
|
+
this.update(e);
|
|
15131
|
+
}
|
|
15132
|
+
dragEnd() {
|
|
15133
|
+
if (this.dragId !== void 0) {
|
|
15134
|
+
this.parent.releasePointerCapture(this.dragId);
|
|
15135
|
+
this.dragId = void 0;
|
|
15136
|
+
}
|
|
15137
|
+
}
|
|
15138
|
+
pointerup(e) {
|
|
15139
|
+
if (e.pointerId !== this.dragId) return;
|
|
15140
|
+
e.preventDefault();
|
|
15141
|
+
e.stopPropagation();
|
|
15142
|
+
this.commitSelection(e);
|
|
15143
|
+
this.dragEnd();
|
|
15144
|
+
this.points = [];
|
|
15145
|
+
this.paint();
|
|
15146
|
+
}
|
|
15147
|
+
}
|
|
15148
|
+
class PolygonSelection {
|
|
15149
|
+
constructor(parent, maskCanvas, maskCtx, onMaskSelect) {
|
|
15150
|
+
__publicField(this, "parent");
|
|
15151
|
+
__publicField(this, "svg");
|
|
15152
|
+
__publicField(this, "polyline");
|
|
15153
|
+
__publicField(this, "maskCanvas");
|
|
15154
|
+
__publicField(this, "maskCtx");
|
|
15155
|
+
__publicField(this, "onMaskSelect");
|
|
15156
|
+
__publicField(this, "points", []);
|
|
15157
|
+
__publicField(this, "currentPoint", null);
|
|
15158
|
+
this.parent = parent;
|
|
15159
|
+
this.maskCanvas = maskCanvas;
|
|
15160
|
+
this.maskCtx = maskCtx;
|
|
15161
|
+
this.onMaskSelect = onMaskSelect;
|
|
15162
|
+
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
15163
|
+
this.svg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none;z-index:10;";
|
|
15164
|
+
parent.appendChild(this.svg);
|
|
15165
|
+
this.polyline = document.createElementNS(this.svg.namespaceURI, "polyline");
|
|
15166
|
+
this.polyline.setAttribute("fill", "rgba(255,102,0,0.15)");
|
|
15167
|
+
this.polyline.setAttribute("stroke", "#f60");
|
|
15168
|
+
this.polyline.setAttribute("stroke-width", "1.5");
|
|
15169
|
+
this.svg.appendChild(this.polyline);
|
|
15170
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
15171
|
+
this.pointermove = this.pointermove.bind(this);
|
|
15172
|
+
this.pointerup = this.pointerup.bind(this);
|
|
15173
|
+
this.dblclick = this.dblclick.bind(this);
|
|
15174
|
+
}
|
|
15175
|
+
activate() {
|
|
15176
|
+
this.svg.style.display = "block";
|
|
15177
|
+
this.parent.style.cursor = "crosshair";
|
|
15178
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
15179
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
15180
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
15181
|
+
this.parent.addEventListener("dblclick", this.dblclick);
|
|
15182
|
+
}
|
|
15183
|
+
deactivate() {
|
|
15184
|
+
this.svg.style.display = "none";
|
|
15185
|
+
this.parent.style.cursor = "";
|
|
15186
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
15187
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
15188
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
15189
|
+
this.parent.removeEventListener("dblclick", this.dblclick);
|
|
15190
|
+
this.points = [];
|
|
15191
|
+
this.paint();
|
|
15192
|
+
}
|
|
15193
|
+
dist(a, b) {
|
|
15194
|
+
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
15195
|
+
}
|
|
15196
|
+
isClosed() {
|
|
15197
|
+
return this.points.length > 1 && !!this.currentPoint && this.dist(this.currentPoint, this.points[0]) < 8;
|
|
15198
|
+
}
|
|
15199
|
+
paint() {
|
|
15200
|
+
const all = [...this.points, this.currentPoint].filter(Boolean);
|
|
15201
|
+
this.polyline.setAttribute("points", all.map((p) => `${p.x},${p.y}`).join(" "));
|
|
15202
|
+
this.polyline.setAttribute("stroke", this.isClosed() ? "#fa6" : "#f60");
|
|
15203
|
+
}
|
|
15204
|
+
commitSelection(e) {
|
|
15205
|
+
const { maskCanvas: canvas, maskCtx: ctx, parent } = this;
|
|
15206
|
+
if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
|
|
15207
|
+
canvas.width = parent.clientWidth;
|
|
15208
|
+
canvas.height = parent.clientHeight;
|
|
15209
|
+
}
|
|
15210
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
15211
|
+
ctx.beginPath();
|
|
15212
|
+
ctx.fillStyle = "#f60";
|
|
15213
|
+
this.points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
15214
|
+
ctx.closePath();
|
|
15215
|
+
ctx.fill();
|
|
15216
|
+
const op = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15217
|
+
this.onMaskSelect(op, canvas, ctx);
|
|
15218
|
+
this.points = [];
|
|
15219
|
+
this.paint();
|
|
15220
|
+
}
|
|
15221
|
+
pointerdown(e) {
|
|
15222
|
+
if (this.points.length > 0 || (e.pointerType === "mouse" ? e.button === 0 : e.isPrimary)) {
|
|
15223
|
+
e.preventDefault();
|
|
15224
|
+
e.stopPropagation();
|
|
15225
|
+
}
|
|
15226
|
+
}
|
|
15227
|
+
pointermove(e) {
|
|
15228
|
+
this.currentPoint = { x: e.offsetX, y: e.offsetY };
|
|
15229
|
+
if (this.points.length > 0) this.paint();
|
|
15230
|
+
}
|
|
15231
|
+
pointerup(e) {
|
|
15232
|
+
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
15233
|
+
e.preventDefault();
|
|
15234
|
+
e.stopPropagation();
|
|
15235
|
+
if (this.isClosed()) {
|
|
15236
|
+
this.commitSelection(e);
|
|
15237
|
+
} else if (this.currentPoint && (this.points.length === 0 || this.dist(this.points[this.points.length - 1], this.currentPoint) > 0)) {
|
|
15238
|
+
this.points.push(this.currentPoint);
|
|
15239
|
+
}
|
|
15240
|
+
}
|
|
15241
|
+
dblclick(e) {
|
|
15242
|
+
e.preventDefault();
|
|
15243
|
+
e.stopPropagation();
|
|
15244
|
+
if (this.points.length > 2) {
|
|
15245
|
+
this.commitSelection(e);
|
|
15246
|
+
}
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
class BrushSelection {
|
|
15250
|
+
constructor(parent, maskCanvas, maskCtx, onMaskSelect) {
|
|
15251
|
+
__publicField(this, "parent");
|
|
15252
|
+
__publicField(this, "svg");
|
|
15253
|
+
__publicField(this, "circle");
|
|
15254
|
+
__publicField(this, "maskCanvas");
|
|
15255
|
+
__publicField(this, "maskCtx");
|
|
15256
|
+
__publicField(this, "onMaskSelect");
|
|
15257
|
+
__publicField(this, "radius", 40);
|
|
15258
|
+
__publicField(this, "prev", { x: 0, y: 0 });
|
|
15259
|
+
__publicField(this, "dragId");
|
|
15260
|
+
this.parent = parent;
|
|
15261
|
+
this.maskCanvas = maskCanvas;
|
|
15262
|
+
this.maskCtx = maskCtx;
|
|
15263
|
+
this.onMaskSelect = onMaskSelect;
|
|
15264
|
+
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
15265
|
+
this.svg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none;z-index:10;";
|
|
15266
|
+
parent.appendChild(this.svg);
|
|
15267
|
+
this.circle = document.createElementNS(this.svg.namespaceURI, "circle");
|
|
15268
|
+
this.circle.setAttribute("r", this.radius.toString());
|
|
15269
|
+
this.circle.setAttribute("fill", "none");
|
|
15270
|
+
this.circle.setAttribute("stroke", "#f60");
|
|
15271
|
+
this.circle.setAttribute("stroke-width", "1.5");
|
|
15272
|
+
this.circle.setAttribute("stroke-dasharray", "4 2");
|
|
15273
|
+
this.svg.appendChild(this.circle);
|
|
15274
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
15275
|
+
this.pointermove = this.pointermove.bind(this);
|
|
15276
|
+
this.pointerup = this.pointerup.bind(this);
|
|
15277
|
+
this.wheel = this.wheel.bind(this);
|
|
15278
|
+
}
|
|
15279
|
+
activate() {
|
|
15280
|
+
this.svg.style.display = "block";
|
|
15281
|
+
this.parent.style.cursor = "none";
|
|
15282
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
15283
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
15284
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
15285
|
+
this.parent.addEventListener("wheel", this.wheel);
|
|
15286
|
+
}
|
|
15287
|
+
deactivate() {
|
|
15288
|
+
if (this.dragId !== void 0) this.dragEnd();
|
|
15289
|
+
this.svg.style.display = "none";
|
|
15290
|
+
this.maskCanvas.style.display = "none";
|
|
15291
|
+
this.parent.style.cursor = "";
|
|
15292
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
15293
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
15294
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
15295
|
+
this.parent.removeEventListener("wheel", this.wheel);
|
|
15296
|
+
}
|
|
15297
|
+
update(e) {
|
|
15298
|
+
const x = e.offsetX, y = e.offsetY;
|
|
15299
|
+
this.circle.setAttribute("cx", x.toString());
|
|
15300
|
+
this.circle.setAttribute("cy", y.toString());
|
|
15301
|
+
if (this.dragId !== void 0) {
|
|
15302
|
+
this.maskCtx.beginPath();
|
|
15303
|
+
this.maskCtx.strokeStyle = "rgba(255,102,0,0.5)";
|
|
15304
|
+
this.maskCtx.lineCap = "round";
|
|
15305
|
+
this.maskCtx.lineWidth = this.radius * 2;
|
|
15306
|
+
this.maskCtx.moveTo(this.prev.x, this.prev.y);
|
|
15307
|
+
this.maskCtx.lineTo(x, y);
|
|
15308
|
+
this.maskCtx.stroke();
|
|
15309
|
+
this.prev.x = x;
|
|
15310
|
+
this.prev.y = y;
|
|
15311
|
+
}
|
|
15312
|
+
}
|
|
15313
|
+
pointerdown(e) {
|
|
15314
|
+
if (this.dragId !== void 0) return;
|
|
15315
|
+
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
15316
|
+
e.preventDefault();
|
|
15317
|
+
e.stopPropagation();
|
|
15318
|
+
this.dragId = e.pointerId;
|
|
15319
|
+
this.parent.setPointerCapture(this.dragId);
|
|
15320
|
+
const { maskCanvas: canvas, maskCtx: ctx, parent } = this;
|
|
15321
|
+
if (canvas.width !== parent.clientWidth || canvas.height !== parent.clientHeight) {
|
|
15322
|
+
canvas.width = parent.clientWidth;
|
|
15323
|
+
canvas.height = parent.clientHeight;
|
|
15324
|
+
}
|
|
15325
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
15326
|
+
canvas.style.display = "block";
|
|
15327
|
+
this.prev.x = e.offsetX;
|
|
15328
|
+
this.prev.y = e.offsetY;
|
|
15329
|
+
this.update(e);
|
|
15330
|
+
}
|
|
15331
|
+
pointermove(e) {
|
|
15332
|
+
if (this.dragId !== void 0) {
|
|
15333
|
+
e.preventDefault();
|
|
15334
|
+
e.stopPropagation();
|
|
15335
|
+
}
|
|
15336
|
+
this.update(e);
|
|
15337
|
+
}
|
|
15338
|
+
dragEnd() {
|
|
15339
|
+
if (this.dragId !== void 0) {
|
|
15340
|
+
this.parent.releasePointerCapture(this.dragId);
|
|
15341
|
+
this.dragId = void 0;
|
|
15342
|
+
}
|
|
15343
|
+
}
|
|
15344
|
+
pointerup(e) {
|
|
15345
|
+
if (e.pointerId !== this.dragId) return;
|
|
15346
|
+
e.preventDefault();
|
|
15347
|
+
e.stopPropagation();
|
|
15348
|
+
this.dragEnd();
|
|
15349
|
+
const op = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15350
|
+
this.onMaskSelect(op, this.maskCanvas, this.maskCtx);
|
|
15351
|
+
this.maskCanvas.style.display = "none";
|
|
15352
|
+
}
|
|
15353
|
+
wheel(e) {
|
|
15354
|
+
const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
|
|
15355
|
+
if (delta > 0) {
|
|
15356
|
+
this.radius = Math.max(1, this.radius / 1.05);
|
|
15357
|
+
} else {
|
|
15358
|
+
this.radius = Math.min(500, this.radius * 1.05);
|
|
15359
|
+
}
|
|
15360
|
+
this.circle.setAttribute("r", this.radius.toString());
|
|
15361
|
+
e.preventDefault();
|
|
15362
|
+
e.stopPropagation();
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
class FloodSelection {
|
|
15366
|
+
constructor(parent, maskCanvas, maskCtx, onMaskSelect, getOffscreenImage) {
|
|
15367
|
+
__publicField(this, "parent");
|
|
15368
|
+
__publicField(this, "maskCanvas");
|
|
15369
|
+
__publicField(this, "maskCtx");
|
|
15370
|
+
__publicField(this, "onMaskSelect");
|
|
15371
|
+
__publicField(this, "getOffscreenImage");
|
|
15372
|
+
__publicField(this, "thresholdEl");
|
|
15373
|
+
__publicField(this, "threshold", 0.2);
|
|
15374
|
+
__publicField(this, "clicked", false);
|
|
15375
|
+
__publicField(this, "imageData", null);
|
|
15376
|
+
this.parent = parent;
|
|
15377
|
+
this.maskCanvas = maskCanvas;
|
|
15378
|
+
this.maskCtx = maskCtx;
|
|
15379
|
+
this.onMaskSelect = onMaskSelect;
|
|
15380
|
+
this.getOffscreenImage = getOffscreenImage;
|
|
15381
|
+
this.thresholdEl = document.createElement("div");
|
|
15382
|
+
this.thresholdEl.style.cssText = "position:absolute;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(20,20,35,0.9);color:#e0e0e0;padding:8px 16px;border-radius:8px;font-size:13px;display:none;z-index:20;gap:8px;align-items:center;";
|
|
15383
|
+
this.thresholdEl.innerHTML = `
|
|
15384
|
+
<label style="margin-right:8px;">阈值:</label>
|
|
15385
|
+
<input type="range" min="0.01" max="0.99" step="0.01" value="${this.threshold}" style="width:120px;vertical-align:middle;">
|
|
15386
|
+
<span style="margin-left:8px;min-width:40px;">${this.threshold.toFixed(2)}</span>
|
|
15387
|
+
`;
|
|
15388
|
+
parent.appendChild(this.thresholdEl);
|
|
15389
|
+
const slider = this.thresholdEl.querySelector("input");
|
|
15390
|
+
const span = this.thresholdEl.querySelector("span");
|
|
15391
|
+
slider.addEventListener("input", () => {
|
|
15392
|
+
this.threshold = parseFloat(slider.value);
|
|
15393
|
+
span.textContent = this.threshold.toFixed(2);
|
|
15394
|
+
});
|
|
15395
|
+
slider.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
15396
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
15397
|
+
this.pointermove = this.pointermove.bind(this);
|
|
15398
|
+
this.pointerup = this.pointerup.bind(this);
|
|
15399
|
+
}
|
|
15400
|
+
activate() {
|
|
15401
|
+
this.parent.style.cursor = "crosshair";
|
|
15402
|
+
this.thresholdEl.style.display = "flex";
|
|
15403
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
15404
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
15405
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
15406
|
+
}
|
|
15407
|
+
deactivate() {
|
|
15408
|
+
this.parent.style.cursor = "";
|
|
15409
|
+
this.thresholdEl.style.display = "none";
|
|
15410
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
15411
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
15412
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
15413
|
+
}
|
|
15414
|
+
pointerdown(e) {
|
|
15415
|
+
if (e.pointerType === "mouse" ? e.button === 0 : e.isPrimary) {
|
|
15416
|
+
this.clicked = true;
|
|
15417
|
+
}
|
|
15418
|
+
}
|
|
15419
|
+
pointermove(_e) {
|
|
15420
|
+
this.clicked = false;
|
|
15421
|
+
}
|
|
15422
|
+
pointerup(e) {
|
|
15423
|
+
if (!this.clicked || !(e.pointerType === "mouse" ? e.button === 0 : e.isPrimary)) return;
|
|
15424
|
+
this.clicked = false;
|
|
15425
|
+
const width = this.parent.clientWidth;
|
|
15426
|
+
const height = this.parent.clientHeight;
|
|
15427
|
+
const point = { x: Math.floor(e.offsetX), y: Math.floor(e.offsetY) };
|
|
15428
|
+
const data = this.getOffscreenImage(width, height);
|
|
15429
|
+
if (!data) return;
|
|
15430
|
+
const { maskCanvas: canvas, maskCtx: ctx } = this;
|
|
15431
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
15432
|
+
canvas.width = width;
|
|
15433
|
+
canvas.height = height;
|
|
15434
|
+
this.imageData = ctx.createImageData(width, height);
|
|
15435
|
+
}
|
|
15436
|
+
if (!this.imageData) this.imageData = ctx.createImageData(width, height);
|
|
15437
|
+
const PIXEL = 4;
|
|
15438
|
+
const startIdx = (point.y * width + point.x) * PIXEL;
|
|
15439
|
+
const pickedAlpha = data[startIdx + 3];
|
|
15440
|
+
const d = this.imageData.data;
|
|
15441
|
+
d.fill(102);
|
|
15442
|
+
const stack = [{ ...point }];
|
|
15443
|
+
const thresholdByte = this.threshold * 255;
|
|
15444
|
+
while (stack.length > 0) {
|
|
15445
|
+
const cur = stack.pop();
|
|
15446
|
+
const idx = (cur.y * width + cur.x) * PIXEL;
|
|
15447
|
+
if (Math.abs(data[idx + 3] - pickedAlpha) < thresholdByte) {
|
|
15448
|
+
d[idx + 0] = 255;
|
|
15449
|
+
d[idx + 2] = 0;
|
|
15450
|
+
d[idx + 3] = 255;
|
|
15451
|
+
if (cur.x > 0 && d[idx - PIXEL + 3] === 102) stack.push({ x: cur.x - 1, y: cur.y });
|
|
15452
|
+
if (cur.x < width - 1 && d[idx + PIXEL + 3] === 102) stack.push({ x: cur.x + 1, y: cur.y });
|
|
15453
|
+
if (cur.y > 0 && d[idx - width * PIXEL + 3] === 102) stack.push({ x: cur.x, y: cur.y - 1 });
|
|
15454
|
+
if (cur.y < height - 1 && d[idx + width * PIXEL + 3] === 102) stack.push({ x: cur.x, y: cur.y + 1 });
|
|
15455
|
+
} else {
|
|
15456
|
+
d[idx + 3] = 0;
|
|
15457
|
+
}
|
|
15458
|
+
}
|
|
15459
|
+
ctx.putImageData(this.imageData, 0, 0);
|
|
15460
|
+
const op = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15461
|
+
this.onMaskSelect(op, canvas, ctx);
|
|
15462
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
15463
|
+
}
|
|
15464
|
+
}
|
|
15465
|
+
class EyedropperSelection {
|
|
15466
|
+
constructor(parent, onColorMatch) {
|
|
15467
|
+
__publicField(this, "parent");
|
|
15468
|
+
__publicField(this, "onColorMatch");
|
|
15469
|
+
__publicField(this, "thresholdEl");
|
|
15470
|
+
__publicField(this, "threshold", 0.2);
|
|
15471
|
+
__publicField(this, "pointerId", null);
|
|
15472
|
+
this.parent = parent;
|
|
15473
|
+
this.onColorMatch = onColorMatch;
|
|
15474
|
+
this.thresholdEl = document.createElement("div");
|
|
15475
|
+
this.thresholdEl.style.cssText = "position:absolute;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(20,20,35,0.9);color:#e0e0e0;padding:8px 16px;border-radius:8px;font-size:13px;display:none;z-index:20;gap:8px;align-items:center;";
|
|
15476
|
+
this.thresholdEl.innerHTML = `
|
|
15477
|
+
<label style="margin-right:8px;">阈值:</label>
|
|
15478
|
+
<input type="range" min="0" max="1" step="0.01" value="${this.threshold}" style="width:120px;vertical-align:middle;">
|
|
15479
|
+
<span style="margin-left:8px;min-width:40px;">${this.threshold.toFixed(2)}</span>
|
|
15480
|
+
`;
|
|
15481
|
+
parent.appendChild(this.thresholdEl);
|
|
15482
|
+
const slider = this.thresholdEl.querySelector("input");
|
|
15483
|
+
const span = this.thresholdEl.querySelector("span");
|
|
15484
|
+
slider.addEventListener("input", () => {
|
|
15485
|
+
this.threshold = parseFloat(slider.value);
|
|
15486
|
+
span.textContent = this.threshold.toFixed(2);
|
|
15487
|
+
});
|
|
15488
|
+
slider.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
15489
|
+
this.pointerdown = this.pointerdown.bind(this);
|
|
15490
|
+
this.pointermove = this.pointermove.bind(this);
|
|
15491
|
+
this.pointerup = this.pointerup.bind(this);
|
|
15492
|
+
}
|
|
15493
|
+
activate() {
|
|
15494
|
+
this.parent.style.cursor = "crosshair";
|
|
15495
|
+
this.thresholdEl.style.display = "flex";
|
|
15496
|
+
this.parent.addEventListener("pointerdown", this.pointerdown);
|
|
15497
|
+
this.parent.addEventListener("pointermove", this.pointermove);
|
|
15498
|
+
this.parent.addEventListener("pointerup", this.pointerup);
|
|
15499
|
+
}
|
|
15500
|
+
deactivate() {
|
|
15501
|
+
this.parent.style.cursor = "";
|
|
15502
|
+
this.thresholdEl.style.display = "none";
|
|
15503
|
+
if (this.pointerId !== null) {
|
|
15504
|
+
this.parent.releasePointerCapture(this.pointerId);
|
|
15505
|
+
this.pointerId = null;
|
|
15506
|
+
}
|
|
15507
|
+
this.parent.removeEventListener("pointerdown", this.pointerdown);
|
|
15508
|
+
this.parent.removeEventListener("pointermove", this.pointermove);
|
|
15509
|
+
this.parent.removeEventListener("pointerup", this.pointerup);
|
|
15510
|
+
}
|
|
15511
|
+
pointerdown(e) {
|
|
15512
|
+
if (this.pointerId !== null) return;
|
|
15513
|
+
if (e.pointerType === "mouse" ? e.button !== 0 : !e.isPrimary) return;
|
|
15514
|
+
e.preventDefault();
|
|
15515
|
+
e.stopPropagation();
|
|
15516
|
+
this.pointerId = e.pointerId;
|
|
15517
|
+
this.parent.setPointerCapture(this.pointerId);
|
|
15518
|
+
}
|
|
15519
|
+
pointermove(e) {
|
|
15520
|
+
if (e.pointerId === this.pointerId) {
|
|
15521
|
+
e.preventDefault();
|
|
15522
|
+
e.stopPropagation();
|
|
15523
|
+
}
|
|
15524
|
+
}
|
|
15525
|
+
pointerup(e) {
|
|
15526
|
+
if (e.pointerId !== this.pointerId) return;
|
|
15527
|
+
e.preventDefault();
|
|
15528
|
+
e.stopPropagation();
|
|
15529
|
+
const w = this.parent.clientWidth || 1;
|
|
15530
|
+
const h = this.parent.clientHeight || 1;
|
|
15531
|
+
const op = e.shiftKey ? "add" : e.ctrlKey ? "remove" : "set";
|
|
15532
|
+
this.onColorMatch(op, {
|
|
15533
|
+
x: Math.max(0, Math.min(1, e.offsetX / w)),
|
|
15534
|
+
y: Math.max(0, Math.min(1, e.offsetY / h))
|
|
15535
|
+
}, this.threshold);
|
|
15536
|
+
this.parent.releasePointerCapture(this.pointerId);
|
|
15537
|
+
this.pointerId = null;
|
|
15538
|
+
}
|
|
15539
|
+
}
|
|
15540
|
+
function exportEditedPLY(positions, scales, rotations, colors, opacities, shCoeffs, state) {
|
|
15541
|
+
const totalCount = state.count;
|
|
15542
|
+
let keepCount = 0;
|
|
15543
|
+
for (let i = 0; i < totalCount; i++) {
|
|
15544
|
+
if (!(state.data[i] & State.deleted)) keepCount++;
|
|
15545
|
+
}
|
|
15546
|
+
const hasSH = shCoeffs !== null && shCoeffs.length > 0;
|
|
15547
|
+
const shPerSplat = hasSH ? Math.floor(shCoeffs.length / totalCount) : 0;
|
|
15548
|
+
const shNames = [];
|
|
15549
|
+
if (hasSH) {
|
|
15550
|
+
for (let i = 0; i < shPerSplat; i++) {
|
|
15551
|
+
shNames.push(`f_rest_${i}`);
|
|
15552
|
+
}
|
|
15553
|
+
}
|
|
15554
|
+
let header = "ply\n";
|
|
15555
|
+
header += "format binary_little_endian 1.0\n";
|
|
15556
|
+
header += `element vertex ${keepCount}
|
|
15557
|
+
`;
|
|
15558
|
+
header += "property float x\n";
|
|
15559
|
+
header += "property float y\n";
|
|
15560
|
+
header += "property float z\n";
|
|
15561
|
+
header += "property float nx\n";
|
|
15562
|
+
header += "property float ny\n";
|
|
15563
|
+
header += "property float nz\n";
|
|
15564
|
+
header += "property float f_dc_0\n";
|
|
15565
|
+
header += "property float f_dc_1\n";
|
|
15566
|
+
header += "property float f_dc_2\n";
|
|
15567
|
+
for (const name of shNames) {
|
|
15568
|
+
header += `property float ${name}
|
|
15569
|
+
`;
|
|
15570
|
+
}
|
|
15571
|
+
header += "property float opacity\n";
|
|
15572
|
+
header += "property float scale_0\n";
|
|
15573
|
+
header += "property float scale_1\n";
|
|
15574
|
+
header += "property float scale_2\n";
|
|
15575
|
+
header += "property float rot_0\n";
|
|
15576
|
+
header += "property float rot_1\n";
|
|
15577
|
+
header += "property float rot_2\n";
|
|
15578
|
+
header += "property float rot_3\n";
|
|
15579
|
+
header += "end_header\n";
|
|
15580
|
+
const headerBytes = new TextEncoder().encode(header);
|
|
15581
|
+
const floatsPerSplat = 3 + 3 + 3 + shPerSplat + 1 + 3 + 4;
|
|
15582
|
+
const bytesPerSplat = floatsPerSplat * 4;
|
|
15583
|
+
const totalBytes = headerBytes.byteLength + keepCount * bytesPerSplat;
|
|
15584
|
+
const buffer = new ArrayBuffer(totalBytes);
|
|
15585
|
+
const headerView = new Uint8Array(buffer);
|
|
15586
|
+
headerView.set(headerBytes);
|
|
15587
|
+
const SH_C02 = 0.28209479177387814;
|
|
15588
|
+
const dataView = new DataView(buffer);
|
|
15589
|
+
let offset = headerBytes.byteLength;
|
|
15590
|
+
for (let i = 0; i < totalCount; i++) {
|
|
15591
|
+
if (state.data[i] & State.deleted) continue;
|
|
15592
|
+
const i3 = i * 3;
|
|
15593
|
+
const i4 = i * 4;
|
|
15594
|
+
dataView.setFloat32(offset, positions[i3], true);
|
|
15595
|
+
offset += 4;
|
|
15596
|
+
dataView.setFloat32(offset, positions[i3 + 2], true);
|
|
15597
|
+
offset += 4;
|
|
15598
|
+
dataView.setFloat32(offset, positions[i3 + 1], true);
|
|
15599
|
+
offset += 4;
|
|
15600
|
+
dataView.setFloat32(offset, 0, true);
|
|
15601
|
+
offset += 4;
|
|
15602
|
+
dataView.setFloat32(offset, 0, true);
|
|
15603
|
+
offset += 4;
|
|
15604
|
+
dataView.setFloat32(offset, 0, true);
|
|
15605
|
+
offset += 4;
|
|
15606
|
+
dataView.setFloat32(offset, (colors[i3] - 0.5) / SH_C02, true);
|
|
15607
|
+
offset += 4;
|
|
15608
|
+
dataView.setFloat32(offset, (colors[i3 + 1] - 0.5) / SH_C02, true);
|
|
15609
|
+
offset += 4;
|
|
15610
|
+
dataView.setFloat32(offset, (colors[i3 + 2] - 0.5) / SH_C02, true);
|
|
15611
|
+
offset += 4;
|
|
15612
|
+
if (hasSH) {
|
|
15613
|
+
const shBase = i * shPerSplat;
|
|
15614
|
+
for (let j = 0; j < shPerSplat; j++) {
|
|
15615
|
+
dataView.setFloat32(offset, shCoeffs[shBase + j], true);
|
|
15616
|
+
offset += 4;
|
|
15617
|
+
}
|
|
15618
|
+
}
|
|
15619
|
+
const alpha = opacities[i];
|
|
15620
|
+
const clampedAlpha = Math.max(1e-6, Math.min(1 - 1e-6, alpha));
|
|
15621
|
+
const logitOpacity = Math.log(clampedAlpha / (1 - clampedAlpha));
|
|
15622
|
+
dataView.setFloat32(offset, logitOpacity, true);
|
|
15623
|
+
offset += 4;
|
|
15624
|
+
dataView.setFloat32(offset, Math.log(Math.max(1e-8, scales[i3])), true);
|
|
15625
|
+
offset += 4;
|
|
15626
|
+
dataView.setFloat32(offset, Math.log(Math.max(1e-8, scales[i3 + 2])), true);
|
|
15627
|
+
offset += 4;
|
|
15628
|
+
dataView.setFloat32(offset, Math.log(Math.max(1e-8, scales[i3 + 1])), true);
|
|
15629
|
+
offset += 4;
|
|
15630
|
+
dataView.setFloat32(offset, -rotations[i4], true);
|
|
15631
|
+
offset += 4;
|
|
15632
|
+
dataView.setFloat32(offset, rotations[i4 + 1], true);
|
|
15633
|
+
offset += 4;
|
|
15634
|
+
dataView.setFloat32(offset, rotations[i4 + 3], true);
|
|
15635
|
+
offset += 4;
|
|
15636
|
+
dataView.setFloat32(offset, rotations[i4 + 2], true);
|
|
15637
|
+
offset += 4;
|
|
15638
|
+
}
|
|
15639
|
+
return buffer;
|
|
15640
|
+
}
|
|
15641
|
+
class SplatEditor {
|
|
15642
|
+
constructor(camera, gsRenderer, container, callbacks = {}) {
|
|
15643
|
+
__publicField(this, "camera");
|
|
15644
|
+
__publicField(this, "gsRenderer");
|
|
15645
|
+
__publicField(this, "container");
|
|
15646
|
+
__publicField(this, "splatState");
|
|
15647
|
+
__publicField(this, "editHistory");
|
|
15648
|
+
__publicField(this, "toolManager");
|
|
15649
|
+
__publicField(this, "compactData", null);
|
|
15650
|
+
__publicField(this, "callbacks");
|
|
15651
|
+
__publicField(this, "maskCanvas");
|
|
15652
|
+
__publicField(this, "maskCtx");
|
|
15653
|
+
__publicField(this, "toolOverlay");
|
|
15654
|
+
__publicField(this, "projectedPositions", null);
|
|
15655
|
+
__publicField(this, "projDirty", true);
|
|
15656
|
+
__publicField(this, "_active", false);
|
|
15657
|
+
__publicField(this, "overlayCleanup", []);
|
|
15658
|
+
// ============================================
|
|
15659
|
+
// 键盘快捷键
|
|
15660
|
+
// ============================================
|
|
15661
|
+
__publicField(this, "_keyHandler", null);
|
|
15662
|
+
this.camera = camera;
|
|
15663
|
+
this.gsRenderer = gsRenderer;
|
|
15664
|
+
this.container = container;
|
|
15665
|
+
this.callbacks = callbacks;
|
|
15666
|
+
}
|
|
15667
|
+
get active() {
|
|
15668
|
+
return this._active;
|
|
15669
|
+
}
|
|
15670
|
+
get state() {
|
|
15671
|
+
return this.splatState;
|
|
15672
|
+
}
|
|
15673
|
+
get history() {
|
|
15674
|
+
return this.editHistory;
|
|
15675
|
+
}
|
|
15676
|
+
get tools() {
|
|
15677
|
+
return this.toolManager;
|
|
15678
|
+
}
|
|
15679
|
+
/**
|
|
15680
|
+
* 设置 compact 数据引用(用于导出和颜色匹配)
|
|
15681
|
+
*/
|
|
15682
|
+
setCompactData(data) {
|
|
15683
|
+
this.compactData = data;
|
|
15684
|
+
}
|
|
15685
|
+
/**
|
|
15686
|
+
* 进入编辑模式
|
|
15687
|
+
*/
|
|
15688
|
+
enter() {
|
|
15689
|
+
if (this._active) return;
|
|
15690
|
+
this._active = true;
|
|
15691
|
+
const count = this.gsRenderer.getSplatCount();
|
|
15692
|
+
this.splatState = new SplatState(count);
|
|
15693
|
+
this.editHistory = new EditHistory(() => {
|
|
15694
|
+
var _a2, _b2;
|
|
15695
|
+
this.onStateChanged();
|
|
15696
|
+
(_b2 = (_a2 = this.callbacks).onHistoryChanged) == null ? void 0 : _b2.call(_a2, this.editHistory.canUndo(), this.editHistory.canRedo());
|
|
15697
|
+
});
|
|
15698
|
+
this.toolOverlay = document.createElement("div");
|
|
15699
|
+
this.toolOverlay.id = "editor-tool-overlay";
|
|
15700
|
+
this.toolOverlay.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;display:none;z-index:5;";
|
|
15701
|
+
this.container.appendChild(this.toolOverlay);
|
|
15702
|
+
this.setupOverlayEventForwarding();
|
|
15703
|
+
this.maskCanvas = document.createElement("canvas");
|
|
15704
|
+
this.maskCanvas.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none;z-index:6;";
|
|
15705
|
+
this.container.appendChild(this.maskCanvas);
|
|
15706
|
+
this.maskCtx = this.maskCanvas.getContext("2d");
|
|
15707
|
+
const containerPos = getComputedStyle(this.container).position;
|
|
15708
|
+
if (containerPos === "static" || containerPos === "") {
|
|
15709
|
+
this.container.style.position = "relative";
|
|
15710
|
+
}
|
|
15711
|
+
this.toolManager = new ToolManager((name) => {
|
|
15712
|
+
var _a2, _b2;
|
|
15713
|
+
this.toolOverlay.style.display = name ? "block" : "none";
|
|
15714
|
+
(_b2 = (_a2 = this.callbacks).onToolChanged) == null ? void 0 : _b2.call(_a2, name);
|
|
15715
|
+
});
|
|
15716
|
+
this.toolManager.register("rect", new RectSelection(
|
|
15717
|
+
this.toolOverlay,
|
|
15718
|
+
(op, rect) => this.selectByRect(op, rect)
|
|
15719
|
+
));
|
|
15720
|
+
this.toolManager.register("lasso", new LassoSelection(
|
|
15721
|
+
this.toolOverlay,
|
|
15722
|
+
this.maskCanvas,
|
|
15723
|
+
this.maskCtx,
|
|
15724
|
+
(op, canvas, ctx) => this.selectByMask(op, canvas, ctx)
|
|
15725
|
+
));
|
|
15726
|
+
this.toolManager.register("polygon", new PolygonSelection(
|
|
15727
|
+
this.toolOverlay,
|
|
15728
|
+
this.maskCanvas,
|
|
15729
|
+
this.maskCtx,
|
|
15730
|
+
(op, canvas, ctx) => this.selectByMask(op, canvas, ctx)
|
|
15731
|
+
));
|
|
15732
|
+
this.toolManager.register("brush", new BrushSelection(
|
|
15733
|
+
this.toolOverlay,
|
|
15734
|
+
this.maskCanvas,
|
|
15735
|
+
this.maskCtx,
|
|
15736
|
+
(op, canvas, ctx) => this.selectByMask(op, canvas, ctx)
|
|
15737
|
+
));
|
|
15738
|
+
this.toolManager.register("flood", new FloodSelection(
|
|
15739
|
+
this.toolOverlay,
|
|
15740
|
+
this.maskCanvas,
|
|
15741
|
+
this.maskCtx,
|
|
15742
|
+
(op, canvas, ctx) => this.selectByMask(op, canvas, ctx),
|
|
15743
|
+
(_w, _h) => null
|
|
15744
|
+
// offscreen rendering not available yet
|
|
15745
|
+
));
|
|
15746
|
+
this.toolManager.register("eyedropper", new EyedropperSelection(
|
|
15747
|
+
this.toolOverlay,
|
|
15748
|
+
(op, pt, threshold) => this.selectByColor(op, pt, threshold)
|
|
15749
|
+
));
|
|
15750
|
+
this.gsRenderer.setEditorState(this.splatState.data);
|
|
15751
|
+
this._keyHandler = this._onKeyDown.bind(this);
|
|
15752
|
+
window.addEventListener("keydown", this._keyHandler);
|
|
15753
|
+
this.projDirty = true;
|
|
15754
|
+
}
|
|
15755
|
+
/**
|
|
15756
|
+
* 退出编辑模式,将编辑结果永久写入渲染数据
|
|
15757
|
+
*/
|
|
15758
|
+
exit() {
|
|
15759
|
+
var _a2, _b2;
|
|
15760
|
+
if (!this._active) return;
|
|
15761
|
+
this._active = false;
|
|
15762
|
+
window.removeEventListener("keydown", this._keyHandler);
|
|
15763
|
+
this.toolManager.deactivateAll();
|
|
15764
|
+
this.overlayCleanup.forEach((fn) => fn());
|
|
15765
|
+
this.overlayCleanup = [];
|
|
15766
|
+
(_a2 = this.toolOverlay) == null ? void 0 : _a2.remove();
|
|
15767
|
+
(_b2 = this.maskCanvas) == null ? void 0 : _b2.remove();
|
|
15768
|
+
this.applyEditsToRenderer();
|
|
15769
|
+
this.editHistory.clear();
|
|
15770
|
+
this.gsRenderer.clearEditorState();
|
|
15771
|
+
this.projectedPositions = null;
|
|
15772
|
+
this.compactData = null;
|
|
15773
|
+
}
|
|
15774
|
+
/**
|
|
15775
|
+
* 将编辑结果写回渲染器:从 CompactSplatData 中剔除被标记为 deleted 的 splat,
|
|
15776
|
+
* 用精简数据重建 GPU 缓冲区。
|
|
15777
|
+
*/
|
|
15778
|
+
applyEditsToRenderer() {
|
|
15779
|
+
var _a2, _b2;
|
|
15780
|
+
if (!this.compactData || this.splatState.numDeleted === 0) return;
|
|
15781
|
+
const { count, data } = this.splatState;
|
|
15782
|
+
const src = this.compactData;
|
|
15783
|
+
const keepCount = count - this.splatState.numDeleted;
|
|
15784
|
+
if (keepCount <= 0) return;
|
|
15785
|
+
const hasSH = !!src.shCoeffs;
|
|
15786
|
+
const shStride = hasSH ? src.shCoeffs.length / src.count : 0;
|
|
15787
|
+
const positions = new Float32Array(keepCount * 3);
|
|
15788
|
+
const scales = new Float32Array(keepCount * 3);
|
|
15789
|
+
const rotations = new Float32Array(keepCount * 4);
|
|
15790
|
+
const colors = new Float32Array(keepCount * 3);
|
|
15791
|
+
const opacities = new Float32Array(keepCount);
|
|
15792
|
+
const shCoeffs = hasSH ? new Float32Array(keepCount * shStride) : void 0;
|
|
15793
|
+
let dst = 0;
|
|
15794
|
+
for (let i = 0; i < count; i++) {
|
|
15795
|
+
if (data[i] & State.deleted) continue;
|
|
15796
|
+
const i3 = i * 3, d3 = dst * 3;
|
|
15797
|
+
positions[d3] = src.positions[i3];
|
|
15798
|
+
positions[d3 + 1] = src.positions[i3 + 1];
|
|
15799
|
+
positions[d3 + 2] = src.positions[i3 + 2];
|
|
15800
|
+
scales[d3] = src.scales[i3];
|
|
15801
|
+
scales[d3 + 1] = src.scales[i3 + 1];
|
|
15802
|
+
scales[d3 + 2] = src.scales[i3 + 2];
|
|
15803
|
+
const i4 = i * 4, d4 = dst * 4;
|
|
15804
|
+
rotations[d4] = src.rotations[i4];
|
|
15805
|
+
rotations[d4 + 1] = src.rotations[i4 + 1];
|
|
15806
|
+
rotations[d4 + 2] = src.rotations[i4 + 2];
|
|
15807
|
+
rotations[d4 + 3] = src.rotations[i4 + 3];
|
|
15808
|
+
colors[d3] = src.colors[i3];
|
|
15809
|
+
colors[d3 + 1] = src.colors[i3 + 1];
|
|
15810
|
+
colors[d3 + 2] = src.colors[i3 + 2];
|
|
15811
|
+
opacities[dst] = src.opacities[i];
|
|
15812
|
+
if (hasSH) {
|
|
15813
|
+
const srcOff = i * shStride, dstOff = dst * shStride;
|
|
15814
|
+
for (let s = 0; s < shStride; s++) {
|
|
15815
|
+
shCoeffs[dstOff + s] = src.shCoeffs[srcOff + s];
|
|
15816
|
+
}
|
|
15817
|
+
}
|
|
15818
|
+
dst++;
|
|
15819
|
+
}
|
|
15820
|
+
const newData = {
|
|
15821
|
+
count: keepCount,
|
|
15822
|
+
positions,
|
|
15823
|
+
scales,
|
|
15824
|
+
rotations,
|
|
15825
|
+
colors,
|
|
15826
|
+
opacities,
|
|
15827
|
+
...hasSH ? { shCoeffs } : {}
|
|
15828
|
+
};
|
|
15829
|
+
this.gsRenderer.setCompactData(newData);
|
|
15830
|
+
(_b2 = (_a2 = this.callbacks).onApplyEdits) == null ? void 0 : _b2.call(_a2, newData);
|
|
15831
|
+
}
|
|
15832
|
+
// ============================================
|
|
15833
|
+
// 选择操作
|
|
15834
|
+
// ============================================
|
|
15835
|
+
selectAll() {
|
|
15836
|
+
this.editHistory.add(new SelectAllOp(this.splatState));
|
|
15837
|
+
}
|
|
15838
|
+
selectNone() {
|
|
15839
|
+
this.editHistory.add(new SelectNoneOp(this.splatState));
|
|
15840
|
+
}
|
|
15841
|
+
selectInvert() {
|
|
15842
|
+
this.editHistory.add(new SelectInvertOp(this.splatState));
|
|
15843
|
+
}
|
|
15844
|
+
deleteSelection() {
|
|
15845
|
+
if (this.splatState.numSelected === 0) return;
|
|
15846
|
+
this.editHistory.add(new DeleteSelectionOp(this.splatState));
|
|
15847
|
+
}
|
|
15848
|
+
hideSelection() {
|
|
15849
|
+
if (this.splatState.numSelected === 0) return;
|
|
15850
|
+
this.editHistory.add(new HideSelectionOp(this.splatState));
|
|
15851
|
+
}
|
|
15852
|
+
unhideAll() {
|
|
15853
|
+
this.editHistory.add(new UnhideAllOp(this.splatState));
|
|
15854
|
+
}
|
|
15855
|
+
undo() {
|
|
15856
|
+
this.editHistory.undo();
|
|
15857
|
+
}
|
|
15858
|
+
redo() {
|
|
15859
|
+
this.editHistory.redo();
|
|
15860
|
+
}
|
|
15861
|
+
// ============================================
|
|
15862
|
+
// 导出
|
|
15863
|
+
// ============================================
|
|
15864
|
+
exportPLY() {
|
|
15865
|
+
if (!this.compactData) return null;
|
|
15866
|
+
const { positions, scales, rotations, colors, opacities, shCoeffs } = this.compactData;
|
|
15867
|
+
return exportEditedPLY(positions, scales, rotations, colors, opacities, shCoeffs ?? null, this.splatState);
|
|
15868
|
+
}
|
|
15869
|
+
downloadPLY(filename = "edited.ply") {
|
|
15870
|
+
const buffer = this.exportPLY();
|
|
15871
|
+
if (!buffer) return;
|
|
15872
|
+
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
|
15873
|
+
const url = URL.createObjectURL(blob);
|
|
15874
|
+
const a = document.createElement("a");
|
|
15875
|
+
a.href = url;
|
|
15876
|
+
a.download = filename;
|
|
15877
|
+
a.click();
|
|
15878
|
+
URL.revokeObjectURL(url);
|
|
15879
|
+
}
|
|
15880
|
+
// ============================================
|
|
15881
|
+
// 内部:选择逻辑
|
|
15882
|
+
// ============================================
|
|
15883
|
+
selectByRect(op, rect) {
|
|
15884
|
+
this.ensureProjection();
|
|
15885
|
+
const proj = this.projectedPositions;
|
|
15886
|
+
this.splatState.count;
|
|
15887
|
+
const editOp = new SelectOp(this.splatState, op, (i) => {
|
|
15888
|
+
const bx = i * 3;
|
|
15889
|
+
const sx = proj[bx], sy = proj[bx + 1], sz = proj[bx + 2];
|
|
15890
|
+
if (sz <= 0 || sz >= 1) return false;
|
|
15891
|
+
return sx >= rect.startX && sx <= rect.endX && sy >= rect.startY && sy <= rect.endY;
|
|
15892
|
+
});
|
|
15893
|
+
this.editHistory.add(editOp);
|
|
15894
|
+
}
|
|
15895
|
+
selectByMask(op, canvas, ctx) {
|
|
15896
|
+
this.ensureProjection();
|
|
15897
|
+
const proj = this.projectedPositions;
|
|
15898
|
+
const w = canvas.width;
|
|
15899
|
+
const h = canvas.height;
|
|
15900
|
+
const imageData = ctx.getImageData(0, 0, w, h);
|
|
15901
|
+
const pixels = imageData.data;
|
|
15902
|
+
const editOp = new SelectOp(this.splatState, op, (i) => {
|
|
15903
|
+
const bx = i * 3;
|
|
15904
|
+
const sx = proj[bx], sy = proj[bx + 1], sz = proj[bx + 2];
|
|
15905
|
+
if (sz <= 0 || sz >= 1) return false;
|
|
15906
|
+
const px = Math.floor(sx * w);
|
|
15907
|
+
const py = Math.floor(sy * h);
|
|
15908
|
+
if (px < 0 || px >= w || py < 0 || py >= h) return false;
|
|
15909
|
+
const idx = (py * w + px) * 4;
|
|
15910
|
+
return pixels[idx + 3] > 0;
|
|
15911
|
+
});
|
|
15912
|
+
this.editHistory.add(editOp);
|
|
15913
|
+
}
|
|
15914
|
+
selectByColor(op, _normalizedPoint, threshold) {
|
|
15915
|
+
if (!this.compactData) return;
|
|
15916
|
+
this.ensureProjection();
|
|
15917
|
+
const proj = this.projectedPositions;
|
|
15918
|
+
const { colors } = this.compactData;
|
|
15919
|
+
const w = this.container.clientWidth;
|
|
15920
|
+
const h = this.container.clientHeight;
|
|
15921
|
+
const px = Math.floor(_normalizedPoint.x * w);
|
|
15922
|
+
const py = Math.floor(_normalizedPoint.y * h);
|
|
15923
|
+
let bestIdx = -1;
|
|
15924
|
+
let bestDist = Infinity;
|
|
15925
|
+
for (let i = 0; i < this.splatState.count; i++) {
|
|
15926
|
+
const bx = i * 3;
|
|
15927
|
+
const sz = proj[bx + 2];
|
|
15928
|
+
if (sz <= 0 || sz >= 1) continue;
|
|
15929
|
+
if (this.splatState.data[i] & (State.hidden | State.deleted)) continue;
|
|
15930
|
+
const spx = proj[bx] * w;
|
|
15931
|
+
const spy = proj[bx + 1] * h;
|
|
15932
|
+
const d = (spx - px) ** 2 + (spy - py) ** 2;
|
|
15933
|
+
if (d < bestDist) {
|
|
15934
|
+
bestDist = d;
|
|
15935
|
+
bestIdx = i;
|
|
15936
|
+
}
|
|
15937
|
+
}
|
|
15938
|
+
if (bestIdx < 0) return;
|
|
15939
|
+
const refR = colors[bestIdx * 3];
|
|
15940
|
+
const refG = colors[bestIdx * 3 + 1];
|
|
15941
|
+
const refB = colors[bestIdx * 3 + 2];
|
|
15942
|
+
const editOp = new SelectOp(this.splatState, op, (i) => {
|
|
15943
|
+
if (this.splatState.data[i] & (State.hidden | State.deleted)) return false;
|
|
15944
|
+
const ci = i * 3;
|
|
15945
|
+
const dr = colors[ci] - refR;
|
|
15946
|
+
const dg = colors[ci + 1] - refG;
|
|
15947
|
+
const db = colors[ci + 2] - refB;
|
|
15948
|
+
return Math.sqrt(dr * dr + dg * dg + db * db) < threshold;
|
|
15949
|
+
});
|
|
15950
|
+
this.editHistory.add(editOp);
|
|
15951
|
+
}
|
|
15952
|
+
// ============================================
|
|
15953
|
+
// 投影
|
|
15954
|
+
// ============================================
|
|
15955
|
+
/**
|
|
15956
|
+
* 每帧调用,标记投影需要更新
|
|
15957
|
+
*/
|
|
15958
|
+
markProjectionDirty() {
|
|
15959
|
+
this.projDirty = true;
|
|
15960
|
+
}
|
|
15961
|
+
ensureProjection() {
|
|
15962
|
+
const cpuPositions = this.gsRenderer.getCPUPositions();
|
|
15963
|
+
if (!cpuPositions) return;
|
|
15964
|
+
const count = this.splatState.count;
|
|
15965
|
+
if (!this.projectedPositions || this.projectedPositions.length !== count * 3) {
|
|
15966
|
+
this.projectedPositions = new Float32Array(count * 3);
|
|
15967
|
+
}
|
|
15968
|
+
const viewMat = this.camera.viewMatrix;
|
|
15969
|
+
const projMat = this.camera.projectionMatrix;
|
|
15970
|
+
const modelMat = this.gsRenderer.getModelMatrix();
|
|
15971
|
+
const out = this.projectedPositions;
|
|
15972
|
+
for (let i = 0; i < count; i++) {
|
|
15973
|
+
const i3 = i * 3;
|
|
15974
|
+
const x = cpuPositions[i3], y = cpuPositions[i3 + 1], z = cpuPositions[i3 + 2];
|
|
15975
|
+
const wx = modelMat[0] * x + modelMat[4] * y + modelMat[8] * z + modelMat[12];
|
|
15976
|
+
const wy = modelMat[1] * x + modelMat[5] * y + modelMat[9] * z + modelMat[13];
|
|
15977
|
+
const wz = modelMat[2] * x + modelMat[6] * y + modelMat[10] * z + modelMat[14];
|
|
15978
|
+
const vx = viewMat[0] * wx + viewMat[4] * wy + viewMat[8] * wz + viewMat[12];
|
|
15979
|
+
const vy = viewMat[1] * wx + viewMat[5] * wy + viewMat[9] * wz + viewMat[13];
|
|
15980
|
+
const vz = viewMat[2] * wx + viewMat[6] * wy + viewMat[10] * wz + viewMat[14];
|
|
15981
|
+
const vw = viewMat[3] * wx + viewMat[7] * wy + viewMat[11] * wz + viewMat[15];
|
|
15982
|
+
const cx = projMat[0] * vx + projMat[4] * vy + projMat[8] * vz + projMat[12] * vw;
|
|
15983
|
+
const cy = projMat[1] * vx + projMat[5] * vy + projMat[9] * vz + projMat[13] * vw;
|
|
15984
|
+
const cz = projMat[2] * vx + projMat[6] * vy + projMat[10] * vz + projMat[14] * vw;
|
|
15985
|
+
const cw = projMat[3] * vx + projMat[7] * vy + projMat[11] * vz + projMat[15] * vw;
|
|
15986
|
+
if (cw <= 0) {
|
|
15987
|
+
out[i3] = -2;
|
|
15988
|
+
out[i3 + 1] = -2;
|
|
15989
|
+
out[i3 + 2] = -1;
|
|
15990
|
+
continue;
|
|
15991
|
+
}
|
|
15992
|
+
const invW = 1 / cw;
|
|
15993
|
+
out[i3] = (cx * invW + 1) * 0.5;
|
|
15994
|
+
out[i3 + 1] = (1 - cy * invW) * 0.5;
|
|
15995
|
+
out[i3 + 2] = cz * invW;
|
|
15996
|
+
}
|
|
15997
|
+
this.projDirty = false;
|
|
15998
|
+
}
|
|
15999
|
+
// ============================================
|
|
16000
|
+
// 状态变更通知
|
|
16001
|
+
// ============================================
|
|
16002
|
+
onStateChanged() {
|
|
16003
|
+
var _a2, _b2;
|
|
16004
|
+
this.gsRenderer.updateEditorState(this.splatState.data);
|
|
16005
|
+
(_b2 = (_a2 = this.callbacks).onStateChanged) == null ? void 0 : _b2.call(_a2);
|
|
16006
|
+
}
|
|
16007
|
+
// ============================================
|
|
16008
|
+
// 覆盖层事件转发(保持相机控制可用)
|
|
16009
|
+
// ============================================
|
|
16010
|
+
setupOverlayEventForwarding() {
|
|
16011
|
+
const overlay = this.toolOverlay;
|
|
16012
|
+
const canvas = this.container.querySelector("canvas");
|
|
16013
|
+
if (!canvas) return;
|
|
16014
|
+
const addForward = (event, handler, options) => {
|
|
16015
|
+
overlay.addEventListener(event, handler, options);
|
|
16016
|
+
this.overlayCleanup.push(
|
|
16017
|
+
() => overlay.removeEventListener(event, handler, options)
|
|
16018
|
+
);
|
|
16019
|
+
};
|
|
16020
|
+
addForward("wheel", (e) => {
|
|
16021
|
+
canvas.dispatchEvent(new WheelEvent("wheel", e));
|
|
16022
|
+
e.preventDefault();
|
|
16023
|
+
}, { passive: false });
|
|
16024
|
+
addForward("mousedown", (e) => {
|
|
16025
|
+
if (e.button !== 0) {
|
|
16026
|
+
canvas.dispatchEvent(new MouseEvent("mousedown", e));
|
|
16027
|
+
}
|
|
16028
|
+
});
|
|
16029
|
+
addForward("mousemove", (e) => {
|
|
16030
|
+
if (e.buttons & 6) {
|
|
16031
|
+
canvas.dispatchEvent(new MouseEvent("mousemove", e));
|
|
16032
|
+
}
|
|
16033
|
+
});
|
|
16034
|
+
addForward("mouseup", (e) => {
|
|
16035
|
+
if (e.button !== 0) {
|
|
16036
|
+
canvas.dispatchEvent(new MouseEvent("mouseup", e));
|
|
16037
|
+
}
|
|
16038
|
+
});
|
|
16039
|
+
addForward("contextmenu", (e) => {
|
|
16040
|
+
e.preventDefault();
|
|
16041
|
+
});
|
|
16042
|
+
}
|
|
16043
|
+
_onKeyDown(e) {
|
|
16044
|
+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
16045
|
+
if (e.ctrlKey && e.key === "z") {
|
|
16046
|
+
e.preventDefault();
|
|
16047
|
+
if (e.shiftKey) this.redo();
|
|
16048
|
+
else this.undo();
|
|
16049
|
+
} else if (e.ctrlKey && e.key === "y") {
|
|
16050
|
+
e.preventDefault();
|
|
16051
|
+
this.redo();
|
|
16052
|
+
} else if (e.ctrlKey && e.key === "a") {
|
|
16053
|
+
e.preventDefault();
|
|
16054
|
+
this.selectAll();
|
|
16055
|
+
} else if (e.key === "Delete" || e.key === "Backspace") {
|
|
16056
|
+
e.preventDefault();
|
|
16057
|
+
this.deleteSelection();
|
|
16058
|
+
}
|
|
16059
|
+
}
|
|
16060
|
+
}
|
|
14213
16061
|
class App {
|
|
14214
16062
|
constructor(canvas) {
|
|
14215
16063
|
__publicField(this, "canvas");
|
|
@@ -14228,6 +16076,8 @@ class App {
|
|
|
14228
16076
|
__publicField(this, "animationId", 0);
|
|
14229
16077
|
// 是否使用移动端渲染器
|
|
14230
16078
|
__publicField(this, "useMobileRenderer", false);
|
|
16079
|
+
// 最近加载的 CompactSplatData(用于编辑器导出)
|
|
16080
|
+
__publicField(this, "lastCompactData", null);
|
|
14231
16081
|
// 绑定的事件处理函数
|
|
14232
16082
|
__publicField(this, "boundOnResize");
|
|
14233
16083
|
this.canvas = canvas;
|
|
@@ -14337,6 +16187,7 @@ class App {
|
|
|
14337
16187
|
if (onProgress) onProgress(90, "upload");
|
|
14338
16188
|
gsRenderer.setCompactData(compactData);
|
|
14339
16189
|
if (onProgress) onProgress(100, "upload");
|
|
16190
|
+
this.lastCompactData = compactData;
|
|
14340
16191
|
this.sceneManager.setGSRenderer(gsRenderer);
|
|
14341
16192
|
this.hotspotManager.setGSRenderer(gsRenderer);
|
|
14342
16193
|
return compactData.count;
|
|
@@ -14352,6 +16203,7 @@ class App {
|
|
|
14352
16203
|
if (onProgress) onProgress(90, "upload");
|
|
14353
16204
|
gsRenderer.setCompactData(compactData);
|
|
14354
16205
|
if (onProgress) onProgress(100, "upload");
|
|
16206
|
+
this.lastCompactData = compactData;
|
|
14355
16207
|
this.sceneManager.setGSRenderer(gsRenderer);
|
|
14356
16208
|
this.hotspotManager.setGSRenderer(gsRenderer);
|
|
14357
16209
|
return compactData.count;
|
|
@@ -14456,6 +16308,7 @@ class App {
|
|
|
14456
16308
|
this.useMobileRenderer = false;
|
|
14457
16309
|
}
|
|
14458
16310
|
gsRenderer.setCompactData(compactData);
|
|
16311
|
+
this.lastCompactData = compactData;
|
|
14459
16312
|
this.sceneManager.setGSRenderer(gsRenderer);
|
|
14460
16313
|
this.hotspotManager.setGSRenderer(gsRenderer);
|
|
14461
16314
|
if (onProgress) onProgress(100, "upload");
|
|
@@ -14720,6 +16573,12 @@ class App {
|
|
|
14720
16573
|
isUsingMobileRenderer() {
|
|
14721
16574
|
return this.useMobileRenderer;
|
|
14722
16575
|
}
|
|
16576
|
+
getLastCompactData() {
|
|
16577
|
+
return this.lastCompactData;
|
|
16578
|
+
}
|
|
16579
|
+
setLastCompactData(data) {
|
|
16580
|
+
this.lastCompactData = data;
|
|
16581
|
+
}
|
|
14723
16582
|
// ============================================
|
|
14724
16583
|
// 热点管理
|
|
14725
16584
|
// ============================================
|
|
@@ -14882,6 +16741,7 @@ export {
|
|
|
14882
16741
|
Camera,
|
|
14883
16742
|
DEFAULT_MATERIAL,
|
|
14884
16743
|
DEFAULT_OBJ_MATERIAL,
|
|
16744
|
+
EditHistory,
|
|
14885
16745
|
GLBLoader,
|
|
14886
16746
|
GSSplatRenderer,
|
|
14887
16747
|
GSSplatRendererMobile,
|
|
@@ -14902,8 +16762,12 @@ export {
|
|
|
14902
16762
|
SceneAidsRenderer,
|
|
14903
16763
|
SceneManager,
|
|
14904
16764
|
SplatBoundingBoxProvider,
|
|
16765
|
+
SplatEditor,
|
|
16766
|
+
SplatState,
|
|
14905
16767
|
SplatTransformProxy,
|
|
16768
|
+
State,
|
|
14906
16769
|
TextureCache,
|
|
16770
|
+
ToolManager,
|
|
14907
16771
|
TransformGizmo,
|
|
14908
16772
|
ViewportGizmo,
|
|
14909
16773
|
calculateTextureDimensions,
|
|
@@ -14915,6 +16779,7 @@ export {
|
|
|
14915
16779
|
deserializeSOG,
|
|
14916
16780
|
deserializeSplat,
|
|
14917
16781
|
destroyCompressedTextures,
|
|
16782
|
+
exportEditedPLY,
|
|
14918
16783
|
getRecommendedDPR,
|
|
14919
16784
|
isMobileDevice,
|
|
14920
16785
|
isWebGPUSupported,
|