@d5techs/3dgs-lib 1.4.8 → 1.4.9
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 +177 -478
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +177 -478
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/core/gizmo/ViewportGizmo.d.ts +20 -65
- package/package.json +1 -1
package/dist/3dgs-lib.cjs
CHANGED
|
@@ -1003,514 +1003,212 @@ class OrbitControls {
|
|
|
1003
1003
|
this.velocityPanZ = 0;
|
|
1004
1004
|
}
|
|
1005
1005
|
}
|
|
1006
|
-
const gizmoShaderCode = (
|
|
1007
|
-
/* wgsl */
|
|
1008
|
-
`
|
|
1009
|
-
struct Uniforms {
|
|
1010
|
-
viewMatrix: mat4x4<f32>,
|
|
1011
|
-
projMatrix: mat4x4<f32>,
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
1015
|
-
|
|
1016
|
-
struct VertexInput {
|
|
1017
|
-
@location(0) position: vec3<f32>,
|
|
1018
|
-
@location(1) color: vec3<f32>,
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
struct VertexOutput {
|
|
1022
|
-
@builtin(position) position: vec4<f32>,
|
|
1023
|
-
@location(0) color: vec3<f32>,
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
@vertex
|
|
1027
|
-
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
1028
|
-
var output: VertexOutput;
|
|
1029
|
-
let worldPos = vec4<f32>(input.position, 1.0);
|
|
1030
|
-
output.position = uniforms.projMatrix * uniforms.viewMatrix * worldPos;
|
|
1031
|
-
output.color = input.color;
|
|
1032
|
-
return output;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
@fragment
|
|
1036
|
-
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
1037
|
-
return vec4<f32>(input.color, 1.0);
|
|
1038
|
-
}
|
|
1039
|
-
`
|
|
1040
|
-
);
|
|
1041
1006
|
class ViewportGizmo {
|
|
1042
|
-
constructor(
|
|
1043
|
-
__publicField(this, "renderer");
|
|
1007
|
+
constructor(_renderer, camera, canvas) {
|
|
1044
1008
|
__publicField(this, "camera");
|
|
1045
1009
|
__publicField(this, "canvas");
|
|
1046
|
-
|
|
1047
|
-
__publicField(this, "
|
|
1048
|
-
__publicField(this, "
|
|
1049
|
-
__publicField(this, "
|
|
1050
|
-
__publicField(this, "
|
|
1051
|
-
__publicField(this, "
|
|
1052
|
-
__publicField(this, "
|
|
1053
|
-
__publicField(this, "indexCount", 0);
|
|
1054
|
-
// Gizmo 配置
|
|
1055
|
-
__publicField(this, "size", 200);
|
|
1056
|
-
// Gizmo 尺寸(像素)
|
|
1057
|
-
__publicField(this, "margin", 20);
|
|
1058
|
-
// 边距
|
|
1059
|
-
// Gizmo 投影矩阵
|
|
1060
|
-
__publicField(this, "projMatrix", new Float32Array(16));
|
|
1061
|
-
__publicField(this, "viewMatrix", new Float32Array(16));
|
|
1062
|
-
// 轴配置
|
|
1063
|
-
__publicField(this, "axes", [
|
|
1064
|
-
{ direction: [1, 0, 0], color: [0.9, 0.2, 0.2], label: "X" },
|
|
1065
|
-
// 红色 X
|
|
1066
|
-
{ direction: [0, 1, 0], color: [0.2, 0.9, 0.2], label: "Y" },
|
|
1067
|
-
// 绿色 Y
|
|
1068
|
-
{ direction: [0, 0, 1], color: [0.2, 0.4, 0.9], label: "Z" }
|
|
1069
|
-
// 蓝色 Z
|
|
1070
|
-
]);
|
|
1071
|
-
// 交互回调
|
|
1010
|
+
__publicField(this, "container");
|
|
1011
|
+
__publicField(this, "svg");
|
|
1012
|
+
__publicField(this, "group");
|
|
1013
|
+
__publicField(this, "shapes");
|
|
1014
|
+
__publicField(this, "size", 140);
|
|
1015
|
+
__publicField(this, "margin", 10);
|
|
1016
|
+
__publicField(this, "scale", 40);
|
|
1072
1017
|
__publicField(this, "onAxisClick");
|
|
1073
|
-
this
|
|
1018
|
+
__publicField(this, "resizeObserver");
|
|
1074
1019
|
this.camera = camera;
|
|
1075
1020
|
this.canvas = canvas;
|
|
1076
|
-
this.
|
|
1077
|
-
this.
|
|
1078
|
-
this.createUniformBuffer();
|
|
1079
|
-
this.setupOrthoProjection();
|
|
1021
|
+
this.createSVG();
|
|
1022
|
+
this.setupResizeObserver();
|
|
1080
1023
|
}
|
|
1081
|
-
/**
|
|
1082
|
-
* 设置轴点击回调
|
|
1083
|
-
*/
|
|
1084
1024
|
setOnAxisClick(callback) {
|
|
1085
1025
|
this.onAxisClick = callback;
|
|
1086
1026
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1027
|
+
createSVG() {
|
|
1028
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
1029
|
+
this.container = document.createElement("div");
|
|
1030
|
+
Object.assign(this.container.style, {
|
|
1031
|
+
position: "absolute",
|
|
1032
|
+
width: `${this.size}px`,
|
|
1033
|
+
height: `${this.size}px`,
|
|
1034
|
+
pointerEvents: "none",
|
|
1035
|
+
zIndex: "10"
|
|
1094
1036
|
});
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1037
|
+
this.positionContainer();
|
|
1038
|
+
this.svg = document.createElementNS(ns, "svg");
|
|
1039
|
+
this.svg.setAttribute("width", this.size.toString());
|
|
1040
|
+
this.svg.setAttribute("height", this.size.toString());
|
|
1041
|
+
this.group = document.createElementNS(ns, "g");
|
|
1042
|
+
this.group.setAttribute(
|
|
1043
|
+
"transform",
|
|
1044
|
+
`translate(${this.size / 2}, ${this.size / 2})`
|
|
1045
|
+
);
|
|
1046
|
+
this.svg.appendChild(this.group);
|
|
1047
|
+
const r = "#f44";
|
|
1048
|
+
const g = "#4f4";
|
|
1049
|
+
const b = "#77f";
|
|
1050
|
+
this.shapes = {
|
|
1051
|
+
nx: this.createCircle(r, false),
|
|
1052
|
+
ny: this.createCircle(g, false),
|
|
1053
|
+
nz: this.createCircle(b, false),
|
|
1054
|
+
xaxis: this.createLine(r),
|
|
1055
|
+
yaxis: this.createLine(g),
|
|
1056
|
+
zaxis: this.createLine(b),
|
|
1057
|
+
px: this.createCircle(r, true, "X"),
|
|
1058
|
+
py: this.createCircle(g, true, "Y"),
|
|
1059
|
+
pz: this.createCircle(b, true, "Z")
|
|
1113
1060
|
};
|
|
1114
|
-
this.
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
fragment: {
|
|
1122
|
-
module: shaderModule,
|
|
1123
|
-
entryPoint: "fs_main",
|
|
1124
|
-
targets: [{ format: this.renderer.format }]
|
|
1125
|
-
},
|
|
1126
|
-
primitive: {
|
|
1127
|
-
topology: "triangle-list",
|
|
1128
|
-
cullMode: "none"
|
|
1129
|
-
},
|
|
1130
|
-
depthStencil: {
|
|
1131
|
-
format: this.renderer.depthFormat,
|
|
1132
|
-
depthWriteEnabled: false,
|
|
1133
|
-
depthCompare: "always"
|
|
1061
|
+
this.bindClickEvents();
|
|
1062
|
+
this.container.appendChild(this.svg);
|
|
1063
|
+
const parent = this.canvas.parentElement;
|
|
1064
|
+
if (parent) {
|
|
1065
|
+
const pos = getComputedStyle(parent).position;
|
|
1066
|
+
if (pos === "static") {
|
|
1067
|
+
parent.style.position = "relative";
|
|
1134
1068
|
}
|
|
1135
|
-
|
|
1069
|
+
parent.appendChild(this.container);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
createCircle(color, fill, text) {
|
|
1073
|
+
const ns = this.svg.namespaceURI;
|
|
1074
|
+
const g = document.createElementNS(ns, "g");
|
|
1075
|
+
const circle = document.createElementNS(ns, "circle");
|
|
1076
|
+
circle.setAttribute("fill", fill ? color : "#222");
|
|
1077
|
+
circle.setAttribute("stroke", color);
|
|
1078
|
+
circle.setAttribute("stroke-width", "2");
|
|
1079
|
+
circle.setAttribute("r", "10");
|
|
1080
|
+
circle.setAttribute("cx", "0");
|
|
1081
|
+
circle.setAttribute("cy", "0");
|
|
1082
|
+
circle.setAttribute("pointer-events", "all");
|
|
1083
|
+
g.appendChild(circle);
|
|
1084
|
+
g.setAttribute("cursor", "pointer");
|
|
1085
|
+
if (text) {
|
|
1086
|
+
const t = document.createElementNS(ns, "text");
|
|
1087
|
+
t.setAttribute("font-size", "10");
|
|
1088
|
+
t.setAttribute("font-family", "Arial");
|
|
1089
|
+
t.setAttribute("font-weight", "bold");
|
|
1090
|
+
t.setAttribute("text-anchor", "middle");
|
|
1091
|
+
t.setAttribute("alignment-baseline", "central");
|
|
1092
|
+
t.setAttribute("fill", "#fff");
|
|
1093
|
+
t.setAttribute("pointer-events", "none");
|
|
1094
|
+
t.textContent = text;
|
|
1095
|
+
g.appendChild(t);
|
|
1096
|
+
}
|
|
1097
|
+
this.group.appendChild(g);
|
|
1098
|
+
return g;
|
|
1099
|
+
}
|
|
1100
|
+
createLine(color) {
|
|
1101
|
+
const ns = this.svg.namespaceURI;
|
|
1102
|
+
const line = document.createElementNS(ns, "line");
|
|
1103
|
+
line.setAttribute("stroke", color);
|
|
1104
|
+
line.setAttribute("stroke-width", "2");
|
|
1105
|
+
this.group.appendChild(line);
|
|
1106
|
+
return line;
|
|
1107
|
+
}
|
|
1108
|
+
bindClickEvents() {
|
|
1109
|
+
const bind = (shape, axis, positive) => {
|
|
1110
|
+
const circle = shape.children[0];
|
|
1111
|
+
circle.addEventListener("pointerdown", (e) => {
|
|
1112
|
+
e.stopPropagation();
|
|
1113
|
+
if (this.onAxisClick) {
|
|
1114
|
+
this.onAxisClick(axis, positive);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
};
|
|
1118
|
+
bind(this.shapes.px, "X", true);
|
|
1119
|
+
bind(this.shapes.py, "Y", true);
|
|
1120
|
+
bind(this.shapes.pz, "Z", true);
|
|
1121
|
+
bind(this.shapes.nx, "X", false);
|
|
1122
|
+
bind(this.shapes.ny, "Y", false);
|
|
1123
|
+
bind(this.shapes.nz, "Z", false);
|
|
1124
|
+
}
|
|
1125
|
+
positionContainer() {
|
|
1126
|
+
const parent = this.canvas.parentElement;
|
|
1127
|
+
if (!parent) return;
|
|
1128
|
+
const canvasRect = this.canvas.getBoundingClientRect();
|
|
1129
|
+
const parentRect = parent.getBoundingClientRect();
|
|
1130
|
+
const right = parentRect.right - canvasRect.right + this.margin;
|
|
1131
|
+
const top = canvasRect.top - parentRect.top + this.margin;
|
|
1132
|
+
this.container.style.right = `${right}px`;
|
|
1133
|
+
this.container.style.top = `${top}px`;
|
|
1136
1134
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
createGeometry() {
|
|
1141
|
-
const vertices = [];
|
|
1142
|
-
const indices = [];
|
|
1143
|
-
let vertexOffset = 0;
|
|
1144
|
-
const axisLength = 0.8;
|
|
1145
|
-
const axisRadius = 0.04;
|
|
1146
|
-
const coneLength = 0.25;
|
|
1147
|
-
const coneRadius = 0.1;
|
|
1148
|
-
const segments = 12;
|
|
1149
|
-
for (const axis of this.axes) {
|
|
1150
|
-
const [dx, dy, dz] = axis.direction;
|
|
1151
|
-
const [r, g, b] = axis.color;
|
|
1152
|
-
const cylResult = this.createCylinder(
|
|
1153
|
-
[0, 0, 0],
|
|
1154
|
-
[dx * axisLength, dy * axisLength, dz * axisLength],
|
|
1155
|
-
axisRadius,
|
|
1156
|
-
segments,
|
|
1157
|
-
[r, g, b],
|
|
1158
|
-
vertexOffset
|
|
1159
|
-
);
|
|
1160
|
-
vertices.push(...cylResult.vertices);
|
|
1161
|
-
indices.push(...cylResult.indices);
|
|
1162
|
-
vertexOffset += cylResult.vertexCount;
|
|
1163
|
-
const coneStart = [
|
|
1164
|
-
dx * axisLength,
|
|
1165
|
-
dy * axisLength,
|
|
1166
|
-
dz * axisLength
|
|
1167
|
-
];
|
|
1168
|
-
const coneEnd = [
|
|
1169
|
-
dx * (axisLength + coneLength),
|
|
1170
|
-
dy * (axisLength + coneLength),
|
|
1171
|
-
dz * (axisLength + coneLength)
|
|
1172
|
-
];
|
|
1173
|
-
const coneResult = this.createCone(
|
|
1174
|
-
coneStart,
|
|
1175
|
-
coneEnd,
|
|
1176
|
-
coneRadius,
|
|
1177
|
-
segments,
|
|
1178
|
-
[r, g, b],
|
|
1179
|
-
vertexOffset
|
|
1180
|
-
);
|
|
1181
|
-
vertices.push(...coneResult.vertices);
|
|
1182
|
-
indices.push(...coneResult.indices);
|
|
1183
|
-
vertexOffset += coneResult.vertexCount;
|
|
1184
|
-
const sphereResult = this.createSphere(
|
|
1185
|
-
[-dx * 0.15, -dy * 0.15, -dz * 0.15],
|
|
1186
|
-
0.08,
|
|
1187
|
-
8,
|
|
1188
|
-
[r * 0.6, g * 0.6, b * 0.6],
|
|
1189
|
-
vertexOffset
|
|
1190
|
-
);
|
|
1191
|
-
vertices.push(...sphereResult.vertices);
|
|
1192
|
-
indices.push(...sphereResult.indices);
|
|
1193
|
-
vertexOffset += sphereResult.vertexCount;
|
|
1194
|
-
}
|
|
1195
|
-
const centerResult = this.createSphere(
|
|
1196
|
-
[0, 0, 0],
|
|
1197
|
-
0.1,
|
|
1198
|
-
12,
|
|
1199
|
-
[0.5, 0.5, 0.5],
|
|
1200
|
-
vertexOffset
|
|
1201
|
-
);
|
|
1202
|
-
vertices.push(...centerResult.vertices);
|
|
1203
|
-
indices.push(...centerResult.indices);
|
|
1204
|
-
this.vertexCount = vertices.length / 6;
|
|
1205
|
-
this.indexCount = indices.length;
|
|
1206
|
-
const vertexData = new Float32Array(vertices);
|
|
1207
|
-
const indexData = new Uint16Array(indices);
|
|
1208
|
-
const device = this.renderer.device;
|
|
1209
|
-
this.vertexBuffer = device.createBuffer({
|
|
1210
|
-
size: vertexData.byteLength,
|
|
1211
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
1212
|
-
});
|
|
1213
|
-
device.queue.writeBuffer(this.vertexBuffer, 0, vertexData);
|
|
1214
|
-
this.indexBuffer = device.createBuffer({
|
|
1215
|
-
size: indexData.byteLength,
|
|
1216
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
1135
|
+
setupResizeObserver() {
|
|
1136
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1137
|
+
this.positionContainer();
|
|
1217
1138
|
});
|
|
1218
|
-
|
|
1219
|
-
}
|
|
1220
|
-
/**
|
|
1221
|
-
* 创建圆柱体几何
|
|
1222
|
-
*/
|
|
1223
|
-
createCylinder(start, end, radius, segments, color, indexOffset) {
|
|
1224
|
-
const vertices = [];
|
|
1225
|
-
const indices = [];
|
|
1226
|
-
const dx = end[0] - start[0];
|
|
1227
|
-
const dy = end[1] - start[1];
|
|
1228
|
-
const dz = end[2] - start[2];
|
|
1229
|
-
const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1230
|
-
const dir = [dx / length, dy / length, dz / length];
|
|
1231
|
-
const up = Math.abs(dir[1]) < 0.99 ? [0, 1, 0] : [1, 0, 0];
|
|
1232
|
-
const right = this.cross(
|
|
1233
|
-
up,
|
|
1234
|
-
dir
|
|
1235
|
-
);
|
|
1236
|
-
this.normalize(right);
|
|
1237
|
-
const actualUp = this.cross(dir, right);
|
|
1238
|
-
for (let i = 0; i <= segments; i++) {
|
|
1239
|
-
const angle = i / segments * Math.PI * 2;
|
|
1240
|
-
const cos = Math.cos(angle);
|
|
1241
|
-
const sin = Math.sin(angle);
|
|
1242
|
-
const nx0 = right[0] * cos + actualUp[0] * sin;
|
|
1243
|
-
const ny0 = right[1] * cos + actualUp[1] * sin;
|
|
1244
|
-
const nz0 = right[2] * cos + actualUp[2] * sin;
|
|
1245
|
-
vertices.push(
|
|
1246
|
-
start[0] + nx0 * radius,
|
|
1247
|
-
start[1] + ny0 * radius,
|
|
1248
|
-
start[2] + nz0 * radius,
|
|
1249
|
-
color[0],
|
|
1250
|
-
color[1],
|
|
1251
|
-
color[2]
|
|
1252
|
-
);
|
|
1253
|
-
vertices.push(
|
|
1254
|
-
end[0] + nx0 * radius,
|
|
1255
|
-
end[1] + ny0 * radius,
|
|
1256
|
-
end[2] + nz0 * radius,
|
|
1257
|
-
color[0],
|
|
1258
|
-
color[1],
|
|
1259
|
-
color[2]
|
|
1260
|
-
);
|
|
1261
|
-
}
|
|
1262
|
-
for (let i = 0; i < segments; i++) {
|
|
1263
|
-
const i0 = indexOffset + i * 2;
|
|
1264
|
-
const i1 = indexOffset + i * 2 + 1;
|
|
1265
|
-
const i2 = indexOffset + (i + 1) * 2;
|
|
1266
|
-
const i3 = indexOffset + (i + 1) * 2 + 1;
|
|
1267
|
-
indices.push(i0, i1, i2, i2, i1, i3);
|
|
1268
|
-
}
|
|
1269
|
-
return { vertices, indices, vertexCount: (segments + 1) * 2 };
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* 创建圆锥几何
|
|
1273
|
-
*/
|
|
1274
|
-
createCone(base, tip, radius, segments, color, indexOffset) {
|
|
1275
|
-
const vertices = [];
|
|
1276
|
-
const indices = [];
|
|
1277
|
-
const dx = tip[0] - base[0];
|
|
1278
|
-
const dy = tip[1] - base[1];
|
|
1279
|
-
const dz = tip[2] - base[2];
|
|
1280
|
-
const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1281
|
-
const dir = [dx / length, dy / length, dz / length];
|
|
1282
|
-
const up = Math.abs(dir[1]) < 0.99 ? [0, 1, 0] : [1, 0, 0];
|
|
1283
|
-
const right = this.cross(
|
|
1284
|
-
up,
|
|
1285
|
-
dir
|
|
1286
|
-
);
|
|
1287
|
-
this.normalize(right);
|
|
1288
|
-
const actualUp = this.cross(dir, right);
|
|
1289
|
-
vertices.push(tip[0], tip[1], tip[2], color[0], color[1], color[2]);
|
|
1290
|
-
for (let i = 0; i <= segments; i++) {
|
|
1291
|
-
const angle = i / segments * Math.PI * 2;
|
|
1292
|
-
const cos = Math.cos(angle);
|
|
1293
|
-
const sin = Math.sin(angle);
|
|
1294
|
-
const nx = right[0] * cos + actualUp[0] * sin;
|
|
1295
|
-
const ny = right[1] * cos + actualUp[1] * sin;
|
|
1296
|
-
const nz = right[2] * cos + actualUp[2] * sin;
|
|
1297
|
-
vertices.push(
|
|
1298
|
-
base[0] + nx * radius,
|
|
1299
|
-
base[1] + ny * radius,
|
|
1300
|
-
base[2] + nz * radius,
|
|
1301
|
-
color[0],
|
|
1302
|
-
color[1],
|
|
1303
|
-
color[2]
|
|
1304
|
-
);
|
|
1305
|
-
}
|
|
1306
|
-
for (let i = 0; i < segments; i++) {
|
|
1307
|
-
indices.push(indexOffset, indexOffset + i + 1, indexOffset + i + 2);
|
|
1308
|
-
}
|
|
1309
|
-
const baseCenterIdx = indexOffset + segments + 2;
|
|
1310
|
-
vertices.push(
|
|
1311
|
-
base[0],
|
|
1312
|
-
base[1],
|
|
1313
|
-
base[2],
|
|
1314
|
-
color[0] * 0.7,
|
|
1315
|
-
color[1] * 0.7,
|
|
1316
|
-
color[2] * 0.7
|
|
1317
|
-
);
|
|
1318
|
-
for (let i = 0; i < segments; i++) {
|
|
1319
|
-
indices.push(baseCenterIdx, indexOffset + i + 2, indexOffset + i + 1);
|
|
1320
|
-
}
|
|
1321
|
-
return { vertices, indices, vertexCount: segments + 3 };
|
|
1139
|
+
this.resizeObserver.observe(this.canvas);
|
|
1322
1140
|
}
|
|
1323
1141
|
/**
|
|
1324
|
-
*
|
|
1142
|
+
* 每帧更新 SVG 位置(从相机视图矩阵中提取轴投影)
|
|
1325
1143
|
*/
|
|
1326
|
-
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1144
|
+
render(_pass) {
|
|
1145
|
+
const vm = this.camera.viewMatrix;
|
|
1146
|
+
const vecx = { x: vm[0], y: vm[1], z: vm[2] };
|
|
1147
|
+
const vecy = { x: vm[4], y: vm[5], z: vm[6] };
|
|
1148
|
+
const vecz = { x: vm[8], y: vm[9], z: vm[10] };
|
|
1149
|
+
const s = this.scale;
|
|
1150
|
+
const setTransform = (el, x, y) => {
|
|
1151
|
+
el.setAttribute("transform", `translate(${x * s}, ${y * s})`);
|
|
1152
|
+
};
|
|
1153
|
+
const setLine = (line, x, y) => {
|
|
1154
|
+
line.setAttribute("x2", (x * s).toString());
|
|
1155
|
+
line.setAttribute("y2", (y * s).toString());
|
|
1156
|
+
};
|
|
1157
|
+
setTransform(this.shapes.px, vecx.x, -vecx.y);
|
|
1158
|
+
setTransform(this.shapes.nx, -vecx.x, vecx.y);
|
|
1159
|
+
setTransform(this.shapes.py, vecy.x, -vecy.y);
|
|
1160
|
+
setTransform(this.shapes.ny, -vecy.x, vecy.y);
|
|
1161
|
+
setTransform(this.shapes.pz, vecz.x, -vecz.y);
|
|
1162
|
+
setTransform(this.shapes.nz, -vecz.x, vecz.y);
|
|
1163
|
+
setLine(this.shapes.xaxis, vecx.x, -vecx.y);
|
|
1164
|
+
setLine(this.shapes.yaxis, vecy.x, -vecy.y);
|
|
1165
|
+
setLine(this.shapes.zaxis, vecz.x, -vecz.y);
|
|
1166
|
+
const order = [
|
|
1167
|
+
{ keys: ["xaxis", "px"], depth: vecx.z },
|
|
1168
|
+
{ keys: ["yaxis", "py"], depth: vecy.z },
|
|
1169
|
+
{ keys: ["zaxis", "pz"], depth: vecz.z },
|
|
1170
|
+
{ keys: ["nx"], depth: -vecx.z },
|
|
1171
|
+
{ keys: ["ny"], depth: -vecy.z },
|
|
1172
|
+
{ keys: ["nz"], depth: -vecz.z }
|
|
1173
|
+
].sort((a, b) => a.depth - b.depth);
|
|
1174
|
+
const fragment = document.createDocumentFragment();
|
|
1175
|
+
for (const item of order) {
|
|
1176
|
+
for (const key of item.keys) {
|
|
1177
|
+
fragment.appendChild(this.shapes[key]);
|
|
1348
1178
|
}
|
|
1349
1179
|
}
|
|
1350
|
-
|
|
1180
|
+
this.group.appendChild(fragment);
|
|
1351
1181
|
}
|
|
1352
1182
|
/**
|
|
1353
|
-
*
|
|
1183
|
+
* 点击检测 — SVG 版本通过 DOM 事件处理,此方法仅保留接口兼容
|
|
1354
1184
|
*/
|
|
1355
|
-
|
|
1356
|
-
const device = this.renderer.device;
|
|
1357
|
-
this.uniformBuffer = device.createBuffer({
|
|
1358
|
-
size: 128,
|
|
1359
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
1360
|
-
});
|
|
1361
|
-
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
1362
|
-
this.bindGroup = device.createBindGroup({
|
|
1363
|
-
layout: bindGroupLayout,
|
|
1364
|
-
entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }]
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
/**
|
|
1368
|
-
* 设置正交投影矩阵
|
|
1369
|
-
*/
|
|
1370
|
-
setupOrthoProjection() {
|
|
1371
|
-
const s = 1.5;
|
|
1372
|
-
this.projMatrix[0] = 1 / s;
|
|
1373
|
-
this.projMatrix[5] = 1 / s;
|
|
1374
|
-
this.projMatrix[10] = -1 / 10;
|
|
1375
|
-
this.projMatrix[14] = 0;
|
|
1376
|
-
this.projMatrix[15] = 1;
|
|
1377
|
-
}
|
|
1378
|
-
/**
|
|
1379
|
-
* 更新 Gizmo 视图矩阵(从相机提取旋转部分)
|
|
1380
|
-
*/
|
|
1381
|
-
updateViewMatrix() {
|
|
1382
|
-
const camView = this.camera.viewMatrix;
|
|
1383
|
-
this.viewMatrix[0] = camView[0];
|
|
1384
|
-
this.viewMatrix[1] = camView[1];
|
|
1385
|
-
this.viewMatrix[2] = camView[2];
|
|
1386
|
-
this.viewMatrix[3] = 0;
|
|
1387
|
-
this.viewMatrix[4] = camView[4];
|
|
1388
|
-
this.viewMatrix[5] = camView[5];
|
|
1389
|
-
this.viewMatrix[6] = camView[6];
|
|
1390
|
-
this.viewMatrix[7] = 0;
|
|
1391
|
-
this.viewMatrix[8] = camView[8];
|
|
1392
|
-
this.viewMatrix[9] = camView[9];
|
|
1393
|
-
this.viewMatrix[10] = camView[10];
|
|
1394
|
-
this.viewMatrix[11] = 0;
|
|
1395
|
-
this.viewMatrix[12] = 0;
|
|
1396
|
-
this.viewMatrix[13] = 0;
|
|
1397
|
-
this.viewMatrix[14] = -3;
|
|
1398
|
-
this.viewMatrix[15] = 1;
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* 渲染 Gizmo
|
|
1402
|
-
*/
|
|
1403
|
-
render(pass) {
|
|
1404
|
-
this.updateViewMatrix();
|
|
1405
|
-
const dpr = window.devicePixelRatio || 1;
|
|
1406
|
-
let gizmoSize = Math.floor(this.size * dpr);
|
|
1407
|
-
const marginX = Math.floor(this.margin * dpr);
|
|
1408
|
-
const marginY = Math.floor(this.margin * dpr);
|
|
1409
|
-
const maxSize = Math.min(
|
|
1410
|
-
this.canvas.width - marginX * 2,
|
|
1411
|
-
this.canvas.height - marginY * 2
|
|
1412
|
-
);
|
|
1413
|
-
if (maxSize < 50) {
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
gizmoSize = Math.min(gizmoSize, maxSize);
|
|
1417
|
-
const x = Math.max(0, this.canvas.width - gizmoSize - marginX);
|
|
1418
|
-
const y = marginY;
|
|
1419
|
-
pass.setViewport(x, y, gizmoSize, gizmoSize, 0, 1);
|
|
1420
|
-
pass.setScissorRect(x, y, gizmoSize, gizmoSize);
|
|
1421
|
-
this.renderer.device.queue.writeBuffer(
|
|
1422
|
-
this.uniformBuffer,
|
|
1423
|
-
0,
|
|
1424
|
-
new Float32Array(this.viewMatrix)
|
|
1425
|
-
);
|
|
1426
|
-
this.renderer.device.queue.writeBuffer(
|
|
1427
|
-
this.uniformBuffer,
|
|
1428
|
-
64,
|
|
1429
|
-
new Float32Array(this.projMatrix)
|
|
1430
|
-
);
|
|
1431
|
-
pass.setPipeline(this.pipeline);
|
|
1432
|
-
pass.setBindGroup(0, this.bindGroup);
|
|
1433
|
-
pass.setVertexBuffer(0, this.vertexBuffer);
|
|
1434
|
-
pass.setIndexBuffer(this.indexBuffer, "uint16");
|
|
1435
|
-
pass.drawIndexed(this.indexCount);
|
|
1436
|
-
pass.setViewport(0, 0, this.canvas.width, this.canvas.height, 0, 1);
|
|
1437
|
-
pass.setScissorRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* 处理点击事件,检测是否点击了某个轴
|
|
1441
|
-
*/
|
|
1442
|
-
handleClick(clientX, clientY) {
|
|
1443
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
1444
|
-
const gizmoSize = this.size;
|
|
1445
|
-
const marginX = this.margin;
|
|
1446
|
-
const marginY = this.margin;
|
|
1447
|
-
const gizmoLeft = rect.right - gizmoSize - marginX;
|
|
1448
|
-
const gizmoTop = rect.top + marginY;
|
|
1449
|
-
const gizmoRight = gizmoLeft + gizmoSize;
|
|
1450
|
-
const gizmoBottom = gizmoTop + gizmoSize;
|
|
1451
|
-
if (clientX < gizmoLeft || clientX > gizmoRight || clientY < gizmoTop || clientY > gizmoBottom) {
|
|
1452
|
-
return false;
|
|
1453
|
-
}
|
|
1454
|
-
const relX = (clientX - gizmoLeft) / gizmoSize * 2 - 1;
|
|
1455
|
-
const relY = -((clientY - gizmoTop) / gizmoSize * 2 - 1);
|
|
1456
|
-
const clickedAxis = this.detectClickedAxis(relX, relY);
|
|
1457
|
-
if (clickedAxis && this.onAxisClick) {
|
|
1458
|
-
this.onAxisClick(clickedAxis.axis, clickedAxis.positive);
|
|
1459
|
-
return true;
|
|
1460
|
-
}
|
|
1185
|
+
handleClick(_clientX, _clientY) {
|
|
1461
1186
|
return false;
|
|
1462
1187
|
}
|
|
1463
|
-
/**
|
|
1464
|
-
* 检测点击了哪个轴
|
|
1465
|
-
*/
|
|
1466
|
-
detectClickedAxis(relX, relY) {
|
|
1467
|
-
const threshold = 0.4;
|
|
1468
|
-
for (const axis of this.axes) {
|
|
1469
|
-
const [dx, dy, dz] = axis.direction;
|
|
1470
|
-
const posX = this.viewMatrix[0] * dx + this.viewMatrix[4] * dy + this.viewMatrix[8] * dz;
|
|
1471
|
-
const posY = this.viewMatrix[1] * dx + this.viewMatrix[5] * dy + this.viewMatrix[9] * dz;
|
|
1472
|
-
const distPos = Math.sqrt(
|
|
1473
|
-
(relX - posX * 0.5) ** 2 + (relY - posY * 0.5) ** 2
|
|
1474
|
-
);
|
|
1475
|
-
if (distPos < threshold) {
|
|
1476
|
-
return { axis: axis.label, positive: true };
|
|
1477
|
-
}
|
|
1478
|
-
const distNeg = Math.sqrt(
|
|
1479
|
-
(relX + posX * 0.15) ** 2 + (relY + posY * 0.15) ** 2
|
|
1480
|
-
);
|
|
1481
|
-
if (distNeg < threshold * 0.5) {
|
|
1482
|
-
return { axis: axis.label, positive: false };
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
return null;
|
|
1486
|
-
}
|
|
1487
|
-
// 向量工具函数
|
|
1488
|
-
cross(a, b) {
|
|
1489
|
-
return [
|
|
1490
|
-
a[1] * b[2] - a[2] * b[1],
|
|
1491
|
-
a[2] * b[0] - a[0] * b[2],
|
|
1492
|
-
a[0] * b[1] - a[1] * b[0]
|
|
1493
|
-
];
|
|
1494
|
-
}
|
|
1495
|
-
normalize(v) {
|
|
1496
|
-
const len = Math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2);
|
|
1497
|
-
if (len > 0) {
|
|
1498
|
-
v[0] /= len;
|
|
1499
|
-
v[1] /= len;
|
|
1500
|
-
v[2] /= len;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* 设置 Gizmo 大小
|
|
1505
|
-
*/
|
|
1506
1188
|
setSize(size) {
|
|
1507
1189
|
this.size = size;
|
|
1190
|
+
if (this.container) {
|
|
1191
|
+
this.container.style.width = `${size}px`;
|
|
1192
|
+
this.container.style.height = `${size}px`;
|
|
1193
|
+
this.svg.setAttribute("width", size.toString());
|
|
1194
|
+
this.svg.setAttribute("height", size.toString());
|
|
1195
|
+
this.group.setAttribute(
|
|
1196
|
+
"transform",
|
|
1197
|
+
`translate(${size / 2}, ${size / 2})`
|
|
1198
|
+
);
|
|
1199
|
+
this.positionContainer();
|
|
1200
|
+
}
|
|
1508
1201
|
}
|
|
1509
|
-
/**
|
|
1510
|
-
* 设置边距
|
|
1511
|
-
*/
|
|
1512
1202
|
setMargin(margin) {
|
|
1513
1203
|
this.margin = margin;
|
|
1204
|
+
this.positionContainer();
|
|
1205
|
+
}
|
|
1206
|
+
destroy() {
|
|
1207
|
+
var _a2;
|
|
1208
|
+
if (this.resizeObserver) {
|
|
1209
|
+
this.resizeObserver.disconnect();
|
|
1210
|
+
}
|
|
1211
|
+
(_a2 = this.container) == null ? void 0 : _a2.remove();
|
|
1514
1212
|
}
|
|
1515
1213
|
}
|
|
1516
1214
|
class BoundingBoxRenderer {
|
|
@@ -13592,6 +13290,7 @@ class GizmoManager {
|
|
|
13592
13290
|
this.canvas.removeEventListener("pointermove", this.boundOnPointerMove);
|
|
13593
13291
|
this.canvas.removeEventListener("pointerdown", this.boundOnPointerDown);
|
|
13594
13292
|
this.canvas.removeEventListener("pointerup", this.boundOnPointerUp);
|
|
13293
|
+
this.viewportGizmo.destroy();
|
|
13595
13294
|
this.transformGizmo.destroy();
|
|
13596
13295
|
this.boundingBoxRenderer.destroy();
|
|
13597
13296
|
}
|