Spaces:
Sleeping
Sleeping
Update index.html
Browse files- index.html +62 -110
index.html
CHANGED
@@ -26,20 +26,15 @@
|
|
26 |
|
27 |
let scene, camera, renderer;
|
28 |
let raycaster, mouse;
|
29 |
-
let
|
30 |
-
let
|
31 |
-
const MAX_RETRIES = 5;
|
32 |
|
33 |
// Access State from Streamlit
|
34 |
const myUsername = window.USERNAME || `User_${Math.random().toString(36).substring(2, 6)}`;
|
35 |
-
const websocketUrl = window.WEBSOCKET_URL || "ws://localhost:8765";
|
36 |
let selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
|
37 |
const plotWidth = window.PLOT_WIDTH || 50.0;
|
38 |
const plotDepth = window.PLOT_DEPTH || 50.0;
|
39 |
-
|
40 |
-
// World State
|
41 |
-
const worldObjects = new Map();
|
42 |
-
const groundMeshes = {};
|
43 |
|
44 |
// Materials
|
45 |
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide });
|
@@ -78,8 +73,6 @@
|
|
78 |
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
79 |
document.body.appendChild(renderer.domElement);
|
80 |
|
81 |
-
connectWebSocket();
|
82 |
-
|
83 |
document.addEventListener('mousemove', onMouseMove, false);
|
84 |
document.addEventListener('click', onDocumentClick, false);
|
85 |
document.addEventListener('contextmenu', onRightClick, false);
|
@@ -87,7 +80,10 @@
|
|
87 |
|
88 |
window.updateSelectedObjectType = updateSelectedObjectType;
|
89 |
|
90 |
-
|
|
|
|
|
|
|
91 |
animate();
|
92 |
}
|
93 |
|
@@ -130,89 +126,17 @@
|
|
130 |
return groundMesh;
|
131 |
}
|
132 |
|
133 |
-
function
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
console.log("WebSocket connection established.");
|
139 |
-
connectionRetries = 0;
|
140 |
-
};
|
141 |
-
|
142 |
-
socket.onmessage = (event) => {
|
143 |
-
try {
|
144 |
-
const data = JSON.parse(event.data);
|
145 |
-
console.log("WebSocket message received:", data);
|
146 |
-
handleWebSocketMessage(data);
|
147 |
-
} catch (e) {
|
148 |
-
console.error("Failed to parse WebSocket message:", event.data, e);
|
149 |
-
}
|
150 |
-
};
|
151 |
-
|
152 |
-
socket.onerror = (error) => {
|
153 |
-
console.error("WebSocket error:", error);
|
154 |
-
};
|
155 |
-
|
156 |
-
socket.onclose = (event) => {
|
157 |
-
console.warn(`WebSocket connection closed. Code: ${event.code}, Reason: ${event.reason}. Clean: ${event.wasClean}`);
|
158 |
-
socket = null;
|
159 |
-
if (connectionRetries < MAX_RETRIES) {
|
160 |
-
connectionRetries++;
|
161 |
-
const delay = Math.pow(2, connectionRetries) * 1000;
|
162 |
-
console.log(`Attempting reconnection in ${delay / 1000}s...`);
|
163 |
-
setTimeout(connectWebSocket, delay);
|
164 |
-
} else {
|
165 |
-
console.error("WebSocket reconnection failed after max retries.");
|
166 |
-
}
|
167 |
-
};
|
168 |
-
}
|
169 |
-
|
170 |
-
function sendWebSocketMessage(type, payload) {
|
171 |
-
if (socket && socket.readyState === WebSocket.OPEN) {
|
172 |
-
const message = JSON.stringify({ type, payload });
|
173 |
-
socket.send(message);
|
174 |
-
} else {
|
175 |
-
console.warn("WebSocket not open. Message not sent:", type, payload);
|
176 |
}
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
switch (type) {
|
183 |
-
case "initial_state":
|
184 |
-
console.log(`Received initial world state with ${Object.keys(payload).length} objects.`);
|
185 |
-
clearWorldObjects();
|
186 |
-
for (const obj_id in payload) {
|
187 |
-
createAndPlaceObject(payload[obj_id], false);
|
188 |
-
}
|
189 |
-
break;
|
190 |
-
case "object_placed":
|
191 |
-
console.log(`Object placed by ${payload.username}:`, payload.object_data);
|
192 |
-
createAndPlaceObject(payload.object_data, false);
|
193 |
-
break;
|
194 |
-
case "object_deleted":
|
195 |
-
console.log(`Object deleted by ${payload.username}:`, payload.obj_id);
|
196 |
-
removeObjectById(payload.obj_id);
|
197 |
-
break;
|
198 |
-
case "user_join":
|
199 |
-
console.log(`User joined: ${payload.username} (${payload.id})`);
|
200 |
-
break;
|
201 |
-
case "user_leave":
|
202 |
-
console.log(`User left: ${payload.username} (${payload.id})`);
|
203 |
-
break;
|
204 |
-
case "user_rename":
|
205 |
-
console.log(`User ${payload.old_username} is now ${payload.new_username}`);
|
206 |
-
break;
|
207 |
-
case "chat_message":
|
208 |
-
console.log(`Chat from ${payload.username}: ${payload.message}`);
|
209 |
-
break;
|
210 |
-
case "tool_selected":
|
211 |
-
console.log(`Tool selected by ${payload.username}: ${payload.tool_type}`);
|
212 |
-
selectedObjectType = payload.tool_type;
|
213 |
-
break;
|
214 |
-
default:
|
215 |
-
console.warn("Received unknown WebSocket message type:", type);
|
216 |
}
|
217 |
}
|
218 |
|
@@ -222,17 +146,22 @@
|
|
222 |
scene.remove(mesh);
|
223 |
}
|
224 |
worldObjects.clear();
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
}
|
226 |
|
227 |
-
function
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
}
|
236 |
}
|
237 |
|
238 |
function createAndPlaceObject(objData, isNewlyPlacedLocally) {
|
@@ -590,10 +519,11 @@
|
|
590 |
|
591 |
createAndPlaceObject(newObjData, true);
|
592 |
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
|
|
597 |
}
|
598 |
}
|
599 |
|
@@ -605,10 +535,22 @@
|
|
605 |
if (intersects.length > 0) {
|
606 |
const obj_id = intersects[0].object.userData.obj_id;
|
607 |
console.log(`Deleting object ${obj_id}`);
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
612 |
}
|
613 |
}
|
614 |
|
@@ -628,6 +570,16 @@
|
|
628 |
renderer.render(scene, camera);
|
629 |
}
|
630 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
631 |
init();
|
632 |
</script>
|
633 |
</body>
|
|
|
26 |
|
27 |
let scene, camera, renderer;
|
28 |
let raycaster, mouse;
|
29 |
+
let worldObjects = new Map();
|
30 |
+
let groundMeshes = {};
|
|
|
31 |
|
32 |
// Access State from Streamlit
|
33 |
const myUsername = window.USERNAME || `User_${Math.random().toString(36).substring(2, 6)}`;
|
|
|
34 |
let selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
|
35 |
const plotWidth = window.PLOT_WIDTH || 50.0;
|
36 |
const plotDepth = window.PLOT_DEPTH || 50.0;
|
37 |
+
const worldState = window.WORLD_STATE || { objects: {}, players: {} };
|
|
|
|
|
|
|
38 |
|
39 |
// Materials
|
40 |
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide });
|
|
|
73 |
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
74 |
document.body.appendChild(renderer.domElement);
|
75 |
|
|
|
|
|
76 |
document.addEventListener('mousemove', onMouseMove, false);
|
77 |
document.addEventListener('click', onDocumentClick, false);
|
78 |
document.addEventListener('contextmenu', onRightClick, false);
|
|
|
80 |
|
81 |
window.updateSelectedObjectType = updateSelectedObjectType;
|
82 |
|
83 |
+
// Initialize scene with world state
|
84 |
+
loadWorldState();
|
85 |
+
|
86 |
+
console.log(`Three.js Initialized for user: ${myUsername}`);
|
87 |
animate();
|
88 |
}
|
89 |
|
|
|
126 |
return groundMesh;
|
127 |
}
|
128 |
|
129 |
+
function loadWorldState() {
|
130 |
+
clearWorldObjects();
|
131 |
+
const objects = worldState.objects || {};
|
132 |
+
for (const obj_id in objects) {
|
133 |
+
createAndPlaceObject(objects[obj_id], false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
}
|
135 |
+
// Initialize player positions (visualize as markers)
|
136 |
+
const players = worldState.players || {};
|
137 |
+
for (const username in players) {
|
138 |
+
const pos = players[username].position;
|
139 |
+
createPlayerMarker(username, pos);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
}
|
141 |
}
|
142 |
|
|
|
146 |
scene.remove(mesh);
|
147 |
}
|
148 |
worldObjects.clear();
|
149 |
+
// Clear player markers
|
150 |
+
scene.children.forEach(child => {
|
151 |
+
if (child.userData.isPlayerMarker) {
|
152 |
+
scene.remove(child);
|
153 |
+
}
|
154 |
+
});
|
155 |
}
|
156 |
|
157 |
+
function createPlayerMarker(username, position) {
|
158 |
+
const geometry = new THREE.SphereGeometry(0.3, 8, 8);
|
159 |
+
const material = new THREE.MeshBasicMaterial({ color: username === myUsername ? 0x0000ff : 0xff0000 });
|
160 |
+
const marker = new THREE.Mesh(geometry, material);
|
161 |
+
marker.position.set(position.x, position.y + 0.5, position.z);
|
162 |
+
marker.userData.isPlayerMarker = true;
|
163 |
+
marker.userData.username = username;
|
164 |
+
scene.add(marker);
|
|
|
165 |
}
|
166 |
|
167 |
function createAndPlaceObject(objData, isNewlyPlacedLocally) {
|
|
|
519 |
|
520 |
createAndPlaceObject(newObjData, true);
|
521 |
|
522 |
+
// Send place action to Streamlit
|
523 |
+
window.parent.postMessage({
|
524 |
+
type: 'place_object',
|
525 |
+
payload: { username: myUsername, object_data: newObjData }
|
526 |
+
}, '*');
|
527 |
}
|
528 |
}
|
529 |
|
|
|
535 |
if (intersects.length > 0) {
|
536 |
const obj_id = intersects[0].object.userData.obj_id;
|
537 |
console.log(`Deleting object ${obj_id}`);
|
538 |
+
removeObjectById(obj_id);
|
539 |
+
window.parent.postMessage({
|
540 |
+
type: 'delete_object',
|
541 |
+
payload: { username: myUsername, obj_id: obj_id }
|
542 |
+
}, '*');
|
543 |
+
}
|
544 |
+
}
|
545 |
+
|
546 |
+
function removeObjectById(obj_id) {
|
547 |
+
if (worldObjects.has(obj_id)) {
|
548 |
+
const mesh = worldObjects.get(obj_id);
|
549 |
+
scene.remove(mesh);
|
550 |
+
worldObjects.delete(obj_id);
|
551 |
+
console.log(`Removed object ${obj_id} from scene.`);
|
552 |
+
} else {
|
553 |
+
console.warn(`Attempted to remove non-existent object ID: ${obj_id}`);
|
554 |
}
|
555 |
}
|
556 |
|
|
|
570 |
renderer.render(scene, camera);
|
571 |
}
|
572 |
|
573 |
+
// Listen for messages from Streamlit
|
574 |
+
window.addEventListener('message', (event) => {
|
575 |
+
const data = event.data;
|
576 |
+
if (data.type === 'place_object') {
|
577 |
+
createAndPlaceObject(data.payload.object_data, false);
|
578 |
+
} else if (data.type === 'delete_object') {
|
579 |
+
removeObjectById(data.payload.obj_id);
|
580 |
+
}
|
581 |
+
});
|
582 |
+
|
583 |
init();
|
584 |
</script>
|
585 |
</body>
|