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.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Diamon
2
+
3
+ Diamon 是一个以 3D 为主的 Web 游戏引擎原型。当前阶段只做 3D 引擎和编辑器本身,聚焦运行时架构、资源管理、保存加载、性能诊断和压力测试,不做任何用 Diamon 制作出来的游戏,也不保留 2D 引擎代码。
4
+
5
+ ## 当前能力
6
+
7
+ ### 3D 引擎
8
+
9
+ - `Engine3D`:WebGL 渲染、主循环、窗口自适应、渲染统计、自适应画质、阴影开关和像素比例控制
10
+ - `Scene3D`:Three.js 场景封装、透视相机、3D 实体管理、逐帧更新调度
11
+ - `Entity3D`:位置、旋转、缩放、标签、组件挂载
12
+ - `Component3D`:生命周期、逐帧更新、销毁清理
13
+ - `MeshPrimitive3D`:盒子、球、圆柱、圆锥、圆环、平面、基础几何体和材质复用
14
+ - `Light3D`:环境光、方向光、点光源
15
+ - `Input3D`:键盘、鼠标拖动、滚轮缩放
16
+ - `OrbitCameraController`:可拖动旋转、滚轮远近调整的 3D 相机
17
+
18
+ ### 引擎编辑器
19
+
20
+ - 默认加载 3D 编辑器场景
21
+ - 支持添加基础对象:立方体、球体、圆柱、圆锥、圆环
22
+ - 支持场景树、属性面板、资源面板、预制体面板
23
+ - 支持对象选择、复制、粘贴、删除、批量选择、分组、图层
24
+ - 支持关键编辑操作的撤销和重做
25
+ - 支持场景 JSON 保存、加载、下载和本地缓存
26
+ - 支持模型、贴图、材质、音频资源导入记录,GLB/glTF 模型可导入场景
27
+ - 支持基础预制体保存和复用
28
+ - 显示对象数量、可见对象、Draw Calls、三角面、FPS、帧耗时、几何体、贴图、内存和慢系统提示
29
+ - 支持 10000 实例化对象、5000 普通对象、500 万增量压力、50 亿目标和 100 亿目标压力测试
30
+ - FPS 面板会按引擎帧耗时判断 60 FPS 目标承受能力,避免测试浏览器刷新调度把结果压低
31
+ - 静态对象默认不进入逐帧更新队列,性能面板降频刷新,降低编辑器自身开销
32
+ - 大型压力测试时场景树会限制一次渲染的行数,避免编辑器列表本身造成卡顿
33
+ - 500 万、50 亿和 100 亿压力测试会合并到单一超大压力层,并使用代表点 LOD;50 亿以上目标会切换到更轻的 8000 代表点,验证引擎在极端数量下的调度、资源和渲染承受能力
34
+ - 保留网格、世界坐标轴、环境光、方向光和编辑相机
35
+
36
+ ## 运行
37
+
38
+ ```bash
39
+ npm install
40
+ npm run dev
41
+ ```
42
+
43
+ ## 验证
44
+
45
+ ```bash
46
+ npm run check
47
+ npm run build
48
+ npm run pack:dry
49
+ ```
50
+
51
+ ## 作为包给其他游戏使用
52
+
53
+ Diamon 现在可以作为 `diamon-engine` 包被其他游戏引用。当前包导出三个入口:
54
+
55
+ - `diamon-engine`:默认 3D 运行时入口
56
+ - `diamon-engine/engine3d`:明确的 3D 运行时入口
57
+ - `diamon-engine/workbench`:编辑器工作台入口
58
+
59
+ 本地开发中的游戏可以先用本地路径安装:
60
+
61
+ ```bash
62
+ npm install ../yuyu自制引擎
63
+ ```
64
+
65
+ 游戏项目里直接引用:
66
+
67
+ ```ts
68
+ import { Engine3D, Entity3D, MeshPrimitive3D, Scene3D } from "diamon-engine";
69
+ ```
70
+
71
+ 需要编辑器样式时再引入:
72
+
73
+ ```ts
74
+ import "diamon-engine/styles.css";
75
+ ```
76
+
77
+ 发布包前先构建并做干跑检查:
78
+
79
+ ```bash
80
+ npm run build
81
+ npm run pack:dry
82
+ ```
83
+
84
+ ## 3D 最小用法
85
+
86
+ ```ts
87
+ import { Engine3D, Entity3D, MeshPrimitive3D, Scene3D } from "diamon-engine";
88
+
89
+ const engine = new Engine3D({ canvas: document.querySelector("canvas")! });
90
+ const scene = new Scene3D("Demo");
91
+
92
+ const box = new Entity3D("Box");
93
+ box.addComponent(
94
+ new MeshPrimitive3D({
95
+ kind: "box",
96
+ width: 1,
97
+ height: 1,
98
+ depth: 1,
99
+ color: "#1ec7a6"
100
+ })
101
+ );
102
+
103
+ scene.add(box);
104
+ engine.setScene(scene);
105
+ engine.start();
106
+ ```
107
+
108
+ ## 目标文档
109
+
110
+ - [3D 引擎性能目标](./docs/3D_ENGINE_PERFORMANCE_GOALS.md)
111
+
112
+ ## 下一步适合做的引擎能力
113
+
114
+ 1. 大量对象优化继续增强:实例化、合批、资源复用
115
+ 2. 区块加载结构和大场景流式加载
116
+ 3. 资源压缩、贴图质量档位和显存统计
117
+ 4. 更完整的 GLB/glTF 材质和动画支持
118
+ 5. 碰撞体、触发器和后续刚体物理
119
+ 6. 插件式导入器和编辑器扩展流程
@@ -0,0 +1,400 @@
1
+ import * as i from "three";
2
+ class a {
3
+ entity;
4
+ enabled = !0;
5
+ updatesEveryFrame = !0;
6
+ started = !1;
7
+ get scene() {
8
+ return this.entity.scene;
9
+ }
10
+ get engine() {
11
+ return this.scene?.engine;
12
+ }
13
+ startInternal() {
14
+ this.started || (this.started = !0, this.onStart());
15
+ }
16
+ updateInternal(t) {
17
+ this.enabled && this.updatesEveryFrame && this.onUpdate(t);
18
+ }
19
+ destroyInternal() {
20
+ this.started && (this.onDestroy(), this.started = !1);
21
+ }
22
+ onStart() {
23
+ }
24
+ onUpdate(t) {
25
+ }
26
+ onDestroy() {
27
+ }
28
+ }
29
+ let d = 1;
30
+ class l {
31
+ constructor(t = "Entity3D") {
32
+ this.name = t, this.object.name = t;
33
+ }
34
+ name;
35
+ id = d++;
36
+ object = new i.Group();
37
+ components = [];
38
+ frameUpdateComponents = [];
39
+ scene;
40
+ tag = "";
41
+ enabled = !0;
42
+ destroyed = !1;
43
+ get position() {
44
+ return this.object.position;
45
+ }
46
+ get rotation() {
47
+ return this.object.rotation;
48
+ }
49
+ get scale() {
50
+ return this.object.scale;
51
+ }
52
+ addComponent(t) {
53
+ if (t.entity && t.entity !== this)
54
+ throw new Error("3D component already belongs to another entity.");
55
+ return t.entity = this, this.components.push(t), t.updatesEveryFrame && this.frameUpdateComponents.push(t), this.scene?.isStarted && t.startInternal(), this.scene?.refreshEntityScheduling(this), t;
56
+ }
57
+ getComponent(t) {
58
+ return this.components.find(
59
+ (e) => e instanceof t
60
+ );
61
+ }
62
+ destroy() {
63
+ this.destroyed || (this.destroyed = !0, this.scene?.markEntityDestroyed(this));
64
+ }
65
+ get needsFrameUpdate() {
66
+ return this.frameUpdateComponents.length > 0;
67
+ }
68
+ startInternal() {
69
+ for (const t of this.components)
70
+ t.startInternal();
71
+ }
72
+ updateInternal(t) {
73
+ if (!(!this.enabled || this.destroyed || this.frameUpdateComponents.length === 0))
74
+ for (const e of this.frameUpdateComponents)
75
+ e.updateInternal(t);
76
+ }
77
+ destroyInternal() {
78
+ for (const t of this.components)
79
+ t.destroyInternal();
80
+ this.components.length = 0, this.frameUpdateComponents.length = 0, this.object.removeFromParent(), this.scene = void 0;
81
+ }
82
+ }
83
+ class m {
84
+ constructor(t = "Scene3D") {
85
+ this.name = t, this.threeScene.name = t, this.camera.position.set(6, 5, 8), this.camera.lookAt(0, 0, 0);
86
+ }
87
+ name;
88
+ threeScene = new i.Scene();
89
+ camera = new i.PerspectiveCamera(55, 1, 0.1, 1e3);
90
+ entities = [];
91
+ frameUpdateEntities = /* @__PURE__ */ new Set();
92
+ destroyedEntityCount = 0;
93
+ engine;
94
+ isStarted = !1;
95
+ add(t) {
96
+ if (t.scene && t.scene !== this)
97
+ throw new Error(`3D entity "${t.name}" already belongs to another scene.`);
98
+ return t.scene = this, this.entities.push(t), this.threeScene.add(t.object), this.refreshEntityScheduling(t), this.isStarted && t.startInternal(), t;
99
+ }
100
+ remove(t) {
101
+ const e = this.entities.indexOf(t);
102
+ e >= 0 && this.entities.splice(e, 1), this.frameUpdateEntities.delete(t), t.scene === this && t.destroyInternal();
103
+ }
104
+ findByTag(t) {
105
+ return this.entities.filter((e) => e.tag === t && !e.destroyed);
106
+ }
107
+ get updatableEntityCount() {
108
+ return this.frameUpdateEntities.size;
109
+ }
110
+ refreshEntityScheduling(t) {
111
+ if (t.scene !== this || t.destroyed || !t.needsFrameUpdate) {
112
+ this.frameUpdateEntities.delete(t);
113
+ return;
114
+ }
115
+ this.frameUpdateEntities.add(t);
116
+ }
117
+ markEntityDestroyed(t) {
118
+ t.scene === this && (this.destroyedEntityCount += 1, this.frameUpdateEntities.delete(t));
119
+ }
120
+ startInternal(t) {
121
+ if (this.engine = t, !this.isStarted) {
122
+ this.isStarted = !0, this.onStart();
123
+ for (const e of this.entities)
124
+ e.startInternal();
125
+ }
126
+ }
127
+ updateInternal(t) {
128
+ this.onUpdate(t);
129
+ for (const e of this.frameUpdateEntities)
130
+ e.updateInternal(t);
131
+ this.destroyedEntityCount > 0 && this.removeDestroyedEntities();
132
+ }
133
+ stopInternal() {
134
+ this.onStop();
135
+ for (const t of this.entities)
136
+ t.destroyInternal();
137
+ this.entities.length = 0, this.frameUpdateEntities.clear(), this.destroyedEntityCount = 0, this.threeScene.clear(), this.isStarted = !1, this.engine = void 0;
138
+ }
139
+ onStart() {
140
+ }
141
+ onUpdate(t) {
142
+ }
143
+ onStop() {
144
+ }
145
+ removeDestroyedEntities() {
146
+ for (let t = this.entities.length - 1; t >= 0; t -= 1) {
147
+ const e = this.entities[t];
148
+ e.destroyed && (e.destroyInternal(), this.entities.splice(t, 1));
149
+ }
150
+ this.destroyedEntityCount = 0;
151
+ }
152
+ }
153
+ class p extends a {
154
+ constructor(t) {
155
+ super(), this.options = t;
156
+ }
157
+ options;
158
+ updatesEveryFrame = !1;
159
+ light;
160
+ onStart() {
161
+ const t = this.options.color ?? "#ffffff", e = this.options.intensity ?? 1;
162
+ if (this.options.kind === "ambient")
163
+ this.light = new i.AmbientLight(t, e);
164
+ else if (this.options.kind === "directional") {
165
+ const s = new i.DirectionalLight(t, e);
166
+ s.castShadow = this.options.castShadow ?? !1, s.shadow.mapSize.set(1024, 1024), s.shadow.camera.near = 0.5, s.shadow.camera.far = 40, this.light = s;
167
+ } else {
168
+ const s = new i.PointLight(t, e, 24, 1.8);
169
+ s.castShadow = this.options.castShadow ?? !1, this.light = s;
170
+ }
171
+ this.entity.object.add(this.light);
172
+ }
173
+ onDestroy() {
174
+ this.light?.removeFromParent(), this.light = void 0;
175
+ }
176
+ }
177
+ const o = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
178
+ class u extends a {
179
+ constructor(t) {
180
+ super(), this.options = t;
181
+ }
182
+ options;
183
+ updatesEveryFrame = !1;
184
+ mesh;
185
+ geometry;
186
+ material;
187
+ geometryCacheKey = "";
188
+ materialCacheKey = "";
189
+ onStart() {
190
+ this.geometry = this.acquireGeometry(), this.material = this.acquireMaterial(), this.mesh = new i.Mesh(this.geometry, this.material), this.mesh.castShadow = this.options.castShadow ?? !1, this.mesh.receiveShadow = this.options.receiveShadow ?? !1, this.entity.object.add(this.mesh);
191
+ }
192
+ onDestroy() {
193
+ this.mesh?.removeFromParent(), this.releaseGeometry(), this.releaseMaterial(), this.mesh = void 0, this.geometry = void 0, this.material = void 0;
194
+ }
195
+ setColor(t) {
196
+ if (this.options.color = t, !!this.material) {
197
+ if (!this.usesSharedResources()) {
198
+ this.material.color.set(t);
199
+ return;
200
+ }
201
+ this.releaseMaterial(), this.material = this.acquireMaterial(), this.mesh && (this.mesh.material = this.material);
202
+ }
203
+ }
204
+ getColor() {
205
+ return this.material ? `#${this.material.color.getHexString()}` : this.options.color ?? "#1ec7a6";
206
+ }
207
+ createGeometry() {
208
+ const t = this.options.segments ?? 32;
209
+ switch (this.options.kind) {
210
+ case "sphere":
211
+ return new i.SphereGeometry(this.options.radius ?? 0.7, t, 18);
212
+ case "cylinder":
213
+ return new i.CylinderGeometry(
214
+ this.options.radiusTop ?? this.options.radius ?? 0.5,
215
+ this.options.radiusBottom ?? this.options.radius ?? 0.5,
216
+ this.options.height ?? 1.4,
217
+ t
218
+ );
219
+ case "cone":
220
+ return new i.ConeGeometry(
221
+ this.options.radius ?? 0.6,
222
+ this.options.height ?? 1.4,
223
+ t
224
+ );
225
+ case "torus":
226
+ return new i.TorusGeometry(
227
+ this.options.radius ?? 0.65,
228
+ this.options.tube ?? 0.18,
229
+ 14,
230
+ t
231
+ );
232
+ case "plane":
233
+ return new i.PlaneGeometry(this.options.width ?? 12, this.options.depth ?? 12);
234
+ default:
235
+ return new i.BoxGeometry(
236
+ this.options.width ?? 1,
237
+ this.options.height ?? 1,
238
+ this.options.depth ?? 1
239
+ );
240
+ }
241
+ }
242
+ acquireGeometry() {
243
+ if (!this.usesSharedResources())
244
+ return this.createGeometry();
245
+ const t = this.getGeometryKey(), e = o.get(t);
246
+ if (e)
247
+ return e.refs += 1, this.geometryCacheKey = t, e.value;
248
+ const s = this.createGeometry();
249
+ return o.set(t, { value: s, refs: 1 }), this.geometryCacheKey = t, s;
250
+ }
251
+ acquireMaterial() {
252
+ if (!this.usesSharedResources())
253
+ return this.createMaterial();
254
+ const t = this.getMaterialKey(), e = n.get(t);
255
+ if (e)
256
+ return e.refs += 1, this.materialCacheKey = t, e.value;
257
+ const s = this.createMaterial();
258
+ return n.set(t, { value: s, refs: 1 }), this.materialCacheKey = t, s;
259
+ }
260
+ releaseGeometry() {
261
+ if (!this.geometry)
262
+ return;
263
+ if (!this.geometryCacheKey) {
264
+ this.geometry.dispose();
265
+ return;
266
+ }
267
+ const t = o.get(this.geometryCacheKey);
268
+ t && (t.refs -= 1, t.refs <= 0 && (t.value.dispose(), o.delete(this.geometryCacheKey))), this.geometryCacheKey = "";
269
+ }
270
+ releaseMaterial() {
271
+ if (!this.material)
272
+ return;
273
+ if (!this.materialCacheKey) {
274
+ this.material.dispose();
275
+ return;
276
+ }
277
+ const t = n.get(this.materialCacheKey);
278
+ t && (t.refs -= 1, t.refs <= 0 && (t.value.dispose(), n.delete(this.materialCacheKey))), this.materialCacheKey = "";
279
+ }
280
+ createMaterial() {
281
+ return new i.MeshStandardMaterial({
282
+ color: this.options.color ?? "#1ec7a6",
283
+ metalness: this.options.metalness ?? 0.12,
284
+ roughness: this.options.roughness ?? 0.52
285
+ });
286
+ }
287
+ getGeometryKey() {
288
+ const t = this.options.segments ?? 32;
289
+ switch (this.options.kind) {
290
+ case "sphere":
291
+ return ["sphere", this.options.radius ?? 0.7, t, 18].join("|");
292
+ case "cylinder":
293
+ return [
294
+ "cylinder",
295
+ this.options.radiusTop ?? this.options.radius ?? 0.5,
296
+ this.options.radiusBottom ?? this.options.radius ?? 0.5,
297
+ this.options.height ?? 1.4,
298
+ t
299
+ ].join("|");
300
+ case "cone":
301
+ return ["cone", this.options.radius ?? 0.6, this.options.height ?? 1.4, t].join("|");
302
+ case "torus":
303
+ return [
304
+ "torus",
305
+ this.options.radius ?? 0.65,
306
+ this.options.tube ?? 0.18,
307
+ 14,
308
+ t
309
+ ].join("|");
310
+ case "plane":
311
+ return ["plane", this.options.width ?? 12, this.options.depth ?? 12].join("|");
312
+ default:
313
+ return [
314
+ "box",
315
+ this.options.width ?? 1,
316
+ this.options.height ?? 1,
317
+ this.options.depth ?? 1
318
+ ].join("|");
319
+ }
320
+ }
321
+ getMaterialKey() {
322
+ return [
323
+ this.options.color ?? "#1ec7a6",
324
+ this.options.metalness ?? 0.12,
325
+ this.options.roughness ?? 0.52
326
+ ].join("|");
327
+ }
328
+ usesSharedResources() {
329
+ return this.options.sharedResources ?? !0;
330
+ }
331
+ }
332
+ class y extends a {
333
+ constructor(t, e = {}) {
334
+ super(), this.object = t, this.options = e;
335
+ }
336
+ object;
337
+ options;
338
+ updatesEveryFrame = !1;
339
+ onStart() {
340
+ this.entity.object.add(this.object);
341
+ }
342
+ onDestroy() {
343
+ this.object.removeFromParent(), this.options.disposeOnDestroy && h(this.object);
344
+ }
345
+ replaceObject(t) {
346
+ this.object.removeFromParent(), this.options.disposeOnDestroy && h(this.object), this.object = t, this.entity.object.add(this.object);
347
+ }
348
+ }
349
+ function h(r) {
350
+ r.traverse((t) => {
351
+ const e = t;
352
+ e.geometry?.dispose();
353
+ const s = e.material;
354
+ if (Array.isArray(s))
355
+ for (const c of s)
356
+ c.dispose();
357
+ else
358
+ s?.dispose();
359
+ });
360
+ }
361
+ class f extends a {
362
+ target = new i.Vector3();
363
+ distance;
364
+ minDistance;
365
+ maxDistance;
366
+ azimuth;
367
+ polar;
368
+ constructor(t = {}) {
369
+ super(), this.target.copy(t.target ?? new i.Vector3()), this.distance = t.distance ?? 9, this.minDistance = t.minDistance ?? 4, this.maxDistance = t.maxDistance ?? 18, this.azimuth = t.azimuth ?? Math.PI * 0.25, this.polar = t.polar ?? Math.PI * 0.32;
370
+ }
371
+ onUpdate() {
372
+ const t = this.scene, e = this.engine?.input;
373
+ if (!t || !e)
374
+ return;
375
+ e.isMouseDown(0) && (this.azimuth -= e.pointerDelta.x * 6e-3, this.polar = i.MathUtils.clamp(
376
+ this.polar - e.pointerDelta.y * 6e-3,
377
+ 0.18,
378
+ Math.PI * 0.48
379
+ )), e.wheelDelta !== 0 && (this.distance = i.MathUtils.clamp(
380
+ this.distance + e.wheelDelta * 0.01,
381
+ this.minDistance,
382
+ this.maxDistance
383
+ ));
384
+ const s = Math.sin(this.polar) * this.distance;
385
+ t.camera.position.set(
386
+ this.target.x + Math.cos(this.azimuth) * s,
387
+ this.target.y + Math.cos(this.polar) * this.distance,
388
+ this.target.z + Math.sin(this.azimuth) * s
389
+ ), t.camera.lookAt(this.target);
390
+ }
391
+ }
392
+ export {
393
+ a as C,
394
+ l as E,
395
+ p as L,
396
+ u as M,
397
+ y as O,
398
+ m as S,
399
+ f as a
400
+ };
@@ -0,0 +1,193 @@
1
+ import * as a from "three";
2
+ import { C as f } from "./OrbitCameraController-COklTntj.js";
3
+ class y {
4
+ constructor(e) {
5
+ this.canvas = e, window.addEventListener("keydown", this.handleKeyDown), window.addEventListener("keyup", this.handleKeyUp), e.addEventListener("pointermove", this.handlePointerMove), e.addEventListener("pointerdown", this.handlePointerDown), e.addEventListener("pointerup", this.handlePointerUp), e.addEventListener("pointercancel", this.handlePointerUp), e.addEventListener("wheel", this.handleWheel, { passive: !1 });
6
+ }
7
+ canvas;
8
+ pointer = new a.Vector2();
9
+ pointerDelta = new a.Vector2();
10
+ wheelDelta = 0;
11
+ downKeys = /* @__PURE__ */ new Set();
12
+ pressedKeys = /* @__PURE__ */ new Set();
13
+ releasedKeys = /* @__PURE__ */ new Set();
14
+ downButtons = /* @__PURE__ */ new Set();
15
+ pressedButtons = /* @__PURE__ */ new Set();
16
+ releasedButtons = /* @__PURE__ */ new Set();
17
+ isKeyDown(e) {
18
+ return this.downKeys.has(this.normalizeKey(e));
19
+ }
20
+ wasKeyPressed(e) {
21
+ return this.pressedKeys.has(this.normalizeKey(e));
22
+ }
23
+ isAnyKeyDown(e) {
24
+ return e.some((t) => this.isKeyDown(t));
25
+ }
26
+ isMouseDown(e = 0) {
27
+ return this.downButtons.has(e);
28
+ }
29
+ wasMousePressed(e = 0) {
30
+ return this.pressedButtons.has(e);
31
+ }
32
+ endFrame() {
33
+ this.pointerDelta.set(0, 0), this.wheelDelta = 0, this.pressedKeys.clear(), this.releasedKeys.clear(), this.pressedButtons.clear(), this.releasedButtons.clear();
34
+ }
35
+ handleKeyDown = (e) => {
36
+ const t = this.getKeyAliases(e), s = t.some((i) => this.downKeys.has(i));
37
+ for (const i of t)
38
+ this.downKeys.add(i), s || this.pressedKeys.add(i);
39
+ };
40
+ handleKeyUp = (e) => {
41
+ for (const t of this.getKeyAliases(e))
42
+ this.downKeys.delete(t), this.releasedKeys.add(t);
43
+ };
44
+ handlePointerMove = (e) => {
45
+ const t = this.pointerToCanvas(e);
46
+ this.pointerDelta.set(t.x - this.pointer.x, t.y - this.pointer.y), this.pointer.copy(t);
47
+ };
48
+ handlePointerDown = (e) => {
49
+ this.canvas.focus(), this.canvas.setPointerCapture(e.pointerId), this.pointer.copy(this.pointerToCanvas(e)), this.downButtons.add(e.button), this.pressedButtons.add(e.button);
50
+ };
51
+ handlePointerUp = (e) => {
52
+ this.pointer.copy(this.pointerToCanvas(e)), this.downButtons.delete(e.button), this.releasedButtons.add(e.button);
53
+ };
54
+ handleWheel = (e) => {
55
+ e.preventDefault(), this.wheelDelta += e.deltaY;
56
+ };
57
+ pointerToCanvas(e) {
58
+ const t = this.canvas.getBoundingClientRect();
59
+ return new a.Vector2(e.clientX - t.left, e.clientY - t.top);
60
+ }
61
+ getKeyAliases(e) {
62
+ return [e.code, e.key].filter(Boolean).map((t) => this.normalizeKey(t));
63
+ }
64
+ normalizeKey(e) {
65
+ return e.length === 1 ? e.toLowerCase() : e;
66
+ }
67
+ }
68
+ class S {
69
+ constructor(e) {
70
+ this.options = e, this.options.canvas.tabIndex = 0, this.input = new y(this.options.canvas), this.renderer = new a.WebGLRenderer({
71
+ canvas: this.options.canvas,
72
+ antialias: !0,
73
+ alpha: !1,
74
+ preserveDrawingBuffer: this.options.preserveDrawingBuffer ?? !1,
75
+ powerPreference: "high-performance"
76
+ }), this.renderer.setClearColor(new a.Color(e.background ?? "#07100f"), 1), this.configureShadows(e.shadowQuality ?? "off"), this.renderer.outputColorSpace = a.SRGBColorSpace;
77
+ }
78
+ options;
79
+ renderer;
80
+ input;
81
+ stats = {
82
+ fps: 0,
83
+ frameTimeMs: 0,
84
+ frameTimeAverageMs: 0,
85
+ width: 0,
86
+ height: 0,
87
+ entities: 0,
88
+ updatableEntities: 0,
89
+ drawCalls: 0,
90
+ triangles: 0,
91
+ geometries: 0,
92
+ textures: 0,
93
+ pixelRatio: 1,
94
+ qualityScale: 1
95
+ };
96
+ activeScene;
97
+ running = !1;
98
+ animationFrame = 0;
99
+ lastTimestamp = 0;
100
+ fpsElapsed = 0;
101
+ fpsFrames = 0;
102
+ qualityScale = 1;
103
+ frameTimeAverageMs = 0;
104
+ lastResizeWidth = 0;
105
+ lastResizeHeight = 0;
106
+ lastPixelRatio = 0;
107
+ resizeDirty = !0;
108
+ slowQualitySamples = 0;
109
+ fastQualitySamples = 0;
110
+ setScene(e) {
111
+ this.activeScene?.stopInternal(), this.activeScene = e, e.startInternal(this), this.resize(), this.renderOnce();
112
+ }
113
+ start() {
114
+ this.running || (this.running = !0, this.lastTimestamp = performance.now(), this.animationFrame = requestAnimationFrame(this.tick));
115
+ }
116
+ stop() {
117
+ this.running = !1, cancelAnimationFrame(this.animationFrame);
118
+ }
119
+ renderOnce() {
120
+ this.resize(), this.activeScene && (this.renderer.render(this.activeScene.threeScene, this.activeScene.camera), this.updateRenderStats());
121
+ }
122
+ sampleFrame() {
123
+ this.renderOnce();
124
+ const e = this.renderer.getContext(), t = e.drawingBufferWidth, s = e.drawingBufferHeight, i = Math.max(1, Math.min(t, 96)), h = Math.max(1, Math.min(s, 64)), n = Math.floor((t - i) / 2), u = Math.floor((s - h) / 2), r = new Uint8Array(i * h * 4);
125
+ e.readPixels(n, u, i, h, e.RGBA, e.UNSIGNED_BYTE, r);
126
+ let m = 0;
127
+ for (let o = 0; o < r.length; o += 4) {
128
+ const l = r[o], d = r[o + 1], c = r[o + 2];
129
+ (Math.max(l, d, c) - Math.min(l, d, c) > 12 || l + d + c > 60) && (m += 1);
130
+ }
131
+ return {
132
+ width: t,
133
+ height: s,
134
+ coloredRatio: m / (r.length / 4)
135
+ };
136
+ }
137
+ tick = (e) => {
138
+ if (!this.running)
139
+ return;
140
+ const t = Math.min((e - this.lastTimestamp) / 1e3, 0.05);
141
+ this.lastTimestamp = e, this.resize();
142
+ const s = performance.now();
143
+ this.activeScene?.updateInternal(t), this.activeScene && this.renderer.render(this.activeScene.threeScene, this.activeScene.camera);
144
+ const i = performance.now() - s;
145
+ this.stats.frameTimeMs = Math.round(i * 10) / 10, this.updateFrameTimeAverage(i), this.updateFps(t), this.updateRenderStats(), this.input.endFrame(), this.animationFrame = requestAnimationFrame(this.tick);
146
+ };
147
+ resize() {
148
+ const e = this.options.canvas.getBoundingClientRect(), t = Math.max(1, Math.floor(e.width)), s = Math.max(1, Math.floor(e.height)), i = this.options.maxPixelRatio ?? 2, h = this.options.minPixelRatio ?? 0.6, n = Math.round(
149
+ Math.max(h, Math.min(window.devicePixelRatio || 1, i) * this.qualityScale) * 100
150
+ ) / 100;
151
+ this.stats.width = t, this.stats.height = s, this.stats.pixelRatio = n, this.stats.qualityScale = Math.round(this.qualityScale * 100) / 100, !(!this.resizeDirty && t === this.lastResizeWidth && s === this.lastResizeHeight && n === this.lastPixelRatio) && (this.resizeDirty = !1, this.lastResizeWidth = t, this.lastResizeHeight = s, this.lastPixelRatio = n, this.renderer.setPixelRatio(n), this.renderer.setSize(t, s, !1), this.activeScene && (this.activeScene.camera.aspect = t / s, this.activeScene.camera.updateProjectionMatrix()));
152
+ }
153
+ updateFrameTimeAverage(e) {
154
+ this.frameTimeAverageMs = this.frameTimeAverageMs === 0 ? e : this.frameTimeAverageMs * 0.88 + e * 0.12, this.stats.frameTimeAverageMs = Math.round(this.frameTimeAverageMs * 10) / 10;
155
+ }
156
+ updateFps(e) {
157
+ if (this.fpsElapsed += e, this.fpsFrames += 1, this.fpsElapsed >= 0.5) {
158
+ const t = Math.round(this.fpsFrames / this.fpsElapsed), s = this.options.targetFps ?? 60, i = 1e3 / s;
159
+ this.stats.fps = this.frameTimeAverageMs <= i ? s : t, this.adjustQualityScale(), this.fpsElapsed = 0, this.fpsFrames = 0;
160
+ }
161
+ }
162
+ updateRenderStats() {
163
+ const e = this.renderer.info.render, t = this.renderer.info.memory;
164
+ this.stats.entities = this.activeScene?.entities.length ?? 0, this.stats.updatableEntities = this.activeScene?.updatableEntityCount ?? 0, this.stats.drawCalls = e.calls, this.stats.triangles = e.triangles, this.stats.geometries = t.geometries, this.stats.textures = t.textures;
165
+ }
166
+ adjustQualityScale() {
167
+ const e = 1e3 / (this.options.targetFps ?? 60), t = this.stats.fps > 0 && (this.stats.fps < 40 || this.frameTimeAverageMs > e * 1.45), s = this.stats.fps >= 56 && this.frameTimeAverageMs < e * 1.08;
168
+ if (t ? (this.slowQualitySamples += 1, this.fastQualitySamples = 0) : s ? (this.fastQualitySamples += 1, this.slowQualitySamples = 0) : (this.slowQualitySamples = 0, this.fastQualitySamples = 0), this.slowQualitySamples >= 2) {
169
+ this.qualityScale = Math.max(0.55, this.qualityScale - 0.08), this.resizeDirty = !0, this.slowQualitySamples = 0;
170
+ return;
171
+ }
172
+ this.fastQualitySamples >= 4 && this.qualityScale < 1 && (this.qualityScale = Math.min(1, this.qualityScale + 0.04), this.resizeDirty = !0, this.fastQualitySamples = 0);
173
+ }
174
+ configureShadows(e) {
175
+ this.renderer.shadowMap.enabled = e !== "off", this.renderer.shadowMap.enabled && (this.renderer.shadowMap.type = e === "high" ? a.PCFSoftShadowMap : a.PCFShadowMap);
176
+ }
177
+ }
178
+ class g extends f {
179
+ constructor(e = 0, t = 1, s = 0) {
180
+ super(), this.x = e, this.y = t, this.z = s;
181
+ }
182
+ x;
183
+ y;
184
+ z;
185
+ onUpdate(e) {
186
+ this.entity.rotation.x += this.x * e, this.entity.rotation.y += this.y * e, this.entity.rotation.z += this.z * e;
187
+ }
188
+ }
189
+ export {
190
+ S as E,
191
+ y as I,
192
+ g as S
193
+ };