|
1 | | -import * as THREE from "three"; |
2 | | -import { VRButton } from "three/addons/webxr/VRButton.js"; |
3 | | -import { CHARACTER_MODES_REGISTRY } from "./modes/modes_registry.js"; |
| 1 | +import { createApp } from "./app/create_app.js"; |
4 | 2 |
|
5 | | -const statusEl = document.getElementById("status"); |
6 | | -const startButton = document.getElementById("start-btn"); |
7 | | -const joinPanel = document.getElementById("join-panel"); |
8 | | -const joinButton = document.getElementById("join-btn"); |
9 | | -const characterModeSelect = document.getElementById("character-mode"); |
10 | | -let selectedCharacterMode = "demo-spline"; |
11 | | - |
12 | | -const searchParams = new URLSearchParams(window.location.search); |
13 | | -const defaultWsScheme = window.location.protocol === "https:" ? "wss" : "ws"; |
14 | | -const defaultWsHost = window.location.hostname || "127.0.0.1"; |
15 | | -const defaultWsPort = searchParams.get("ws_port") ?? "8765"; |
16 | | -const serverHost = |
17 | | - searchParams.get("ws") ?? |
18 | | - `${defaultWsScheme}://${defaultWsHost}:${defaultWsPort}`; |
19 | | - |
20 | | -const calibrationOffset = new THREE.Vector3( |
21 | | - Number(searchParams.get("ox") ?? 0.0), |
22 | | - Number(searchParams.get("oy") ?? 0.0), |
23 | | - Number(searchParams.get("oz") ?? 0.0) |
24 | | -); |
25 | | - |
26 | | -let socket = null; |
27 | | -let userId = null; |
28 | | -let sessionRole = "vr_client"; |
29 | | -let sessionMode = "vr_client"; |
30 | | - |
31 | | -const scene = new THREE.Scene(); |
32 | | -scene.background = new THREE.Color(0x1a1f2b); |
33 | | -const worldRoot = new THREE.Group(); |
34 | | -worldRoot.position.copy(calibrationOffset); |
35 | | -scene.add(worldRoot); |
36 | | - |
37 | | -const camera = new THREE.PerspectiveCamera( |
38 | | - 70, |
39 | | - window.innerWidth / window.innerHeight, |
40 | | - 0.01, |
41 | | - 50 |
42 | | -); |
43 | | -camera.position.set(0, 1.4, 2.0); |
44 | | - |
45 | | -const renderer = new THREE.WebGLRenderer({ antialias: true }); |
46 | | -renderer.setSize(window.innerWidth, window.innerHeight); |
47 | | -renderer.xr.enabled = true; |
48 | | -document.body.appendChild(renderer.domElement); |
49 | | - |
50 | | -const hemi = new THREE.HemisphereLight(0xffffff, 0x223344, 1.2); |
51 | | -scene.add(hemi); |
52 | | - |
53 | | -const grid = new THREE.GridHelper(8, 16, 0x6c757d, 0x495057); |
54 | | -grid.position.y = 0; |
55 | | -worldRoot.add(grid); |
56 | | -const worldAxes = new THREE.AxesHelper(0.3); |
57 | | -worldRoot.add(worldAxes); |
58 | | - |
59 | | -function setStatus(text) { |
60 | | - statusEl.textContent = text; |
61 | | -} |
62 | | - |
63 | | -function applyCharacterMode(modeKey) { |
64 | | - // TODO: Implement character mode selection |
65 | | -} |
66 | | - |
67 | | -function connect(joinConfig) { |
68 | | - socket = new WebSocket(serverHost); |
69 | | - sessionRole = joinConfig.serverRole ?? joinConfig.role; |
70 | | - sessionMode = joinConfig.role; |
71 | | - |
72 | | - socket.onopen = () => { |
73 | | - setStatus(`connected: ${serverHost}`); |
74 | | - socket.send( |
75 | | - JSON.stringify({ |
76 | | - version: 1, |
77 | | - type: "hello", |
78 | | - payload: { |
79 | | - client: "sparc-webxr", |
80 | | - role: sessionRole, |
81 | | - }, |
82 | | - }) |
83 | | - ); |
84 | | - }; |
85 | | - |
86 | | - socket.onclose = (event) => { |
87 | | - setStatus(`disconnected: ${serverHost} (code=${event.code})`); |
88 | | - }; |
89 | | - socket.onerror = () => { |
90 | | - setStatus(`error: failed to connect ${serverHost}`); |
91 | | - }; |
92 | | - |
93 | | - socket.onmessage = (event) => { |
94 | | - // TODO: Implement message handling |
95 | | - |
96 | | - const message = JSON.parse(event.data); |
97 | | - |
98 | | - if (message.type === "error") { |
99 | | - setStatus(`server error: ${message.payload?.reason ?? "unknown"}`); |
100 | | - } |
101 | | - |
102 | | - }; |
103 | | -} |
104 | | - |
105 | | -function sendXRInput(frame) { |
106 | | - // TODO: Implement XR input sending |
107 | | -} |
108 | | - |
109 | | -function animate() { |
110 | | - // TODO: Implement animation loop |
111 | | - let lastTime = performance.now() / 1000; |
112 | | - renderer.setAnimationLoop((_, frame) => { |
113 | | - const now = performance.now() / 1000; |
114 | | - const dt = Math.min(0.05, now - lastTime); |
115 | | - lastTime = now; |
116 | | - |
117 | | - if (frame) sendXRInput(frame); |
118 | | - |
119 | | - renderer.render(scene, camera); |
120 | | - }); |
121 | | -} |
122 | | - |
123 | | -startButton.addEventListener("click", () => { |
124 | | - document.body.appendChild(VRButton.createButton(renderer)); |
125 | | - startButton.remove(); |
126 | | -}); |
127 | | - |
128 | | -window.addEventListener("resize", () => { |
129 | | - camera.aspect = window.innerWidth / window.innerHeight; |
130 | | - camera.updateProjectionMatrix(); |
131 | | - renderer.setSize(window.innerWidth, window.innerHeight); |
132 | | -}); |
133 | | - |
134 | | -/* TODO: Implement key handling for desktop mode */ |
135 | | -window.addEventListener("keydown", (event) => { |
136 | | - const key = event.key.toLowerCase(); |
137 | | - // TODO: Implement key handling |
138 | | -}); |
139 | | - |
140 | | -window.addEventListener("keyup", (event) => { |
141 | | - const key = event.key.toLowerCase(); |
142 | | - // TODO: Implement key handling |
143 | | -}); |
144 | | -/* TODO */ |
145 | | - |
146 | | -renderer.domElement.addEventListener("click", () => { |
147 | | - if (!renderer.xr.isPresenting && document.pointerLockElement !== renderer.domElement) { |
148 | | - renderer.domElement.requestPointerLock(); |
149 | | - } |
150 | | -}); |
151 | | - |
152 | | -document.addEventListener("mousemove", (event) => { |
153 | | - if (document.pointerLockElement !== renderer.domElement) return; |
154 | | - if (renderer.xr.isPresenting) return; |
155 | | - |
156 | | - // TODO: Implement desktop mode mouse movement handling |
157 | | -}); |
158 | | - |
159 | | -function setupJoinPanel() { |
160 | | - // TODO: Implement join panel setup |
161 | | -} |
162 | | - |
163 | | -function populateCharacterModeSelect() { |
164 | | - characterModeSelect.replaceChildren(); |
165 | | - for (const { id, label } of CHARACTER_MODES_REGISTRY) { |
166 | | - const opt = document.createElement("option"); |
167 | | - opt.value = id; |
168 | | - opt.textContent = label; |
169 | | - characterModeSelect.appendChild(opt); |
170 | | - } |
171 | | -} |
172 | | - |
173 | | -populateCharacterModeSelect(); |
174 | | -setupJoinPanel(); |
175 | | -animate(); |
| 3 | +const app = createApp({ document, window }); |
| 4 | +app.start(); |
0 commit comments