@scenoco-three/editor 0.5.4 → 0.6.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 +31 -93
- package/dist/cli/index.js +49 -115
- package/dist-app/assets/EffectComposer-lekj9UUq.js +1 -0
- package/dist-app/assets/GLTFLoader-C2lUhzim.js +1 -0
- package/dist-app/assets/OutputPass-DZtd342R.js +72 -0
- package/dist-app/assets/Pass-BP3b3hlf.js +1 -0
- package/dist-app/assets/RenderPass-BjVchlYg.js +1 -0
- package/dist-app/assets/ShaderPass-DMXLcV9X.js +24 -0
- package/dist-app/assets/SkeletonUtils-Cxy_owx9.js +1 -0
- package/dist-app/assets/index-AxOpglZ-.css +1 -0
- package/dist-app/assets/index-D-Iq7tix.js +4135 -0
- package/dist-app/assets/inter-CaiW00F8.js +1 -0
- package/dist-app/assets/registry-BOM35tSr.js +1433 -0
- package/dist-app/assets/worker-_Lh8Vt-i.js +1270 -0
- package/dist-app/examples/breakout/assets/geometry/ball.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/geometry/brick.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/geometry/floor-sensor.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/geometry/paddle.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/geometry/wall-side.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/geometry/wall-top.geometry.xml +1 -0
- package/dist-app/examples/breakout/assets/materials/ball.material.xml +1 -0
- package/dist-app/examples/breakout/assets/materials/brick-firm.material.xml +1 -0
- package/dist-app/examples/breakout/assets/materials/brick-hard.material.xml +1 -0
- package/dist-app/examples/breakout/assets/materials/brick-soft.material.xml +1 -0
- package/dist-app/examples/breakout/assets/materials/paddle.material.xml +10 -0
- package/dist-app/examples/breakout/assets/materials/wall.material.xml +1 -0
- package/dist-app/examples/breakout/assets/render/neon.render.xml +14 -0
- package/dist-app/examples/breakout/assets/scenes/arena.prefab.xml +68 -0
- package/dist-app/examples/breakout/assets/scenes/brick.prefab.xml +15 -0
- package/dist-app/examples/breakout/assets/scenes/hud.scene.xml +56 -0
- package/dist-app/examples/breakout/assets/scenes/level-1.scene.xml +36 -0
- package/dist-app/examples/breakout/assets/scenes/level-2.scene.xml +36 -0
- package/dist-app/examples/breakout/assets/scenes/level-3.scene.xml +36 -0
- package/dist-app/examples/castle/assets/geometry/capsule.geometry.xml +3 -0
- package/dist-app/examples/castle/assets/geometry/cone.geometry.xml +2 -0
- package/dist-app/examples/castle/assets/geometry/cube.geometry.xml +3 -0
- package/dist-app/examples/castle/assets/geometry/cylinder.geometry.xml +3 -0
- package/dist-app/examples/castle/assets/geometry/plane.geometry.xml +4 -0
- package/dist-app/examples/castle/assets/geometry/quad.geometry.xml +3 -0
- package/dist-app/examples/castle/assets/geometry/sphere.geometry.xml +2 -0
- package/dist-app/examples/castle/assets/materials/grass.material.xml +1 -0
- package/dist-app/examples/castle/assets/materials/roof.material.xml +1 -0
- package/dist-app/examples/castle/assets/materials/stone.material.xml +1 -0
- package/dist-app/examples/castle/assets/materials/wood.material.xml +1 -0
- package/dist-app/examples/castle/assets/scenes/main.scene.xml +25 -0
- package/dist-app/examples/collector/assets/geometry/capsule.geometry.xml +3 -0
- package/dist-app/examples/collector/assets/geometry/cone.geometry.xml +2 -0
- package/dist-app/examples/collector/assets/geometry/cube.geometry.xml +3 -0
- package/dist-app/examples/collector/assets/geometry/cylinder.geometry.xml +3 -0
- package/dist-app/examples/collector/assets/geometry/plane.geometry.xml +4 -0
- package/dist-app/examples/collector/assets/geometry/quad.geometry.xml +3 -0
- package/dist-app/examples/collector/assets/geometry/sphere.geometry.xml +2 -0
- package/dist-app/examples/collector/assets/materials/crate.material.xml +1 -0
- package/dist-app/examples/collector/assets/materials/depot.material.xml +1 -0
- package/dist-app/examples/collector/assets/materials/ground.material.xml +1 -0
- package/dist-app/examples/collector/assets/materials/player.material.xml +1 -0
- package/dist-app/examples/collector/assets/scenes/crate.prefab.xml +9 -0
- package/dist-app/examples/collector/assets/scenes/main.scene.xml +83 -0
- package/dist-app/examples/playable/assets/geometry/capsule.geometry.xml +3 -0
- package/dist-app/examples/playable/assets/geometry/cone.geometry.xml +2 -0
- package/dist-app/examples/playable/assets/geometry/cube.geometry.xml +3 -0
- package/dist-app/examples/playable/assets/geometry/cylinder.geometry.xml +3 -0
- package/dist-app/examples/playable/assets/geometry/plane.geometry.xml +4 -0
- package/dist-app/examples/playable/assets/geometry/quad.geometry.xml +3 -0
- package/dist-app/examples/playable/assets/geometry/sphere.geometry.xml +2 -0
- package/dist-app/examples/playable/assets/materials/ground.material.xml +1 -0
- package/dist-app/examples/playable/assets/materials/target.material.xml +2 -0
- package/dist-app/examples/playable/assets/scenes/main.scene.xml +53 -0
- package/dist-app/examples/playable/assets/scenes/target.prefab.xml +9 -0
- package/dist-app/examples.json +68 -0
- package/{app → dist-app}/index.html +2 -2
- package/dist-app/model.json +1 -0
- package/package.json +25 -29
- package/app/cameraRig.ts +0 -118
- package/app/commands.ts +0 -78
- package/app/desktopControls.ts +0 -47
- package/app/document.ts +0 -547
- package/app/editLog.ts +0 -127
- package/app/editor.css +0 -51
- package/app/gizmo.ts +0 -169
- package/app/globals.d.ts +0 -9
- package/app/main.ts +0 -546
- package/app/meta.ts +0 -24
- package/app/picking.ts +0 -47
- package/app/session.ts +0 -612
- package/app/touchControls.ts +0 -127
- package/app/ui-react/AddMemberPopup.tsx +0 -114
- package/app/ui-react/BottomLauncher.tsx +0 -15
- package/app/ui-react/EditorUI.tsx +0 -22
- package/app/ui-react/FilePicker.tsx +0 -80
- package/app/ui-react/HeadBtn.tsx +0 -21
- package/app/ui-react/InspectorSheet.tsx +0 -475
- package/app/ui-react/NodeListSheet.tsx +0 -56
- package/app/ui-react/TopBar.tsx +0 -137
- package/app/ui-react/Tree.tsx +0 -106
- package/app/ui-react/cx.ts +0 -4
- package/app/ui-react/fields.tsx +0 -407
- package/app/ui-react/format.ts +0 -30
- package/app/ui-react/mount.tsx +0 -31
- package/app/ui-react/store.ts +0 -80
- package/app/ui-react/types.ts +0 -88
- package/app/ui-react/ui.module.css +0 -250
- package/app/ui-react/useSize.ts +0 -20
- package/app/values.ts +0 -137
- package/app/vite-env.d.ts +0 -7
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -10
- package/dist/mcp.d.ts +0 -32
- package/dist/mcp.js +0 -191
- package/dist/protocol.d.ts +0 -170
- package/dist/protocol.js +0 -22
- package/dist/server.d.ts +0 -124
- package/dist/server.js +0 -440
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenoco-three/editor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "The standalone SceNoCo editor: a static site that opens any .scene.xml from a folder or a URL, builds the scene live, and saves back \u2014 a tree, an inspector, gizmos. `scenoco-editor` serves it locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -14,25 +14,20 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"scenoco-editor": "./dist/cli/index.js"
|
|
16
16
|
},
|
|
17
|
-
"main": "./dist/index.js",
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
|
-
"exports": {
|
|
20
|
-
".": {
|
|
21
|
-
"types": "./dist/index.d.ts",
|
|
22
|
-
"import": "./dist/index.js"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
17
|
+
"main": "./dist/cli/index.js",
|
|
25
18
|
"files": [
|
|
26
19
|
"dist",
|
|
27
|
-
"app"
|
|
20
|
+
"dist-app"
|
|
28
21
|
],
|
|
29
22
|
"keywords": [
|
|
30
23
|
"three",
|
|
31
24
|
"threejs",
|
|
32
25
|
"scenoco",
|
|
33
26
|
"editor",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
27
|
+
"scene",
|
|
28
|
+
"xml",
|
|
29
|
+
"gizmo",
|
|
30
|
+
"inspector",
|
|
36
31
|
"gamedev"
|
|
37
32
|
],
|
|
38
33
|
"publishConfig": {
|
|
@@ -40,29 +35,30 @@
|
|
|
40
35
|
},
|
|
41
36
|
"scripts": {
|
|
42
37
|
"prebuild": "rm -rf dist",
|
|
43
|
-
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli/index.js",
|
|
38
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli/index.js && npm run build:app",
|
|
39
|
+
"build:app": "node scripts/static-model.mjs && node scripts/static-assets.mjs && vite build --config vite.static.config.ts",
|
|
40
|
+
"preview:app": "vite preview --config vite.static.config.ts --port 5195 --strictPort",
|
|
44
41
|
"typecheck": "npm --prefix ../core run build && npm --prefix ../compiler run build && tsc --noEmit -p tsconfig.json"
|
|
45
42
|
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"@scenoco-three/
|
|
43
|
+
"dependencies": {},
|
|
44
|
+
"peerDependencies": {},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@scenoco-three/audio": "^0.6.0",
|
|
47
|
+
"@scenoco-three/box2d": "^0.6.0",
|
|
48
|
+
"@scenoco-three/collision": "^0.6.0",
|
|
49
|
+
"@scenoco-three/compiler": "^0.6.0",
|
|
50
|
+
"@scenoco-three/core": "^0.6.0",
|
|
51
|
+
"@scenoco-three/particles": "^0.6.0",
|
|
52
|
+
"@scenoco-three/postfx": "^0.6.0",
|
|
53
|
+
"@scenoco-three/ui": "^0.6.0",
|
|
54
|
+
"@types/react": "^18.3.31",
|
|
55
|
+
"@types/react-dom": "^18.3.7",
|
|
50
56
|
"@vitejs/plugin-react": "^6.0.3",
|
|
51
57
|
"lucide-react": "^0.468.0",
|
|
52
58
|
"react": "^18.3.1",
|
|
53
59
|
"react-arborist": "^3.4.0",
|
|
54
60
|
"react-dom": "^18.3.1",
|
|
55
|
-
"
|
|
56
|
-
"zod": "^4.0.0",
|
|
61
|
+
"vite": "^8.0.0",
|
|
57
62
|
"zustand": "^5.0.2"
|
|
58
|
-
},
|
|
59
|
-
"peerDependencies": {
|
|
60
|
-
"three": ">=0.160.0",
|
|
61
|
-
"vite": ">=5"
|
|
62
|
-
},
|
|
63
|
-
"devDependencies": {
|
|
64
|
-
"@types/react": "^18.3.31",
|
|
65
|
-
"@types/react-dom": "^18.3.7",
|
|
66
|
-
"@types/ws": "^8.5.13"
|
|
67
63
|
}
|
|
68
64
|
}
|
package/app/cameraRig.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { Euler, Quaternion, Vector3, type PerspectiveCamera } from 'three';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Owns the editor camera's orientation (yaw/pitch, roll-free) and exposes simple
|
|
5
|
-
* verbs that both the desktop (mouse/keyboard) and touch input adapters call, so
|
|
6
|
-
* the two never fight over the camera transform. Call `sync()` once per frame.
|
|
7
|
-
*/
|
|
8
|
-
export class CameraRig {
|
|
9
|
-
readonly euler = new Euler(0, 0, 0, 'YXZ');
|
|
10
|
-
/** Base movement speed (world units/sec), tuned by the wheel / scene size. */
|
|
11
|
-
speed: number;
|
|
12
|
-
/**
|
|
13
|
-
* Distance to the focus point (Unity scene-camera pivot). Touch pan/dolly scale
|
|
14
|
-
* by this, so motion stays consistent on screen at any zoom — fine near objects,
|
|
15
|
-
* fast when far. Set by `frameOn`; dolly shrinks/grows it.
|
|
16
|
-
*/
|
|
17
|
-
focusDistance: number;
|
|
18
|
-
|
|
19
|
-
private readonly q = new Quaternion();
|
|
20
|
-
private readonly v = new Vector3();
|
|
21
|
-
|
|
22
|
-
/** Where a focus glide is taking the camera, while one is running. */
|
|
23
|
-
private glideTo: Vector3 | null = null;
|
|
24
|
-
|
|
25
|
-
readonly camera: PerspectiveCamera;
|
|
26
|
-
|
|
27
|
-
constructor(camera: PerspectiveCamera, target: Vector3) {
|
|
28
|
-
this.camera = camera;
|
|
29
|
-
camera.lookAt(target);
|
|
30
|
-
this.euler.setFromQuaternion(camera.quaternion);
|
|
31
|
-
this.speed = Math.max(1, camera.position.distanceTo(target) * 0.5);
|
|
32
|
-
this.focusDistance = Math.max(0.1, camera.position.distanceTo(target));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Frame a point: keep looking exactly the way you are, and move until it is centred
|
|
37
|
-
* `distance` away.
|
|
38
|
-
*
|
|
39
|
-
* This is Unity's F, and the angle is the whole of it. Turning to face the selection instead
|
|
40
|
-
* throws away the view the person spent their time arranging — they lined the scene up from
|
|
41
|
-
* one side, pressed F to get closer to a node, and now they are looking at it from wherever
|
|
42
|
-
* the pivot maths decided. Keeping the orientation means F only ever answers "put that in
|
|
43
|
-
* front of me", which is the question it was asked.
|
|
44
|
-
*
|
|
45
|
-
* The camera glides rather than teleports: a cut leaves you working out where you ended up,
|
|
46
|
-
* and the motion is what tells you the scene did not change — only your seat.
|
|
47
|
-
*/
|
|
48
|
-
frameOn(point: Vector3, distance: number): void {
|
|
49
|
-
this.focusDistance = Math.max(0.1, distance);
|
|
50
|
-
// Straight back along the current view direction — `-Z` in camera space, which is where the
|
|
51
|
-
// camera already looks, so nothing about the orientation moves.
|
|
52
|
-
this.glideTo = point.clone().addScaledVector(this.forward(this.v), -this.focusDistance);
|
|
53
|
-
this.speed = Math.max(1, this.focusDistance * 0.5); // fly speed scales with how far out we sit
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The unit vector the camera currently looks along. */
|
|
57
|
-
private forward(into: Vector3): Vector3 {
|
|
58
|
-
return into.set(0, 0, -1).applyQuaternion(this.q.setFromEuler(this.euler));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Add yaw/pitch in radians (pitch clamped to avoid flipping). */
|
|
62
|
-
rotate(dYaw: number, dPitch: number): void {
|
|
63
|
-
this.glideTo = null; // the person is driving now
|
|
64
|
-
this.euler.y += dYaw;
|
|
65
|
-
this.euler.x = clamp(this.euler.x + dPitch, -Math.PI / 2 + 0.01, Math.PI / 2 - 0.01);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Translate: `right` and `forward` in camera-local space, `up` along **world** up.
|
|
70
|
-
*
|
|
71
|
-
* Local up is the tempting reading and the wrong one. With the camera pitched down at the
|
|
72
|
-
* scene — which is where an editor camera spends its life — a local "up" also carries you
|
|
73
|
-
* backwards, so Q and E slide the view around instead of raising and lowering it. Unity's scene
|
|
74
|
-
* camera uses world up for the same reason.
|
|
75
|
-
*/
|
|
76
|
-
move(right: number, up: number, forward: number): void {
|
|
77
|
-
this.glideTo = null; // any input cancels a focus glide — the camera has one owner at a time
|
|
78
|
-
this.v.set(right, 0, -forward).applyQuaternion(this.q.setFromEuler(this.euler));
|
|
79
|
-
this.v.y += up;
|
|
80
|
-
this.camera.position.add(this.v);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Apply the orientation, and advance a focus glide. Call once per frame, after input.
|
|
85
|
-
*
|
|
86
|
-
* Frame-rate independent easing: the *fraction of the remaining distance* covered per second
|
|
87
|
-
* is fixed, so the same glide takes the same time at 60Hz and at 144Hz. `1 - k ** dt` is the
|
|
88
|
-
* exponential form of that — the naive `lerp(a, b, 0.1)` per frame is twice as fast on a
|
|
89
|
-
* 120Hz display, which is how a smooth camera on one machine becomes a snap on another.
|
|
90
|
-
*/
|
|
91
|
-
sync(dt = 0): void {
|
|
92
|
-
this.camera.quaternion.setFromEuler(this.euler);
|
|
93
|
-
if (!this.glideTo) return;
|
|
94
|
-
const remaining = this.v.subVectors(this.glideTo, this.camera.position);
|
|
95
|
-
// Close enough that another frame of easing would be invisible: land exactly and stop, so
|
|
96
|
-
// the camera is never left a hair off with a live tween attached to it.
|
|
97
|
-
if (remaining.lengthSq() < 1e-8) {
|
|
98
|
-
this.camera.position.copy(this.glideTo);
|
|
99
|
-
this.glideTo = null;
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
this.camera.position.addScaledVector(remaining, 1 - GLIDE_LEFT_PER_SECOND ** dt);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The fraction of the distance to the focus target still left after one second.
|
|
108
|
-
*
|
|
109
|
-
* Measured rather than guessed: at 1e-6 the camera is 94% of the way there in 0.2s and 98.5% in
|
|
110
|
-
* 0.3s, so the move reads as quick and decisive and is visually over in about a third of a
|
|
111
|
-
* second. Ten times gentler (1e-5) drifts long enough to feel laggy on a phone; ten times
|
|
112
|
-
* sharper is indistinguishable from a cut.
|
|
113
|
-
*/
|
|
114
|
-
const GLIDE_LEFT_PER_SECOND = 1e-6;
|
|
115
|
-
|
|
116
|
-
function clamp(v: number, lo: number, hi: number): number {
|
|
117
|
-
return Math.max(lo, Math.min(hi, v));
|
|
118
|
-
}
|
package/app/commands.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The stateful edit primitive. Every mutation in the editor is a `Command` so it
|
|
3
|
-
* can be undone and (because each one also mutates the compiled scene) saved as a
|
|
4
|
-
* diff. Commands are deliberately tiny — `apply` redoes, `invert` undoes.
|
|
5
|
-
*/
|
|
6
|
-
export interface Command {
|
|
7
|
-
/** Human label (undo menu / debugging). */
|
|
8
|
-
readonly label: string;
|
|
9
|
-
apply(): void;
|
|
10
|
-
invert(): void;
|
|
11
|
-
/**
|
|
12
|
-
* Edits sharing a key, applied back-to-back, collapse into a single undo step.
|
|
13
|
-
* A gizmo drag or a numeric scrub emits one command per frame; undo should
|
|
14
|
-
* rewind the whole gesture, not each intermediate value.
|
|
15
|
-
*/
|
|
16
|
-
readonly coalesceKey?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Undo/redo history with same-key coalescing. `onChange` fires after every mutation. */
|
|
20
|
-
export class UndoStack {
|
|
21
|
-
private readonly undoables: Command[] = [];
|
|
22
|
-
private readonly redoables: Command[] = [];
|
|
23
|
-
|
|
24
|
-
private readonly onChange: (() => void) | undefined;
|
|
25
|
-
|
|
26
|
-
constructor(onChange?: () => void) {
|
|
27
|
-
this.onChange = onChange;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Apply a command and record it, merging into the previous one if keys match. */
|
|
31
|
-
run(cmd: Command): void {
|
|
32
|
-
cmd.apply();
|
|
33
|
-
const prev = this.undoables[this.undoables.length - 1];
|
|
34
|
-
if (cmd.coalesceKey !== undefined && prev?.coalesceKey === cmd.coalesceKey) {
|
|
35
|
-
// Adopt the latest `apply` (current value) but keep the earliest `invert`
|
|
36
|
-
// (the pre-gesture state), so one undo rewinds the entire gesture.
|
|
37
|
-
this.undoables[this.undoables.length - 1] = {
|
|
38
|
-
label: prev.label,
|
|
39
|
-
coalesceKey: prev.coalesceKey,
|
|
40
|
-
apply: cmd.apply,
|
|
41
|
-
invert: prev.invert,
|
|
42
|
-
};
|
|
43
|
-
} else {
|
|
44
|
-
this.undoables.push(cmd);
|
|
45
|
-
}
|
|
46
|
-
this.redoables.length = 0;
|
|
47
|
-
this.onChange?.();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
undo(): void {
|
|
51
|
-
const cmd = this.undoables.pop();
|
|
52
|
-
if (!cmd) return;
|
|
53
|
-
cmd.invert();
|
|
54
|
-
this.redoables.push(cmd);
|
|
55
|
-
this.onChange?.();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
redo(): void {
|
|
59
|
-
const cmd = this.redoables.pop();
|
|
60
|
-
if (!cmd) return;
|
|
61
|
-
cmd.apply();
|
|
62
|
-
this.undoables.push(cmd);
|
|
63
|
-
this.onChange?.();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get canUndo(): boolean {
|
|
67
|
-
return this.undoables.length > 0;
|
|
68
|
-
}
|
|
69
|
-
get canRedo(): boolean {
|
|
70
|
-
return this.redoables.length > 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
clear(): void {
|
|
74
|
-
this.undoables.length = 0;
|
|
75
|
-
this.redoables.length = 0;
|
|
76
|
-
this.onChange?.();
|
|
77
|
-
}
|
|
78
|
-
}
|
package/app/desktopControls.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Input, KeyCode, MouseButton } from '@scenoco-three/core';
|
|
2
|
-
import type { CameraRig } from './cameraRig.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Desktop fly navigation via `engine.input` (Unity scene-view style):
|
|
6
|
-
* • hold RIGHT mouse to look · WASD move · Q/E down/up · Shift faster · wheel speed.
|
|
7
|
-
*/
|
|
8
|
-
export class DesktopControls {
|
|
9
|
-
private readonly look = 0.0025; // radians per pixel
|
|
10
|
-
|
|
11
|
-
private readonly rig: CameraRig;
|
|
12
|
-
/** v2's input is one singleton attached to the canvas, not a per-engine manager. */
|
|
13
|
-
private readonly input = Input;
|
|
14
|
-
|
|
15
|
-
constructor(rig: CameraRig) {
|
|
16
|
-
this.rig = rig;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Advance one frame (call after `engine.tick`). */
|
|
20
|
-
update(dt: number): void {
|
|
21
|
-
const { input, rig } = this;
|
|
22
|
-
|
|
23
|
-
const wheel = input.mouseScrollDelta.y;
|
|
24
|
-
if (wheel !== 0) rig.speed = clamp(rig.speed * (wheel < 0 ? 1.1 : 1 / 1.1), 0.05, 1000);
|
|
25
|
-
|
|
26
|
-
if (input.getMouseButton(MouseButton.Right)) {
|
|
27
|
-
const d = input.mouseDelta;
|
|
28
|
-
rig.rotate(-d.x * this.look, -d.y * this.look);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const right = axis(input.getKey(KeyCode.D), input.getKey(KeyCode.A));
|
|
32
|
-
const up = axis(input.getKey(KeyCode.E), input.getKey(KeyCode.Q));
|
|
33
|
-
const forward = axis(input.getKey(KeyCode.W), input.getKey(KeyCode.S));
|
|
34
|
-
if (right || up || forward) {
|
|
35
|
-
const len = Math.hypot(right, up, forward);
|
|
36
|
-
const step = (rig.speed * (input.getKey(KeyCode.LeftShift) ? 4 : 1) * dt) / len;
|
|
37
|
-
rig.move(right * step, up * step, forward * step);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function axis(pos: boolean, neg: boolean): number {
|
|
43
|
-
return (pos ? 1 : 0) - (neg ? 1 : 0);
|
|
44
|
-
}
|
|
45
|
-
function clamp(v: number, lo: number, hi: number): number {
|
|
46
|
-
return Math.max(lo, Math.min(hi, v));
|
|
47
|
-
}
|