diamon-engine 0.1.0

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.
@@ -0,0 +1,713 @@
1
+ import * as l from "three";
2
+ import { GLTFLoader as L } from "three/examples/jsm/loaders/GLTFLoader.js";
3
+ import { E as m, S as A, O as y, M as $, a as P, L as R, C as E } from "./OrbitCameraController-COklTntj.js";
4
+ const U = {
5
+ box: "#1ec7a6",
6
+ sphere: "#f5c95f",
7
+ cylinder: "#4ba3ff",
8
+ cone: "#ef6f50",
9
+ torus: "#d989ff"
10
+ }, M = {
11
+ box: "Cube",
12
+ sphere: "Sphere",
13
+ cylinder: "Cylinder",
14
+ cone: "Cone",
15
+ torus: "Torus"
16
+ }, T = {
17
+ box: { kind: "box", width: 1, height: 1, depth: 1 },
18
+ sphere: { kind: "sphere", radius: 0.62 },
19
+ cylinder: { kind: "cylinder", radius: 0.52, height: 1.15 },
20
+ cone: { kind: "cone", radius: 0.6, height: 1.25 },
21
+ torus: { kind: "torus", radius: 0.62, tube: 0.18 }
22
+ }, D = 700, H = 2500, F = 12e4, _ = 8e3, B = 5e9, j = 0.07;
23
+ function te(r) {
24
+ return new G(r);
25
+ }
26
+ class G {
27
+ constructor(e) {
28
+ this.ui = e, this.scene.threeScene.background = new l.Color("#07100f"), this.scene.threeScene.fog = new l.Fog("#07100f", 32, 90), this.scene.add(q()), this.scene.add(z()), this.scene.add(W()), this.scene.add(K()), this.scene.add(V());
29
+ const t = new m("Workbench Runtime");
30
+ t.addComponent(new N(() => this.updateRuntimeUi(), 0.25)), this.scene.add(t), this.addPrimitive("box"), this.addPrimitive("sphere"), this.addPrimitive("cylinder"), this.commitHistory("初始场景"), this.bindCanvasPicking(), this.refreshUi(), this.log("引擎编辑器已启动");
31
+ }
32
+ ui;
33
+ scene = new A("Engine Workbench");
34
+ objects = /* @__PURE__ */ new Map();
35
+ resources = /* @__PURE__ */ new Map();
36
+ prefabs = /* @__PURE__ */ new Map();
37
+ selectedIds = /* @__PURE__ */ new Set();
38
+ undoStack = [];
39
+ redoStack = [];
40
+ clipboard = [];
41
+ raycaster = new l.Raycaster();
42
+ pointer = new l.Vector2();
43
+ gltfLoader = new L();
44
+ objectSerial = 0;
45
+ resourceSerial = 0;
46
+ prefabSerial = 0;
47
+ groupSerial = 0;
48
+ lastStressResult = "未测试";
49
+ restoring = !1;
50
+ addPrimitive(e) {
51
+ const t = this.createPrimitiveRecord(e);
52
+ return this.selectOnly(t.id), this.commitHistory(`添加 ${M[e]}`), this.refreshUi(), t.entity;
53
+ }
54
+ copySelection() {
55
+ this.clipboard.length = 0;
56
+ for (const e of this.getSelectedRecords())
57
+ this.clipboard.push(this.serializeObject(e, !1));
58
+ this.log(`已复制 ${this.clipboard.length} 个对象`), this.refreshUi();
59
+ }
60
+ pasteSelection() {
61
+ if (this.clipboard.length === 0) {
62
+ this.log("没有可粘贴对象");
63
+ return;
64
+ }
65
+ this.selectedIds.clear();
66
+ for (const e of this.clipboard) {
67
+ const t = {
68
+ ...e,
69
+ name: `${e.name} Copy`,
70
+ position: [
71
+ e.position[0] + 0.75,
72
+ e.position[1],
73
+ e.position[2] + 0.75
74
+ ]
75
+ }, s = this.createRecordFromSerialized(t);
76
+ this.selectedIds.add(s.id);
77
+ }
78
+ this.commitHistory("粘贴对象"), this.refreshUi();
79
+ }
80
+ deleteSelection() {
81
+ const e = this.getSelectedRecords();
82
+ if (e.length === 0) {
83
+ this.log("没有选中对象");
84
+ return;
85
+ }
86
+ for (const t of e)
87
+ this.removeRecord(t);
88
+ this.selectedIds.clear(), this.commitHistory(`删除 ${e.length} 个对象`), this.refreshUi();
89
+ }
90
+ undo() {
91
+ if (this.undoStack.length <= 1) {
92
+ this.log("没有可撤销操作");
93
+ return;
94
+ }
95
+ const e = this.undoStack.pop();
96
+ e && this.redoStack.push(e);
97
+ const t = this.undoStack[this.undoStack.length - 1];
98
+ this.restoreSnapshot(t), this.log("已撤销");
99
+ }
100
+ redo() {
101
+ const e = this.redoStack.pop();
102
+ if (!e) {
103
+ this.log("没有可重做操作");
104
+ return;
105
+ }
106
+ this.undoStack.push(e), this.restoreSnapshot(e), this.log("已重做");
107
+ }
108
+ selectAll() {
109
+ this.selectedIds.clear();
110
+ for (const e of this.objects.keys())
111
+ this.selectedIds.add(e);
112
+ this.refreshUi(), this.log(`已选择 ${this.selectedIds.size} 个对象`);
113
+ }
114
+ createGroupFromSelection() {
115
+ const e = this.getSelectedRecords();
116
+ if (e.length === 0) {
117
+ this.log("请先选择对象再分组");
118
+ return;
119
+ }
120
+ this.groupSerial += 1;
121
+ const t = `Group ${this.groupSerial}`;
122
+ for (const s of e)
123
+ s.group = t;
124
+ this.commitHistory(`创建分组 ${t}`), this.refreshUi();
125
+ }
126
+ saveScene() {
127
+ const e = this.makeSnapshot(), t = JSON.stringify(e, null, 2);
128
+ this.ui.sceneData.value = t, window.localStorage.setItem("diamon-engine-scene", t), this.log("场景已保存到面板和本地缓存");
129
+ }
130
+ loadScene() {
131
+ const e = this.ui.sceneData.value.trim() || window.localStorage.getItem("diamon-engine-scene");
132
+ if (!e) {
133
+ this.log("没有可加载的场景数据");
134
+ return;
135
+ }
136
+ try {
137
+ const t = JSON.parse(e);
138
+ this.restoreSnapshot(t), this.commitHistory("加载场景"), this.log("场景加载完成");
139
+ } catch (t) {
140
+ this.log(`场景加载失败:${t instanceof Error ? t.message : String(t)}`);
141
+ }
142
+ }
143
+ downloadScene() {
144
+ this.saveScene(), this.downloadText("diamon-scene.json", this.ui.sceneData.value, "application/json");
145
+ }
146
+ createPrefabFromSelection() {
147
+ const e = this.getSelectedRecords();
148
+ if (e.length === 0) {
149
+ this.log("请先选择对象再保存预制体");
150
+ return;
151
+ }
152
+ this.prefabSerial += 1;
153
+ const t = {
154
+ id: `prefab-${this.prefabSerial}`,
155
+ name: `Prefab ${this.prefabSerial}`,
156
+ objects: e.map((s) => this.serializeObject(s, !1))
157
+ };
158
+ this.prefabs.set(t.id, t), this.commitHistory(`保存预制体 ${t.name}`), this.refreshUi(), this.log(`已保存预制体:${t.name}`);
159
+ }
160
+ addStressObjects(e) {
161
+ const t = performance.now();
162
+ this.selectedIds.clear();
163
+ const s = Math.ceil(Math.sqrt(e));
164
+ for (let o = 0; o < e; o += 1) {
165
+ const i = this.createPrimitiveRecord("box", {
166
+ name: `Stress Cube ${this.objectSerial + 1}`,
167
+ color: o % 2 === 0 ? "#1ec7a6" : "#4ba3ff",
168
+ position: [
169
+ o % s * 0.78 - s * 0.39,
170
+ 0.45,
171
+ Math.floor(o / s) * 0.78 + 5
172
+ ],
173
+ layer: 2,
174
+ group: "Stress Objects"
175
+ });
176
+ o < 20 && this.selectedIds.add(i.id);
177
+ }
178
+ const n = Math.round(performance.now() - t);
179
+ this.lastStressResult = `新增 ${e} 个对象,用时 ${n}ms`, this.commitHistory("大量对象压力测试"), this.refreshUi(), this.log(this.lastStressResult);
180
+ }
181
+ addInstancedStressObjects(e) {
182
+ const t = performance.now(), s = new m(`Instanced Stress ${e}`);
183
+ s.tag = "workbench-object";
184
+ const n = new l.BoxGeometry(0.42, 0.42, 0.42), o = new l.MeshStandardMaterial({
185
+ color: "#1ec7a6",
186
+ roughness: 0.65,
187
+ metalness: 0.08
188
+ }), i = new l.InstancedMesh(n, o, e);
189
+ i.castShadow = !1, i.receiveShadow = !1;
190
+ const c = new l.Matrix4(), a = Math.ceil(Math.sqrt(e));
191
+ for (let f = 0; f < e; f += 1) {
192
+ const g = f % a * 0.55 - a * 0.275, S = Math.floor(f / a) * 0.55 + 16;
193
+ c.makeTranslation(g, 0.35, S), i.setMatrixAt(f, c);
194
+ }
195
+ i.instanceMatrix.needsUpdate = !0, i.userData.workbenchId = `object-${this.objectSerial + 1}`, s.addComponent(new y(i, { disposeOnDestroy: !0 }));
196
+ const d = this.nextObjectId(), u = {
197
+ id: d,
198
+ entity: s,
199
+ kind: "instanced",
200
+ primitiveKind: "box",
201
+ instancedCount: e,
202
+ color: "#1ec7a6",
203
+ layer: 2,
204
+ group: "Instanced Stress",
205
+ locked: !1,
206
+ visible: !0
207
+ };
208
+ i.userData.workbenchId = d, this.scene.add(s), this.objects.set(d, u), this.selectOnly(d);
209
+ const v = Math.round(performance.now() - t);
210
+ this.lastStressResult = `实例化 ${e} 个对象,用时 ${v}ms`, this.commitHistory("实例化压力测试"), this.refreshUi(), this.log(this.lastStressResult);
211
+ }
212
+ addMegaInstancedStressObjects(e) {
213
+ const t = performance.now(), s = this.getMegaStressRecord();
214
+ if (s) {
215
+ const n = (s.instancedCount ?? 0) + e;
216
+ this.updateMegaStressRecord(s, n, t, "合并极限压力层", "极限实例化累计");
217
+ return;
218
+ }
219
+ this.createMegaStressRecord(e, t, "500万对象极限压力测试", "极限实例化");
220
+ }
221
+ setMegaInstancedStressObjects(e) {
222
+ const t = performance.now(), s = Y(e), n = `设置${s}极限压力层`, o = `${s}目标`, i = this.getMegaStressRecord();
223
+ if (i) {
224
+ this.updateMegaStressRecord(i, e, t, n, o);
225
+ return;
226
+ }
227
+ this.createMegaStressRecord(e, t, n, o);
228
+ }
229
+ importAssets(e) {
230
+ const t = Array.from(e);
231
+ if (t.length !== 0) {
232
+ for (const s of t)
233
+ this.registerAsset(s);
234
+ this.commitHistory("导入资源"), this.refreshUi();
235
+ }
236
+ }
237
+ clearLogs() {
238
+ this.ui.logList.innerHTML = "";
239
+ }
240
+ stats() {
241
+ return {
242
+ objects: this.objects.size,
243
+ logicalObjects: this.logicalObjectCount(),
244
+ resources: this.resources.size,
245
+ prefabs: this.prefabs.size,
246
+ selected: this.selectedIds.size,
247
+ lastStressResult: this.lastStressResult
248
+ };
249
+ }
250
+ createPrimitiveRecord(e, t = {}) {
251
+ const s = this.nextObjectId(), n = this.objectSerial, o = new m(t.name ?? `${M[e]} ${n}`);
252
+ o.tag = "workbench-object";
253
+ const i = t.position ?? [
254
+ -3 + n % 6 * 1.15,
255
+ 0.62,
256
+ Math.floor(n / 6) * 1.15
257
+ ];
258
+ o.position.set(i[0], i[1], i[2]), t.rotation && o.rotation.set(t.rotation[0], t.rotation[1], t.rotation[2]), t.scale && o.scale.set(t.scale[0], t.scale[1], t.scale[2]);
259
+ const c = t.color ?? U[e];
260
+ o.addComponent(
261
+ new $({
262
+ ...T[e],
263
+ color: c,
264
+ segments: 24,
265
+ metalness: 0.14,
266
+ roughness: 0.5
267
+ })
268
+ ), o.object.traverse((d) => {
269
+ d.userData.workbenchId = s;
270
+ });
271
+ const a = {
272
+ id: s,
273
+ entity: o,
274
+ kind: "primitive",
275
+ primitiveKind: e,
276
+ color: c,
277
+ layer: t.layer ?? 0,
278
+ group: t.group ?? "",
279
+ locked: t.locked ?? !1,
280
+ visible: t.visible ?? !0
281
+ };
282
+ return o.enabled = a.visible, o.object.visible = a.visible, this.scene.add(o), this.objects.set(s, a), a;
283
+ }
284
+ createRecordFromSerialized(e) {
285
+ return e.kind === "mega-instanced" ? this.createMegaInstancedRecordFromSerialized(e) : e.kind === "instanced" ? this.createInstancedRecordFromSerialized(e) : e.kind === "model" ? this.createModelPlaceholder(e) : this.createPrimitiveRecord(e.primitiveKind ?? "box", e);
286
+ }
287
+ createModelPlaceholder(e) {
288
+ const t = this.createPrimitiveRecord("box", {
289
+ ...e,
290
+ color: e.color || "#ef6f50"
291
+ });
292
+ return t.kind = "model", t.resourceId = e.resourceId, t.entity.name = e.name, t.entity.object.name = e.name, t;
293
+ }
294
+ createInstancedRecordFromSerialized(e) {
295
+ const t = e.instancedCount ?? 100;
296
+ this.addInstancedStressObjects(t);
297
+ const s = this.getSelectedRecords()[0];
298
+ return s.entity.name = e.name, s.entity.object.name = e.name, s.entity.position.set(...e.position), s.entity.rotation.set(...e.rotation), s.entity.scale.set(...e.scale), s.layer = e.layer, s.group = e.group, s.locked = e.locked, s.visible = e.visible, s;
299
+ }
300
+ createMegaInstancedRecordFromSerialized(e) {
301
+ const t = e.instancedCount ?? 5e6;
302
+ this.setMegaInstancedStressObjects(t);
303
+ const s = this.getSelectedRecords()[0];
304
+ return s.entity.name = e.name, s.entity.object.name = e.name, s.entity.position.set(...e.position), s.entity.rotation.set(...e.rotation), s.entity.scale.set(...e.scale), s.layer = e.layer, s.group = e.group, s.locked = e.locked, s.visible = e.visible, s.entity.enabled = s.visible, s.entity.object.visible = s.visible, s;
305
+ }
306
+ nextObjectId() {
307
+ return this.objectSerial += 1, `object-${this.objectSerial}`;
308
+ }
309
+ registerAsset(e) {
310
+ this.resourceSerial += 1;
311
+ const t = `asset-${this.resourceSerial}`, s = J(e.name), n = URL.createObjectURL(e), o = {
312
+ id: t,
313
+ kind: s,
314
+ name: e.name,
315
+ size: e.size,
316
+ url: n
317
+ };
318
+ this.resources.set(t, o), this.log(`导入资源:${e.name}`), s === "model" && this.gltfLoader.load(
319
+ n,
320
+ (i) => {
321
+ const c = new m(e.name.replace(/\.[^.]+$/, ""));
322
+ c.tag = "workbench-object", c.position.set(0, 0, 3 + this.objects.size * 0.45), i.scene.traverse((u) => {
323
+ u.userData.workbenchId = t, u instanceof l.Mesh && (u.castShadow = !0, u.receiveShadow = !0);
324
+ }), c.addComponent(new y(i.scene));
325
+ const a = this.nextObjectId();
326
+ i.scene.traverse((u) => {
327
+ u.userData.workbenchId = a;
328
+ });
329
+ const d = {
330
+ id: a,
331
+ entity: c,
332
+ kind: "model",
333
+ resourceId: t,
334
+ color: "#ffffff",
335
+ layer: 0,
336
+ group: "Imported Models",
337
+ locked: !1,
338
+ visible: !0
339
+ };
340
+ this.scene.add(c), this.objects.set(a, d), this.selectOnly(a), this.commitHistory("导入模型"), this.refreshUi(), this.log(`模型已加入场景:${e.name}`);
341
+ },
342
+ void 0,
343
+ (i) => this.log(`模型加载失败:${i instanceof Error ? i.message : String(i)}`)
344
+ );
345
+ }
346
+ createMegaStressRecord(e, t, s, n) {
347
+ const o = this.nextObjectId(), i = new m(`Mega Instance Stress ${e}`);
348
+ i.tag = "workbench-object";
349
+ const c = this.createMegaStressPoints(e, o);
350
+ i.addComponent(new y(c, { disposeOnDestroy: !0 }));
351
+ const a = {
352
+ id: o,
353
+ entity: i,
354
+ kind: "mega-instanced",
355
+ primitiveKind: "box",
356
+ instancedCount: e,
357
+ color: "#75f0c8",
358
+ layer: 2,
359
+ group: "Mega Instance Stress",
360
+ locked: !1,
361
+ visible: !0
362
+ };
363
+ return this.scene.add(i), this.objects.set(o, a), this.selectOnly(o), this.finishMegaStressUpdate(a, t, s, n), a;
364
+ }
365
+ updateMegaStressRecord(e, t, s, n, o) {
366
+ const i = this.createMegaStressPoints(t, e.id);
367
+ e.entity.getComponent(y)?.replaceObject(i), e.instancedCount = t, e.entity.name = `Mega Instance Stress ${t}`, e.entity.object.name = e.entity.name, this.selectOnly(e.id), this.finishMegaStressUpdate(e, s, n, o);
368
+ }
369
+ finishMegaStressUpdate(e, t, s, n) {
370
+ const o = e.instancedCount ?? 0, i = C(o), c = Math.round(performance.now() - t);
371
+ this.lastStressResult = `${n} ${o} 个对象,渲染代表点 ${i},用时 ${c}ms`, this.commitHistory(s), this.refreshUi(), this.log(this.lastStressResult);
372
+ }
373
+ createMegaStressPoints(e, t) {
374
+ const s = C(e), n = new Float32Array(s * 3), o = H, i = Math.ceil(e / o), c = (o - 1) * j, a = (i - 1) * j;
375
+ for (let g = 0; g < s; g += 1) {
376
+ const S = g * 3, k = Math.min(e - 1, Math.floor(g / s * e)), I = k % o, O = Math.floor(k / o);
377
+ n[S] = I * j - c * 0.5, n[S + 1] = 0.32 + g * 17 % 11 * 0.01, n[S + 2] = O * j + 8;
378
+ }
379
+ const d = new l.BufferGeometry(), u = new l.BufferAttribute(n, 3);
380
+ u.setUsage(l.StaticDrawUsage), d.setAttribute("position", u), d.boundingSphere = new l.Sphere(
381
+ new l.Vector3(0, 0.4, 8 + a * 0.5),
382
+ Math.sqrt((c * 0.5) ** 2 + (a * 0.5) ** 2 + 2)
383
+ );
384
+ const v = new l.PointsMaterial({
385
+ color: "#75f0c8",
386
+ size: 0.055,
387
+ sizeAttenuation: !0,
388
+ depthWrite: !1
389
+ }), f = new l.Points(d, v);
390
+ return f.frustumCulled = !1, f.userData.workbenchId = t, f;
391
+ }
392
+ selectOnly(e) {
393
+ this.selectedIds.clear(), this.selectedIds.add(e);
394
+ }
395
+ getSelectedRecords() {
396
+ return Array.from(this.selectedIds).map((e) => this.objects.get(e)).filter((e) => !!e);
397
+ }
398
+ getMegaStressRecord() {
399
+ return Array.from(this.objects.values()).find((e) => e.kind === "mega-instanced");
400
+ }
401
+ primarySelection() {
402
+ return this.getSelectedRecords()[0];
403
+ }
404
+ removeRecord(e) {
405
+ this.scene.remove(e.entity), this.objects.delete(e.id);
406
+ }
407
+ commitHistory(e) {
408
+ this.restoring || (this.undoStack.push(this.makeSnapshot()), this.undoStack.length > 60 && this.undoStack.shift(), this.redoStack.length = 0, e && this.log(e));
409
+ }
410
+ makeSnapshot() {
411
+ return {
412
+ version: 1,
413
+ objectSerial: this.objectSerial,
414
+ resourceSerial: this.resourceSerial,
415
+ prefabSerial: this.prefabSerial,
416
+ objects: Array.from(this.objects.values()).map((e) => this.serializeObject(e, !0)),
417
+ resources: Array.from(this.resources.values()).map((e) => ({ ...e })),
418
+ prefabs: Array.from(this.prefabs.values()).map((e) => ({
419
+ ...e,
420
+ objects: e.objects.map((t) => ({ ...t }))
421
+ }))
422
+ };
423
+ }
424
+ restoreSnapshot(e) {
425
+ this.restoring = !0;
426
+ for (const t of Array.from(this.objects.values()))
427
+ this.removeRecord(t);
428
+ this.objects.clear(), this.resources.clear(), this.prefabs.clear(), this.selectedIds.clear(), this.objectSerial = e.objectSerial ?? 0, this.resourceSerial = e.resourceSerial ?? 0, this.prefabSerial = e.prefabSerial ?? 0;
429
+ for (const t of e.resources ?? [])
430
+ this.resources.set(t.id, { ...t });
431
+ for (const t of e.prefabs ?? [])
432
+ this.prefabs.set(t.id, {
433
+ ...t,
434
+ objects: t.objects.map((s) => ({ ...s }))
435
+ });
436
+ for (const t of e.objects ?? []) {
437
+ const s = this.createRecordFromSerialized(t);
438
+ t.id && t.id !== s.id && (this.objects.delete(s.id), s.id = t.id, s.entity.object.traverse((n) => {
439
+ n.userData.workbenchId = s.id;
440
+ }), this.objects.set(s.id, s));
441
+ }
442
+ this.restoring = !1, this.refreshUi();
443
+ }
444
+ serializeObject(e, t) {
445
+ const s = e.entity, n = {
446
+ name: s.name,
447
+ kind: e.kind,
448
+ primitiveKind: e.primitiveKind,
449
+ instancedCount: e.instancedCount,
450
+ resourceId: e.resourceId,
451
+ color: e.color,
452
+ layer: e.layer,
453
+ group: e.group,
454
+ locked: e.locked,
455
+ visible: e.visible,
456
+ position: [s.position.x, s.position.y, s.position.z],
457
+ rotation: [s.rotation.x, s.rotation.y, s.rotation.z],
458
+ scale: [s.scale.x, s.scale.y, s.scale.z]
459
+ };
460
+ return t && (n.id = e.id), n;
461
+ }
462
+ refreshUi() {
463
+ this.renderSceneTree(), this.renderResourceList(), this.renderPrefabList(), this.renderPropertyPanel(), this.updateRuntimeUi();
464
+ }
465
+ updateRuntimeUi() {
466
+ const e = this.scene.engine?.stats, t = this.primarySelection(), s = this.logicalObjectCount(), n = this.logicalVisibleObjectCount(), o = Z(), i = this.detectSlowSystem();
467
+ this.ui.objects.textContent = `${s}`, this.ui.visible.textContent = `${n}`, this.ui.drawCalls.textContent = `${e?.drawCalls ?? 0}`, this.ui.triangles.textContent = `${e?.triangles ?? 0}`, this.ui.fps.textContent = `${e?.fps ?? 0}`, this.ui.frame.textContent = `${e?.frameTimeMs ?? 0}ms`, this.ui.geometries.textContent = `${e?.geometries ?? 0}`, this.ui.textures.textContent = `${e?.textures ?? 0}`, this.ui.assets.textContent = `${this.resources.size}`, this.ui.memory.textContent = o, this.ui.quality.textContent = `${Math.round((e?.qualityScale ?? 1) * 100)}%`, this.ui.slowSystem.textContent = i, this.ui.state.textContent = `编辑器运行中 · ${this.lastStressResult}`, this.ui.selected.textContent = t ? t.entity.name : `${this.selectedIds.size} 个对象`;
468
+ }
469
+ renderSceneTree() {
470
+ const e = Array.from(this.objects.values()), t = e.filter((i) => this.selectedIds.has(i.id)), s = e.filter((i) => !this.selectedIds.has(i.id)), n = [...t, ...s].slice(0, D), o = n.map((i) => {
471
+ const c = this.selectedIds.has(i.id) ? " is-selected" : "", a = i.visible ? "" : " is-hidden", d = i.group ? ` · ${i.group}` : "";
472
+ return `<div class="tree-row${c}${a}" data-id="${i.id}">
473
+ <span class="tree-name">${w(i.entity.name)}</span>
474
+ <span class="tree-meta">L${i.layer}${d}</span>
475
+ </div>`;
476
+ });
477
+ e.length > n.length && o.push(`<div class="tree-row">
478
+ <span class="tree-name">已省略 ${e.length - n.length} 个编辑对象</span>
479
+ <span class="tree-meta">场景总数 ${e.length},逻辑对象 ${this.logicalObjectCount()}</span>
480
+ </div>`), this.ui.sceneTree.innerHTML = o.join("") || '<div class="tree-row">空场景</div>';
481
+ for (const i of Array.from(this.ui.sceneTree.querySelectorAll(".tree-row[data-id]")))
482
+ i.addEventListener("click", (c) => {
483
+ const a = i.dataset.id;
484
+ a && (c.shiftKey || c.metaKey ? this.selectedIds.has(a) ? this.selectedIds.delete(a) : this.selectedIds.add(a) : this.selectOnly(a), this.refreshUi());
485
+ });
486
+ }
487
+ renderResourceList() {
488
+ const e = Array.from(this.resources.values()).map(
489
+ (t) => `<div class="resource-row">
490
+ <span class="resource-name">${w(t.name)}</span>
491
+ <span class="resource-kind">${t.kind} · ${x(t.size)}</span>
492
+ </div>`
493
+ );
494
+ this.ui.resourceList.innerHTML = e.join("") || '<div class="resource-row">暂无资源</div>';
495
+ }
496
+ renderPrefabList() {
497
+ const e = Array.from(this.prefabs.values()).map(
498
+ (t) => `<button class="resource-row" data-prefab="${t.id}" type="button">
499
+ <span class="resource-name">${w(t.name)}</span>
500
+ <span class="resource-kind">${t.objects.length} 对象</span>
501
+ </button>`
502
+ );
503
+ this.ui.prefabList.innerHTML = e.join("") || '<div class="resource-row">暂无预制体</div>';
504
+ for (const t of Array.from(this.ui.prefabList.querySelectorAll("[data-prefab]")))
505
+ t.addEventListener("click", () => {
506
+ const s = this.prefabs.get(t.dataset.prefab ?? "");
507
+ if (s) {
508
+ this.selectedIds.clear();
509
+ for (const n of s.objects) {
510
+ const o = this.createRecordFromSerialized({
511
+ ...n,
512
+ name: `${n.name} Instance`,
513
+ position: [n.position[0] + 1, n.position[1], n.position[2] + 1]
514
+ });
515
+ this.selectedIds.add(o.id);
516
+ }
517
+ this.commitHistory(`实例化预制体 ${s.name}`), this.refreshUi();
518
+ }
519
+ });
520
+ }
521
+ renderPropertyPanel() {
522
+ const e = this.primarySelection();
523
+ if (!e) {
524
+ this.ui.propertyPanel.innerHTML = '<p class="tree-meta">请选择一个对象。</p>';
525
+ return;
526
+ }
527
+ const t = e.entity;
528
+ this.ui.propertyPanel.innerHTML = `
529
+ <div class="property-grid">
530
+ ${h("名称", "name", t.name, "text", "full")}
531
+ ${h("X", "px", b(t.position.x))}
532
+ ${h("Y", "py", b(t.position.y))}
533
+ ${h("Z", "pz", b(t.position.z))}
534
+ ${h("旋转X", "rx", b(t.rotation.x))}
535
+ ${h("旋转Y", "ry", b(t.rotation.y))}
536
+ ${h("旋转Z", "rz", b(t.rotation.z))}
537
+ ${h("缩放X", "sx", b(t.scale.x))}
538
+ ${h("缩放Y", "sy", b(t.scale.y))}
539
+ ${h("缩放Z", "sz", b(t.scale.z))}
540
+ ${h("颜色", "color", e.color, "color")}
541
+ ${h("图层", "layer", `${e.layer}`, "number")}
542
+ ${h("分组", "group", e.group, "text")}
543
+ <div class="property-field">
544
+ <label>显示</label>
545
+ <input id="prop-visible" type="checkbox" ${e.visible ? "checked" : ""} />
546
+ </div>
547
+ <div class="property-field">
548
+ <label>锁定</label>
549
+ <input id="prop-locked" type="checkbox" ${e.locked ? "checked" : ""} />
550
+ </div>
551
+ </div>
552
+ <div class="property-actions">
553
+ <button id="applyPropertyButton" type="button">应用属性</button>
554
+ <button id="focusObjectButton" type="button">定位</button>
555
+ </div>
556
+ `, this.ui.propertyPanel.querySelector("#applyPropertyButton")?.addEventListener("click", () => {
557
+ this.applyPropertyChanges(e);
558
+ }), this.ui.propertyPanel.querySelector("#focusObjectButton")?.addEventListener("click", () => {
559
+ this.focusObject(e);
560
+ });
561
+ }
562
+ applyPropertyChanges(e) {
563
+ if (e.locked) {
564
+ this.log("对象已锁定,不能修改");
565
+ return;
566
+ }
567
+ const t = (n) => this.ui.propertyPanel.querySelector(`#prop-${n}`)?.value ?? "", s = (n) => this.ui.propertyPanel.querySelector(`#prop-${n}`)?.checked ?? !1;
568
+ e.entity.name = t("name") || e.entity.name, e.entity.object.name = e.entity.name, e.entity.position.set(p(t("px")), p(t("py")), p(t("pz"))), e.entity.rotation.set(p(t("rx")), p(t("ry")), p(t("rz"))), e.entity.scale.set(p(t("sx"), 1), p(t("sy"), 1), p(t("sz"), 1)), e.color = t("color") || e.color, e.layer = Math.max(0, Math.floor(p(t("layer")))), e.group = t("group"), e.visible = s("visible"), e.locked = s("locked"), e.entity.enabled = e.visible, e.entity.object.visible = e.visible, e.entity.getComponent($)?.setColor(e.color), this.commitHistory("修改对象属性"), this.refreshUi();
569
+ }
570
+ focusObject(e) {
571
+ const t = this.scene.camera, s = e.entity.position;
572
+ t.position.set(s.x + 5, s.y + 4, s.z + 6), t.lookAt(s), this.scene.engine?.renderOnce(), this.log(`已定位到 ${e.entity.name}`);
573
+ }
574
+ bindCanvasPicking() {
575
+ window.setTimeout(() => {
576
+ const e = this.scene.engine?.renderer.domElement;
577
+ e?.addEventListener("pointerup", (t) => {
578
+ if (!this.scene.engine || t.button !== 0)
579
+ return;
580
+ const s = e.getBoundingClientRect();
581
+ this.pointer.x = (t.clientX - s.left) / s.width * 2 - 1, this.pointer.y = -((t.clientY - s.top) / s.height * 2 - 1), this.raycaster.setFromCamera(this.pointer, this.scene.camera);
582
+ const i = this.raycaster.intersectObjects(this.scene.threeScene.children, !0).find((c) => !!c.object.userData.workbenchId)?.object.userData.workbenchId;
583
+ i && this.objects.has(i) && (this.selectOnly(i), this.refreshUi());
584
+ });
585
+ }, 0);
586
+ }
587
+ detectSlowSystem() {
588
+ const e = this.scene.engine?.stats;
589
+ return e ? e.frameTimeMs > 33 ? "帧耗时过高" : e.updatableEntities > 1e3 ? "逐帧对象过多" : e.drawCalls > 900 ? "Draw Call 过高" : e.triangles > 15e5 ? "三角面过高" : e.qualityScale < 0.95 ? "已自动降画质" : "无" : "无";
590
+ }
591
+ logicalObjectCount() {
592
+ let e = 0;
593
+ for (const t of this.objects.values())
594
+ e += t.instancedCount ?? 1;
595
+ return e;
596
+ }
597
+ logicalVisibleObjectCount() {
598
+ let e = 0;
599
+ for (const t of this.objects.values())
600
+ t.visible && (e += t.instancedCount ?? 1);
601
+ return e;
602
+ }
603
+ downloadText(e, t, s) {
604
+ const n = URL.createObjectURL(new Blob([t], { type: s })), o = document.createElement("a");
605
+ o.href = n, o.download = e, document.body.append(o), o.click(), o.remove(), URL.revokeObjectURL(n);
606
+ }
607
+ log(e) {
608
+ const t = document.createElement("div");
609
+ for (t.className = "log-row", t.innerHTML = `<span class="log-time">${(/* @__PURE__ */ new Date()).toLocaleTimeString()}</span><span>${w(e)}</span>`, this.ui.logList.prepend(t); this.ui.logList.children.length > 80; )
610
+ this.ui.logList.lastElementChild?.remove();
611
+ }
612
+ }
613
+ class N extends E {
614
+ constructor(e, t) {
615
+ super(), this.onFrame = e, this.intervalSeconds = t;
616
+ }
617
+ onFrame;
618
+ intervalSeconds;
619
+ elapsed = 0;
620
+ onUpdate(e) {
621
+ this.elapsed += e, !(this.elapsed < this.intervalSeconds) && (this.elapsed = 0, this.onFrame());
622
+ }
623
+ }
624
+ function q() {
625
+ const r = new m("Editor Camera");
626
+ return r.addComponent(
627
+ new P({
628
+ target: new l.Vector3(0, 0.7, 0),
629
+ distance: 10,
630
+ azimuth: Math.PI * 0.24,
631
+ polar: Math.PI * 0.34
632
+ })
633
+ ), r;
634
+ }
635
+ function z() {
636
+ const r = new m("Ambient Light");
637
+ return r.addComponent(new R({ kind: "ambient", color: "#d7f8ee", intensity: 0.78 })), r;
638
+ }
639
+ function W() {
640
+ const r = new m("Directional Light");
641
+ return r.position.set(4, 7, 5), r.addComponent(new R({ kind: "directional", color: "#fff4d2", intensity: 2.75 })), r;
642
+ }
643
+ function K() {
644
+ const r = new m("Workbench Grid");
645
+ return r.position.set(0, -0.05, 0), r.rotation.x = -Math.PI / 2, r.addComponent(
646
+ new $({
647
+ kind: "plane",
648
+ width: 32,
649
+ depth: 32,
650
+ color: "#18211d",
651
+ roughness: 0.82,
652
+ receiveShadow: !0,
653
+ castShadow: !1
654
+ })
655
+ ), r.addComponent(new y(X())), r;
656
+ }
657
+ function V() {
658
+ const r = new m("World Axis");
659
+ return r.addComponent(new y(new l.AxesHelper(3))), r;
660
+ }
661
+ function X() {
662
+ const r = new l.GridHelper(32, 32, "#1ec7a6", "#31403a"), e = r.material;
663
+ return Array.isArray(e) || (e.transparent = !0, e.opacity = 0.42), r.position.y = 0.02, r;
664
+ }
665
+ function J(r) {
666
+ const e = r.split(".").pop()?.toLowerCase() ?? "";
667
+ return ["glb", "gltf"].includes(e) ? "model" : ["png", "jpg", "jpeg", "webp", "ktx2"].includes(e) ? "texture" : ["mp3", "wav", "ogg", "m4a"].includes(e) ? "audio" : ["mat", "json"].includes(e) ? "material" : "unknown";
668
+ }
669
+ function h(r, e, t, s = "number", n = "") {
670
+ return `<div class="property-field ${n}">
671
+ <label for="prop-${e}">${r}</label>
672
+ <input id="prop-${e}" type="${s}" value="${w(String(t))}" />
673
+ </div>`;
674
+ }
675
+ function b(r) {
676
+ return `${Math.round(r * 100) / 100}`;
677
+ }
678
+ function p(r, e = 0) {
679
+ const t = Number(r);
680
+ return Number.isFinite(t) ? t : e;
681
+ }
682
+ function C(r) {
683
+ return r >= B ? Math.min(r, _) : Math.min(r, F);
684
+ }
685
+ function Y(r) {
686
+ return r >= 1e8 && r % 1e8 === 0 ? `${r / 1e8}亿` : `${r}`;
687
+ }
688
+ function w(r) {
689
+ return r.replace(/[&<>"']/g, (e) => {
690
+ switch (e) {
691
+ case "&":
692
+ return "&amp;";
693
+ case "<":
694
+ return "&lt;";
695
+ case ">":
696
+ return "&gt;";
697
+ case '"':
698
+ return "&quot;";
699
+ default:
700
+ return "&#39;";
701
+ }
702
+ });
703
+ }
704
+ function x(r) {
705
+ return r < 1024 ? `${r}B` : r < 1024 * 1024 ? `${Math.round(r / 1024)}KB` : `${Math.round(r / (1024 * 1024) * 10) / 10}MB`;
706
+ }
707
+ function Z() {
708
+ const e = performance.memory?.usedJSHeapSize;
709
+ return typeof e == "number" ? x(e) : "未知";
710
+ }
711
+ export {
712
+ te as createEngineWorkbench
713
+ };