3DWorldBuilder / index.html
awacke1's picture
Update index.html
ccc8baf verified
raw
history blame
24.3 kB
<!DOCTYPE html>
<html>
<head>
<title>Three.js Infinite World</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
<!-- New: Polling function for game state -->
<script>
// Poll the shared game state every 5 seconds (for demonstration)
function pollGameState() {
console.log("Polling updated game state:", window.GAME_STATE);
// Here you could update the scene based on the new state.
}
setInterval(pollGameState, 5000);
</script>
</head>
<body>
<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, playerMesh;
let raycaster, mouse;
const keysPressed = {};
const playerSpeed = 0.15;
let newlyPlacedObjects = []; // Track objects added THIS session for saving
const placeholderPlots = new Set();
const groundMeshes = {}; // Store ground mesh references
// --- Session Storage Key ---
const SESSION_STORAGE_KEY = 'unsavedInfiniteWorldState';
// --- Injected State from Streamlit ---
const allInitialObjects = window.ALL_INITIAL_OBJECTS || [];
const plotsMetadata = window.PLOTS_METADATA || [];
const selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
const plotWidth = window.PLOT_WIDTH || 50.0;
const plotDepth = window.PLOT_DEPTH || 50.0;
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
});
// ─── Helper to tag every object/group with type & unique ID ──────────────
function createObjectBase(type) {
return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
}
// ─── Primitive creators ─────────────────────────────────────────────────
function createSimpleHouse() {
const base = createObjectBase("Simple House");
const group = new THREE.Group();
Object.assign(group, base);
const mat1 = new THREE.MeshStandardMaterial({ color: 0xffccaa, roughness: 0.8 });
const mat2 = new THREE.MeshStandardMaterial({ color: 0xaa5533, roughness: 0.7 });
const m1 = new THREE.Mesh(new THREE.BoxGeometry(2, 1.5, 2.5), mat1);
m1.position.y = 1.5/2;
m1.castShadow = m1.receiveShadow = true;
group.add(m1);
const m2 = new THREE.Mesh(new THREE.ConeGeometry(1.8, 1, 4), mat2);
m2.position.y = 1.5 + 0.5;
m2.rotation.y = Math.PI/4;
m2.castShadow = m2.receiveShadow = true;
group.add(m2);
return group;
}
function createTree() {
const base = createObjectBase("Tree");
const group = new THREE.Group();
Object.assign(group, base);
const matTrunk = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.9 });
const matFoliage = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.8 });
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.4, 2, 8), matTrunk);
trunk.position.y = 1;
trunk.castShadow = trunk.receiveShadow = true;
group.add(trunk);
const foliage = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2, 0), matFoliage);
foliage.position.y = 2.8;
foliage.castShadow = foliage.receiveShadow = true;
group.add(foliage);
return group;
}
function createRock() {
const base = createObjectBase("Rock");
const mat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8, metalness: 0.1 });
const rock = new THREE.Mesh(new THREE.IcosahedronGeometry(0.7, 0), mat);
Object.assign(rock, base);
rock.position.y = 0.35;
rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
rock.castShadow = rock.receiveShadow = true;
return rock;
}
function createFencePost() {
const base = createObjectBase("Fence Post");
const mat = new THREE.MeshStandardMaterial({ color: 0xdeb887, roughness: 0.9 });
const post = new THREE.Mesh(new THREE.BoxGeometry(0.2, 1.5, 0.2), mat);
Object.assign(post, base);
post.position.y = 0.75;
post.castShadow = post.receiveShadow = true;
return post;
}
// ─── 1. Cyberpunk City Builder Kit ────────────────────────────────────────
function createCyberpunkWallPanel({
width=2, height=3, depth=0.2,
baseMat={ color:0x555555, metalness:0.8, roughness:0.4 },
trimMat={ emissive:0x00ffff, emissiveIntensity:1.2 },
position=new THREE.Vector3()
} = {}) {
const panelBase = new THREE.BoxGeometry(width, height, depth);
const panel = new THREE.Mesh(panelBase, new THREE.MeshStandardMaterial(baseMat));
panel.position.copy(position);
return panel;
}
function createRooftopACUnit({
width=1, height=0.5, depth=1,
matProps={ color:0x777777, roughness:0.7, metalness:0.3 },
position=new THREE.Vector3()
} = {}) {
const unit = new THREE.Mesh(
new THREE.BoxGeometry(width, height, depth),
new THREE.MeshStandardMaterial(matProps)
);
unit.position.copy(position);
return unit;
}
function createHolographicWindowDisplay({
width=1.5, height=1, position=new THREE.Vector3()
} = {}) {
const geom = new THREE.PlaneGeometry(width, height);
const mat = new THREE.MeshBasicMaterial({
color:0xffffff, transparent:true, opacity:0.6
});
const disp = new THREE.Mesh(geom, mat);
disp.position.copy(position);
return disp;
}
function createGreebleSet({
count=10, scale=0.2, position=new THREE.Vector3()
} = {}) {
const grp = new THREE.Group();
for(let i=0; i<count; i++){
const dx = (Math.random()-0.5)*2;
const dy = (Math.random()-0.5)*2;
const dz = (Math.random()-0.5)*0.2;
const g = new THREE.Mesh(
new THREE.BoxGeometry(scale, scale, scale),
new THREE.MeshStandardMaterial({ color:0x444444, roughness:0.6, metalness:0.4 })
);
g.position.copy(position).add(new THREE.Vector3(dx,dy,dz));
grp.add(g);
}
return grp;
}
function createCyberpunkKit() {
const base = createObjectBase("Cyberpunk City Builder Kit");
const kit = new THREE.Group();
Object.assign(kit, base);
kit.add(createCyberpunkWallPanel({ position:new THREE.Vector3(0,1,0) }));
kit.add(createRooftopACUnit({ position:new THREE.Vector3(3,0.5,0) }));
kit.add(createHolographicWindowDisplay({ position:new THREE.Vector3(6,1,0) }));
kit.add(createGreebleSet({ position:new THREE.Vector3(9,0,0) }));
return kit;
}
// ─── 2. POLYGON – Fantasy Kingdom Pack ───────────────────────────────────
function createKingFigure({ position=new THREE.Vector3() } = {}) {
const base = createObjectBase("POLYGON - Fantasy Kingdom Pack");
const mesh = new THREE.Mesh(
new THREE.CylinderGeometry(0.3,0.5,1.8,6),
new THREE.MeshStandardMaterial({ color:0xffd700, metalness:0.9, roughness:0.2 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createSoldierFigure({ position=new THREE.Vector3() } = {}) {
const base = createObjectBase("POLYGON - Fantasy Kingdom Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.5,1.6,0.4),
new THREE.MeshStandardMaterial({ color:0x888888, metalness:0.6, roughness:0.4 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createMageFigure({ position=new THREE.Vector3() } = {}) {
const base = createObjectBase("POLYGON - Fantasy Kingdom Pack");
const mesh = new THREE.Mesh(
new THREE.ConeGeometry(0.4,1.5,6),
new THREE.MeshStandardMaterial({ color:0x9933ff, roughness:0.5 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createFantasyKingdomPack() {
const grp = new THREE.Group();
Object.assign(grp, createObjectBase("POLYGON - Fantasy Kingdom Pack"));
grp.add(createKingFigure({ position:new THREE.Vector3(0,0,0) }));
grp.add(createSoldierFigure({ position:new THREE.Vector3(2,0,0) }));
grp.add(createMageFigure({ position:new THREE.Vector3(4,0,0) }));
return grp;
}
// ─── 3. HEROIC FANTASY CREATURES FULL PACK VOLΒ 1 ─────────────────────────
function createDemonicCreatureBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.8,8,6),
new THREE.MeshStandardMaterial({ color:0x550000, roughness:0.7 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createFantasyAnimalBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1.2,0.6,2),
new THREE.MeshStandardMaterial({ color:0x665533, roughness:0.8 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createFantasyLizardBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.ConeGeometry(0.3,1,6),
new THREE.MeshStandardMaterial({ color:0x339933, roughness:0.6 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createLivingDeadBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.8,1.8,0.5),
new THREE.MeshStandardMaterial({ color:0x777777, roughness:0.9 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createFantasyVillainBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.OctahedronGeometry(0.9,0),
new THREE.MeshStandardMaterial({ color:0x220022, roughness:0.7 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createMythologicalCreatureBase({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1");
const mesh = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.5,0.2,100,16),
new THREE.MeshStandardMaterial({ color:0xdddd00, roughness:0.5 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createHeroicFantasyCreaturesPack() {
const grp = new THREE.Group();
Object.assign(grp, createObjectBase("HEROIC FANTASY CREATURES FULL PACK VOLΒ 1"));
grp.add(createDemonicCreatureBase({ position:new THREE.Vector3(0,0,0) }));
grp.add(createFantasyAnimalBase({ position:new THREE.Vector3(3,0,0) }));
grp.add(createFantasyLizardBase({ position:new THREE.Vector3(6,0,0) }));
grp.add(createLivingDeadBase({ position:new THREE.Vector3(9,0,0) }));
grp.add(createFantasyVillainBase({ position:new THREE.Vector3(12,0,0) }));
grp.add(createMythologicalCreatureBase({ position:new THREE.Vector3(15,0,0) }));
return grp;
}
// ─── 4. POLYGON – Apocalypse Pack (Low Poly) ─────────────────────────────
function createZombieFigure({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("POLYGON - Apocalypse Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.5,1.7,0.4),
new THREE.MeshStandardMaterial({ color:0x445544, roughness:0.9 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createSurvivorFigure({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("POLYGON - Apocalypse Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.5,1.7,0.4),
new THREE.MeshStandardMaterial({ color:0x885522, roughness:0.7 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createBackpack({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("POLYGON - Apocalypse Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.6,0.8,0.3),
new THREE.MeshStandardMaterial({ color:0x333333, roughness:0.8 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createMakeshiftArmor({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("POLYGON - Apocalypse Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1,1.2,0.2),
new THREE.MeshStandardMaterial({ color:0x555555, roughness:0.9 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createBuggyFrame({ position=new THREE.Vector3() }={}) {
const base = createObjectBase("POLYGON - Apocalypse Pack");
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(2,0.5,1.2),
new THREE.MeshStandardMaterial({ color:0x666666, roughness:0.7 })
);
Object.assign(mesh, base);
mesh.position.copy(position);
return mesh;
}
function createApocalypsePack() {
const grp = new THREE.Group();
Object.assign(grp, createObjectBase("POLYGON - Apocalypse Pack"));
grp.add(createZombieFigure({ position:new THREE.Vector3(0,0,0) }));
grp.add(createSurvivorFigure({ position:new THREE.Vector3(3,0,0) }));
grp.add(createBackpack({ position:new THREE.Vector3(6,0,0) }));
grp.add(createMakeshiftArmor({ position:new THREE.Vector3(9,0,0) }));
grp.add(createBuggyFrame({ position:new THREE.Vector3(12,0,0) }));
return grp;
}
// ─── Existing loader that places from Python data ────────────────────────
function createAndPlaceObject(objData, isNewObject) {
let loadedObject = null;
switch (objData.type) {
case "Simple House": loadedObject = createSimpleHouse(); break;
case "Tree": loadedObject = createTree(); break;
case "Rock": loadedObject = createRock(); break;
case "Fence Post": loadedObject = createFencePost(); break;
case "Cyberpunk City Builder Kit":
loadedObject = createCyberpunkKit(); break;
case "POLYGON - Fantasy Kingdom Pack":
loadedObject = createFantasyKingdomPack(); break;
case "HEROIC FANTASY CREATURES FULL PACK VOLΒ 1":
loadedObject = createHeroicFantasyCreaturesPack(); break;
case "POLYGON - Apocalypse Pack":
loadedObject = createApocalypsePack(); break;
default: console.warn("Unknown object type in data:", objData.type); break;
}
if (loadedObject) {
// position & rotation from CSV or JSON
if (objData.position && objData.position.x !== undefined) {
loadedObject.position.set(objData.position.x, objData.position.y, objData.position.z);
} else if (objData.pos_x !== undefined) {
loadedObject.position.set(objData.pos_x, objData.pos_y, objData.pos_z);
}
if (objData.rotation) {
loadedObject.rotation.set(
objData.rotation._x, objData.rotation._y, objData.rotation._z
);
} else if (objData.rot_x !== undefined) {
loadedObject.rotation.set(
objData.rot_x, objData.rot_y, objData.rot_z
);
}
scene.add(loadedObject);
if (isNewObject) newlyPlacedObjects.push(loadedObject);
return loadedObject;
}
return null;
}
// ─── Setup, Animate etc. (unchanged) ────────────────────────────────────
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, 4000);
camera.position.set(0,15,20);
camera.lookAt(0,0,0);
scene.add(camera);
setupLighting();
setupInitialGround();
setupPlayer();
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);
loadInitialObjects();
restoreUnsavedState();
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('click', onDocumentClick, false);
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
window.teleportPlayer = teleportPlayer;
window.getSaveDataAndPosition = getSaveDataAndPosition;
window.resetNewlyPlacedObjects = resetNewlyPlacedObjects;
console.log("Three.js Initialized. World ready.");
animate();
}
// ─── Click to place logic ───────────────────────────────────────────────
function onDocumentClick(event) {
if (selectedObjectType === "None") return;
const grounds = Object.values(groundMeshes);
if (!grounds.length) return;
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(grounds);
if (hits.length) {
const pt = hits[0].point;
let newObj = null;
switch (selectedObjectType) {
case "Simple House": newObj = createSimpleHouse(); break;
case "Tree": newObj = createTree(); break;
case "Rock": newObj = createRock(); break;
case "Fence Post": newObj = createFencePost(); break;
case "Cyberpunk City Builder Kit":
newObj = createCyberpunkKit(); break;
case "POLYGON - Fantasy Kingdom Pack":
newObj = createFantasyKingdomPack(); break;
case "HEROIC FANTASY CREATURES FULL PACK VOLΒ 1":
newObj = createHeroicFantasyCreaturesPack(); break;
case "POLYGON - Apocalypse Pack":
newObj = createApocalypsePack(); break;
default: return;
}
if (newObj) {
newObj.position.copy(pt);
newObj.position.y += 0.01;
scene.add(newObj);
newlyPlacedObjects.push(newObj);
saveUnsavedState();
console.log(`Placed new ${selectedObjectType}. Unsaved total: ${newlyPlacedObjects.length}`);
}
}
}
// ─── Rest of your functions: save/restore state, movement, animate, etc. ──
function saveUnsavedState() { /* unchanged */ }
function restoreUnsavedState() { /* unchanged */ }
function clearUnsavedState() { /* unchanged */ }
function teleportPlayer(x,z) { /* unchanged */ }
function getSaveDataAndPosition() { /* unchanged */ }
function resetNewlyPlacedObjects() { /* unchanged */ }
function updatePlayerMovement() { /* unchanged */ }
function checkAndExpandGround() { /* unchanged */ }
function updateCamera() { /* unchanged */ }
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onKeyDown(e) { keysPressed[e.code] = true; }
function onKeyUp(e) { keysPressed[e.code] = false; }
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
updatePlayerMovement();
updateCamera();
renderer.render(scene, camera);
}
// ─── Kick it off ────────────────────────────────────────────────────────
init();
</script>
</body>
</html>