Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Shared World Builder</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { display: block; } | |
#info { position: absolute; top: 10px; left: 10px; color: white; background: rgba(0,0,0,0.7); padding: 10px; } | |
</style> | |
</head> | |
<body> | |
<div id="info">Click to place selected object or move player, right-click to delete.</div> | |
<script type="importmap"> | |
{ | |
"imports": { | |
"three": "https://unpkg.com/[email protected]/build/three.module.js", | |
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/" | |
} | |
} | |
</script> | |
<script type="module"> | |
import * as THREE from 'three'; | |
let scene, camera, renderer; | |
let raycaster, mouse; | |
let worldObjects = new Map(); | |
let groundMeshes = {}; | |
// Access State from Streamlit | |
const myUsername = window.USERNAME || `User_${Math.random().toString(36).substring(2, 6)}`; | |
let selectedObjectType = window.SELECTED_OBJECT_TYPE || "None"; | |
const plotWidth = window.PLOT_WIDTH || 50.0; | |
const plotDepth = window.PLOT_DEPTH || 50.0; | |
const worldState = window.WORLD_STATE || { objects: {}, players: {}, action_history: [] }; | |
// Materials | |
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide }); | |
const placeholderGroundMaterial = new THREE.MeshStandardMaterial({ color: 0x448844, roughness: 0.95, metalness: 0.1, side: THREE.DoubleSide }); | |
const objectMaterials = { | |
'wood': new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.9 }), | |
'leaf': new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.8 }), | |
'stone': new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8, metalness: 0.1 }), | |
'house_wall': new THREE.MeshStandardMaterial({ color: 0xffccaa, roughness: 0.8 }), | |
'house_roof': new THREE.MeshStandardMaterial({ color: 0xaa5533, roughness: 0.7 }), | |
'brick': new THREE.MeshStandardMaterial({ color: 0x9B4C43, roughness: 0.85 }), | |
'metal': new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.4, metalness: 0.8 }), | |
'gem': new THREE.MeshStandardMaterial({ color: 0x4FFFFF, roughness: 0.1, metalness: 0.2, transparent: true, opacity: 0.8 }), | |
'light': new THREE.MeshBasicMaterial({ color: 0xFFFF88 }) | |
}; | |
const VALID_OBJECT_TYPES = [ | |
"Tree", "Rock", "Simple House", "Pine Tree", "Brick Wall", "Sphere", "Cube", | |
"Cylinder", "Cone", "Torus", "Mushroom", "Cactus", "Campfire", "Star", "Gem", | |
"Tower", "Barrier", "Fountain", "Lantern", "Sign Post" | |
]; | |
function init() { | |
scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0xabcdef); | |
const aspect = window.innerWidth / window.innerHeight; | |
camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 5000); | |
camera.position.set(plotWidth / 2, 15, plotDepth / 2 + 20); | |
camera.lookAt(plotWidth/2, 0, plotDepth/2); | |
scene.add(camera); | |
setupLighting(); | |
createGroundPlane(0, 0, false); | |
raycaster = new THREE.Raycaster(); | |
mouse = new THREE.Vector2(); | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
document.body.appendChild(renderer.domElement); | |
document.addEventListener('mousemove', onMouseMove, false); | |
document.addEventListener('click', onDocumentClick, false); | |
document.addEventListener('contextmenu', onRightClick, false); | |
window.addEventListener('resize', onWindowResize, false); | |
window.updateSelectedObjectType = updateSelectedObjectType; | |
// Initialize scene with world state | |
loadWorldState(); | |
console.log(`Three.js Initialized for user: ${myUsername}`); | |
animate(); | |
} | |
function setupLighting() { | |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); | |
directionalLight.position.set(75, 150, 100); | |
directionalLight.castShadow = true; | |
directionalLight.shadow.mapSize.width = 2048; | |
directionalLight.shadow.mapSize.height = 2048; | |
directionalLight.shadow.camera.near = 10; | |
directionalLight.shadow.camera.far = 400; | |
directionalLight.shadow.camera.left = -150; | |
directionalLight.shadow.camera.right = 150; | |
directionalLight.shadow.camera.top = 150; | |
directionalLight.shadow.camera.bottom = -150; | |
directionalLight.shadow.bias = -0.002; | |
scene.add(directionalLight); | |
const hemiLight = new THREE.HemisphereLight(0xabcdef, 0x55aa55, 0.5); | |
scene.add(hemiLight); | |
} | |
function createGroundPlane(gridX, gridZ, isPlaceholder) { | |
const gridKey = `${gridX}_${gridZ}`; | |
if (groundMeshes[gridKey]) return groundMeshes[gridKey]; | |
const groundGeometry = new THREE.PlaneGeometry(plotWidth, plotDepth); | |
const material = isPlaceholder ? placeholderGroundMaterial : groundMaterial; | |
const groundMesh = new THREE.Mesh(groundGeometry, material); | |
groundMesh.rotation.x = -Math.PI / 2; | |
groundMesh.position.y = -0.05; | |
groundMesh.position.x = gridX * plotWidth + plotWidth / 2.0; | |
groundMesh.position.z = gridZ * plotDepth + plotDepth / 2.0; | |
groundMesh.receiveShadow = true; | |
groundMesh.userData.gridKey = gridKey; | |
groundMesh.userData.isPlaceholder = isPlaceholder; | |
groundMesh.name = 'ground'; | |
scene.add(groundMesh); | |
groundMeshes[gridKey] = groundMesh; | |
return groundMesh; | |
} | |
function loadWorldState() { | |
clearWorldObjects(); | |
const objects = worldState.objects || {}; | |
console.log(`Loading ${Object.keys(objects).length} objects from WORLD_STATE`); | |
for (const obj_id in objects) { | |
const objData = objects[obj_id]; | |
if (!objData || !objData.obj_id || !objData.type || !VALID_OBJECT_TYPES.includes(objData.type) || !objData.position) { | |
console.warn(`Skipping invalid object: ${obj_id}`, objData); | |
continue; | |
} | |
const mesh = createAndPlaceObject(objData, false); | |
if (!mesh) { | |
console.warn(`Failed to create mesh for object: ${obj_id}`, objData); | |
} | |
} | |
const players = worldState.players || {}; | |
console.log(`Loading ${Object.keys(players).length} players from WORLD_STATE`); | |
for (const username in players) { | |
const pos = players[username].position; | |
if (pos && typeof pos.x === 'number' && typeof pos.y === 'number' && typeof pos.z === 'number') { | |
createPlayerMarker(username, pos); | |
} else { | |
console.warn(`Invalid player position for ${username}:`, pos); | |
} | |
} | |
const actionHistory = worldState.action_history || []; | |
console.log(`Loaded ${actionHistory.length} action history entries`); | |
} | |
function clearWorldObjects() { | |
console.log("Clearing existing world objects..."); | |
for (const [obj_id, mesh] of worldObjects.entries()) { | |
scene.remove(mesh); | |
} | |
worldObjects.clear(); | |
scene.children.forEach(child => { | |
if (child.userData.isPlayerMarker) { | |
scene.remove(child); | |
} | |
}); | |
} | |
function createPlayerMarker(username, position) { | |
const geometry = new THREE.SphereGeometry(0.3, 8, 8); | |
const material = new THREE.MeshBasicMaterial({ color: username === myUsername ? 0x0000ff : 0xff0000 }); | |
const marker = new THREE.Mesh(geometry, material); | |
marker.position.set(position.x, position.y + 0.5, position.z); | |
marker.userData.isPlayerMarker = true; | |
marker.userData.username = username; | |
scene.add(marker); | |
console.log(`Created player marker for ${username} at`, position); | |
} | |
function createAndPlaceObject(objData, isNewlyPlacedLocally) { | |
if (!objData || !objData.obj_id || !objData.type || !objData.position) { | |
console.warn("Invalid object data:", objData); | |
return null; | |
} | |
let mesh = worldObjects.get(objData.obj_id); | |
let isNew = false; | |
if (mesh) { | |
if (mesh.position.distanceToSquared(new THREE.Vector3(objData.position.x, objData.position.y, objData.position.z)) > 0.001) { | |
mesh.position.set(objData.position.x, objData.position.y, objData.position.z); | |
} | |
if (objData.rotation && ( | |
Math.abs(mesh.rotation.x - (objData.rotation._x || 0)) > 0.01 || | |
Math.abs(mesh.rotation.y - (objData.rotation._y || 0)) > 0.01 || | |
Math.abs(mesh.rotation.z - (objData.rotation._z || 0)) > 0.01 )) { | |
mesh.rotation.set( | |
objData.rotation._x || 0, | |
objData.rotation._y || 0, | |
objData.rotation._z || 0, | |
objData.rotation._order || 'XYZ' | |
); | |
} | |
} else { | |
mesh = createPrimitiveMesh(objData.type); | |
if (!mesh) return null; | |
isNew = true; | |
mesh.userData.obj_id = objData.obj_id; | |
mesh.userData.type = objData.type; | |
mesh.position.set(objData.position.x, objData.position.y, objData.position.z); | |
if (objData.rotation) { | |
mesh.rotation.set( | |
objData.rotation._x || 0, | |
objData.rotation._y || 0, | |
objData.rotation._z || 0, | |
objData.rotation._order || 'XYZ' | |
); | |
} | |
scene.add(mesh); | |
worldObjects.set(objData.obj_id, mesh); | |
console.log(`Created new object ${objData.obj_id} (${objData.type}) at`, objData.position); | |
} | |
return mesh; | |
} | |
function createPrimitiveMesh(type) { | |
if (!VALID_OBJECT_TYPES.includes(type)) { | |
console.warn(`Invalid object type for mesh creation: ${type}`); | |
return null; | |
} | |
let mesh = null; | |
let geometry, material, material2; | |
const wood = objectMaterials.wood; | |
const leaf = objectMaterials.leaf; | |
const stone = objectMaterials.stone; | |
const house_wall = objectMaterials.house_wall; | |
const house_roof = objectMaterials.house_roof; | |
const brick = objectMaterials.brick; | |
const metal = objectMaterials.metal; | |
const gem = objectMaterials.gem; | |
const lightMat = objectMaterials.light; | |
try { | |
switch(type) { | |
case "Tree": | |
mesh = new THREE.Group(); | |
geometry = new THREE.CylinderGeometry(0.3, 0.4, 2, 8); | |
material = wood; | |
const trunk = new THREE.Mesh(geometry, material); | |
trunk.position.y = 1; | |
trunk.castShadow = true; | |
trunk.receiveShadow = true; | |
mesh.add(trunk); | |
geometry = new THREE.IcosahedronGeometry(1.2, 0); | |
material = leaf; | |
const canopy = new THREE.Mesh(geometry, material); | |
canopy.position.y = 2.8; | |
canopy.castShadow = true; | |
canopy.receiveShadow = false; | |
mesh.add(canopy); | |
break; | |
case "Rock": | |
geometry = new THREE.IcosahedronGeometry(0.7, 1); | |
material = stone; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
mesh.scale.set(1, Math.random()*0.4 + 0.8, 1); | |
break; | |
case "Simple House": | |
mesh = new THREE.Group(); | |
geometry = new THREE.BoxGeometry(2, 1.5, 2.5); | |
material = house_wall; | |
const body = new THREE.Mesh(geometry, material); | |
body.position.y = 0.75; | |
body.castShadow = true; | |
body.receiveShadow = true; | |
mesh.add(body); | |
geometry = new THREE.ConeGeometry(1.8, 1, 4); | |
material = house_roof; | |
const roof = new THREE.Mesh(geometry, material); | |
roof.position.y = 1.5 + 0.5; | |
roof.rotation.y = Math.PI / 4; | |
roof.castShadow = true; | |
roof.receiveShadow = false; | |
mesh.add(roof); | |
break; | |
case "Pine Tree": | |
mesh = new THREE.Group(); | |
geometry = new THREE.CylinderGeometry(0.2, 0.3, 2.5, 8); | |
material = wood; | |
const pineTrunk = new THREE.Mesh(geometry, material); | |
pineTrunk.position.y = 1.25; | |
pineTrunk.castShadow = true; | |
pineTrunk.receiveShadow = true; | |
mesh.add(pineTrunk); | |
geometry = new THREE.ConeGeometry(1, 2.5, 8); | |
material = leaf; | |
const pineCanopy = new THREE.Mesh(geometry, material); | |
pineCanopy.position.y = 2.5 + (2.5/2) - 0.5; | |
pineCanopy.castShadow = true; | |
pineCanopy.receiveShadow = false; | |
mesh.add(pineCanopy); | |
break; | |
case "Brick Wall": | |
geometry = new THREE.BoxGeometry(3, 2, 0.3); | |
material = brick; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 1; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Sphere": | |
geometry = new THREE.SphereGeometry(0.8, 16, 12); | |
material = metal; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.8; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Cube": | |
geometry = new THREE.BoxGeometry(1, 1, 1); | |
material = stone; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.5; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Cylinder": | |
geometry = new THREE.CylinderGeometry(0.5, 0.5, 1.5, 16); | |
material = metal; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.75; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Cone": | |
geometry = new THREE.ConeGeometry(0.6, 1.2, 16); | |
material = house_roof; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.6; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Torus": | |
geometry = new THREE.TorusGeometry(0.6, 0.2, 8, 24); | |
material = gem; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.7; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
mesh.rotation.x = Math.PI / 2; | |
break; | |
case "Mushroom": | |
mesh = new THREE.Group(); | |
geometry = new THREE.CylinderGeometry(0.15, 0.1, 0.6, 8); | |
material = house_wall; | |
const stem = new THREE.Mesh(geometry, material); | |
stem.position.y = 0.3; | |
stem.castShadow = true; | |
stem.receiveShadow = true; | |
mesh.add(stem); | |
geometry = new THREE.SphereGeometry(0.4, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2); | |
material = house_roof; | |
const cap = new THREE.Mesh(geometry, material); | |
cap.position.y = 0.6; | |
cap.castShadow = true; | |
cap.receiveShadow = false; | |
mesh.add(cap); | |
break; | |
case "Cactus": | |
mesh = new THREE.Group(); | |
material = leaf; | |
geometry = new THREE.CylinderGeometry(0.3, 0.3, 1.5, 8); | |
const main = new THREE.Mesh(geometry, material); | |
main.position.y = 0.75; | |
main.castShadow = true; | |
main.receiveShadow = true; | |
mesh.add(main); | |
geometry = new THREE.CylinderGeometry(0.2, 0.2, 0.8, 8); | |
const arm1 = new THREE.Mesh(geometry, material); | |
arm1.position.set(0.3, 1, 0); | |
arm1.rotation.z = Math.PI / 4; | |
arm1.castShadow = true; | |
arm1.receiveShadow = true; | |
mesh.add(arm1); | |
const arm2 = new THREE.Mesh(geometry, material); | |
arm2.position.set(-0.3, 0.8, 0); | |
arm2.rotation.z = -Math.PI / 4; | |
arm2.castShadow = true; | |
arm2.receiveShadow = true; | |
mesh.add(arm2); | |
break; | |
case "Campfire": | |
mesh = new THREE.Group(); | |
material = wood; | |
geometry = new THREE.CylinderGeometry(0.1, 0.1, 0.8, 5); | |
const log1 = new THREE.Mesh(geometry, material); | |
log1.rotation.x = Math.PI/2; | |
log1.position.set(0, 0.1, 0.2); | |
mesh.add(log1); | |
const log2 = new THREE.Mesh(geometry, material); | |
log2.rotation.set(Math.PI/2, 0, Math.PI/3); | |
log2.position.set(0.2*Math.cos(Math.PI/6), 0.1, -0.2*Math.sin(Math.PI/6)); | |
mesh.add(log2); | |
const log3 = new THREE.Mesh(geometry, material); | |
log3.rotation.set(Math.PI/2, 0, -Math.PI/3); | |
log3.position.set(-0.2*Math.cos(Math.PI/6), 0.1, -0.2*Math.sin(Math.PI/6)); | |
mesh.add(log3); | |
material2 = lightMat; | |
geometry = new THREE.ConeGeometry(0.2, 0.5, 8); | |
const flame = new THREE.Mesh(geometry, material2); | |
flame.position.y = 0.35; | |
mesh.add(flame); | |
break; | |
case "Star": | |
geometry = new THREE.SphereGeometry(0.5, 4, 2); | |
material = lightMat; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 1; | |
break; | |
case "Gem": | |
geometry = new THREE.OctahedronGeometry(0.6, 0); | |
material = gem; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.6; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Tower": | |
geometry = new THREE.CylinderGeometry(1, 1.2, 5, 8); | |
material = stone; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 2.5; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Barrier": | |
geometry = new THREE.BoxGeometry(2, 0.5, 0.5); | |
material = metal; | |
mesh = new THREE.Mesh(geometry, material); | |
mesh.position.y = 0.25; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Fountain": | |
mesh = new THREE.Group(); | |
material = stone; | |
geometry = new THREE.CylinderGeometry(1.5, 1.5, 0.3, 16); | |
const baseF = new THREE.Mesh(geometry, material); | |
baseF.position.y = 0.15; | |
mesh.add(baseF); | |
geometry = new THREE.CylinderGeometry(0.8, 0.8, 0.5, 16); | |
const midF = new THREE.Mesh(geometry, material); | |
midF.position.y = 0.3 + 0.25; | |
mesh.add(midF); | |
geometry = new THREE.CylinderGeometry(0.4, 0.4, 0.7, 16); | |
const topF = new THREE.Mesh(geometry, material); | |
topF.position.y = 0.8 + 0.35; | |
mesh.add(topF); | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
case "Lantern": | |
mesh = new THREE.Group(); | |
material = metal; | |
geometry = new THREE.BoxGeometry(0.4, 0.6, 0.4); | |
const bodyL = new THREE.Mesh(geometry, material); | |
bodyL.position.y = 0.3; | |
mesh.add(bodyL); | |
geometry = new THREE.SphereGeometry(0.15); | |
material2 = lightMat; | |
const lightL = new THREE.Mesh(geometry, material2); | |
lightL.position.y = 0.3; | |
mesh.add(lightL); | |
mesh.castShadow = true; | |
break; | |
case "Sign Post": | |
mesh = new THREE.Group(); | |
material = wood; | |
geometry = new THREE.CylinderGeometry(0.05, 0.05, 1.8, 8); | |
const postS = new THREE.Mesh(geometry, material); | |
postS.position.y = 0.9; | |
mesh.add(postS); | |
geometry = new THREE.BoxGeometry(0.8, 0.4, 0.05); | |
const signS = new THREE.Mesh(geometry, material); | |
signS.position.y = 1.5; | |
mesh.add(signS); | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
break; | |
default: | |
console.warn("Unexpected object type:", type); | |
return null; | |
} | |
} catch (e) { | |
console.error(`Error creating geometry/mesh for type ${type}:`, e); | |
return null; | |
} | |
if (mesh) { | |
mesh.userData = { type: type }; | |
if (!mesh.position.y && mesh.geometry) { | |
mesh.geometry.computeBoundingBox(); | |
mesh.position.y = (mesh.geometry.boundingBox.max.y - mesh.geometry.boundingBox.min.y) / 2; | |
} | |
} | |
return mesh; | |
} | |
function onMouseMove(event) { | |
mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |
} | |
function onDocumentClick(event) { | |
const groundCandidates = Object.values(groundMeshes); | |
if (groundCandidates.length === 0) return; | |
raycaster.setFromCamera(mouse, camera); | |
const intersects = raycaster.intersectObjects(groundCandidates); | |
if (intersects.length > 0) { | |
const intersectPoint = intersects[0].point; | |
if (selectedObjectType !== "None" && selectedObjectType) { | |
// Place object | |
const newObjData = { | |
obj_id: THREE.MathUtils.generateUUID(), | |
type: selectedObjectType, | |
position: { x: intersectPoint.x, y: 0, z: intersectPoint.z }, | |
rotation: { _x: 0, _y: Math.random() * Math.PI * 2, _z: 0, _order: 'XYZ' } | |
}; | |
const tempMesh = createPrimitiveMesh(selectedObjectType); | |
if (tempMesh && tempMesh.geometry) { | |
tempMesh.geometry.computeBoundingBox(); | |
const height = tempMesh.geometry.boundingBox.max.y - tempMesh.geometry.boundingBox.min.y; | |
newObjData.position.y = (height / 2) + intersectPoint.y + 0.01; | |
} else { | |
newObjData.position.y = 0.5 + intersectPoint.y; | |
} | |
console.log(`Placing ${selectedObjectType} (${newObjData.obj_id}) at`, newObjData.position); | |
const mesh = createAndPlaceObject(newObjData, true); | |
if (mesh) { | |
window.parent.postMessage({ | |
type: 'place_object', | |
payload: { username: myUsername, object_data: newObjData } | |
}, '*'); | |
} | |
} else { | |
// Move player | |
const newPosition = { | |
x: intersectPoint.x, | |
y: 0.5, | |
z: intersectPoint.z | |
}; | |
console.log(`Moving player ${myUsername} to`, newPosition); | |
window.parent.postMessage({ | |
type: 'move_player', | |
payload: { username: myUsername, position: newPosition } | |
}, '*'); | |
} | |
} | |
} | |
function onRightClick(event) { | |
event.preventDefault(); | |
raycaster.setFromCamera(mouse, camera); | |
const intersects = raycaster.intersectObjects(Array.from(worldObjects.values())); | |
if (intersects.length > 0) { | |
const obj_id = intersects[0].object.userData.obj_id; | |
console.log(`Deleting object ${obj_id}`); | |
removeObjectById(obj_id); | |
window.parent.postMessage({ | |
type: 'delete_object', | |
payload: { username: myUsername, obj_id: obj_id } | |
}, '*'); | |
} | |
} | |
function removeObjectById(obj_id) { | |
if (worldObjects.has(obj_id)) { | |
const mesh = worldObjects.get(obj_id); | |
scene.remove(mesh); | |
worldObjects.delete(obj_id); | |
console.log(`Removed object ${obj_id} from scene.`); | |
} else { | |
console.warn(`Attempted to remove non-existent object ID: ${obj_id}`); | |
} | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
function updateSelectedObjectType(newType) { | |
console.log("JS updateSelectedObjectType received:", newType); | |
selectedObjectType = newType; | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
renderer.render(scene, camera); | |
} | |
// Listen for messages from Streamlit | |
window.addEventListener('message', (event) => { | |
const data = event.data; | |
if (data.type === 'place_object') { | |
const mesh = createAndPlaceObject(data.payload.object_data, false); | |
if (!mesh) { | |
console.warn(`Failed to place object from message:`, data.payload.object_data); | |
} | |
} else if (data.type === 'delete_object') { | |
removeObjectById(data.payload.obj_id); | |
} else if (data.type === 'move_player') { | |
const username = data.payload.username; | |
const position = data.payload.position; | |
scene.children.forEach(child => { | |
if (child.userData.isPlayerMarker && child.userData.username === username) { | |
child.position.set(position.x, position.y + 0.5, position.z); | |
console.log(`Updated player marker for ${username} to`, position); | |
} | |
}); | |
} | |
}); | |
init(); | |
</script> | |
</body> | |
</html> |