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>
|