Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Three.js Infinite World Builder</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| background-color: #333; | |
| } | |
| canvas { | |
| display: block; | |
| } | |
| #ui-container { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| z-index: 10; | |
| background: rgba(0,0,0,0.6); | |
| padding: 10px; | |
| border-radius: 8px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| color: white; | |
| max-width: 250px; | |
| } | |
| .ui-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .ui-row label { | |
| white-space: nowrap; | |
| } | |
| button, select, input[type="number"] { | |
| background-color: #555; | |
| color: white; | |
| border: 1px solid #777; | |
| padding: 8px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| width: 100%; | |
| box-sizing: border-box; | |
| } | |
| button:hover { | |
| background-color: #666; | |
| } | |
| select, input[type="number"] { | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| appearance: none; | |
| background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23ffffff%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E'); | |
| background-repeat: no-repeat; | |
| background-position: right 10px top 50%; | |
| background-size: .65em auto; | |
| padding-right: 2em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- UI Elements for User Interaction --> | |
| <div id="ui-container"> | |
| <div class="ui-row"> | |
| <button id="newWorldButton">New World</button> | |
| </div> | |
| <div class="ui-row"> | |
| <button id="loadWorldButton">Load World</button> | |
| <input type="file" id="loadWorldInput" accept=".json" style="display: none;"> | |
| </div> | |
| <div class="ui-row"> | |
| <button id="saveWorldButton">Save World</button> | |
| </div> | |
| <hr style="border-color: #555; width:100%;"> | |
| <div class="ui-row"> | |
| <label for="objectSelect">Object:</label> | |
| <select id="objectSelect"></select> | |
| </div> | |
| <div class="ui-row"> | |
| <label for="scaleInput">Scale:</label> | |
| <input type="number" id="scaleInput" value="1.0" step="0.1" min="0.1"> | |
| </div> | |
| <div class="ui-row"> | |
| <label for="rotationInput">Rotation (Y):</label> | |
| <input type="number" id="rotationInput" value="0" step="15" min="0" max="345"> | |
| </div> | |
| </div> | |
| <!-- Import map for Three.js --> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js", | |
| "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| <!-- Main Application Logic --> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| // --- Core Scene Variables --- | |
| let scene, camera, renderer, playerMesh; | |
| let raycaster, mouse; | |
| const keysPressed = {}; | |
| const playerSpeed = 0.15; | |
| // --- World State Management --- | |
| let worldObjects = []; // This is the single source of truth for all placed objects | |
| const groundMeshes = {}; // Store ground mesh references by grid key | |
| // --- World Configuration --- | |
| const PLOT_WIDTH = 50.0; | |
| const PLOT_DEPTH = 50.0; | |
| // --- Materials --- | |
| const groundMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide | |
| }); | |
| // --- Object Creation Map --- | |
| const objectFactory = {}; | |
| // --- UI Elements --- | |
| const newWorldButton = document.getElementById('newWorldButton'); | |
| const saveWorldButton = document.getElementById('saveWorldButton'); | |
| const loadWorldButton = document.getElementById('loadWorldButton'); | |
| const loadWorldInput = document.getElementById('loadWorldInput'); | |
| const objectSelect = document.getElementById('objectSelect'); | |
| const scaleInput = document.getElementById('scaleInput'); | |
| const rotationInput = document.getElementById('rotationInput'); | |
| /** | |
| * Initializes the entire application. | |
| */ | |
| 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(); | |
| setupPlayer(); | |
| populateObjectFactory(); | |
| populateObjectSelector(); | |
| 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); | |
| addEventListeners(); | |
| resetWorld(); // Start with a fresh world | |
| console.log("Three.js Initialized. World ready."); | |
| animate(); | |
| } | |
| /** | |
| * Sets up lights for the scene. | |
| */ | |
| function setupLighting() { | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); | |
| directionalLight.position.set(50, 150, 100); | |
| directionalLight.castShadow = true; | |
| directionalLight.shadow.mapSize.width = 4096; | |
| directionalLight.shadow.mapSize.height = 4096; | |
| directionalLight.shadow.camera.near = 0.5; | |
| directionalLight.shadow.camera.far = 500; | |
| directionalLight.shadow.camera.left = -150; | |
| directionalLight.shadow.camera.right = 150; | |
| directionalLight.shadow.camera.top = 150; | |
| directionalLight.shadow.camera.bottom = -150; | |
| directionalLight.shadow.bias = -0.001; | |
| scene.add(directionalLight); | |
| } | |
| /** | |
| * Creates the player representation in the scene. | |
| */ | |
| function setupPlayer() { | |
| const playerGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8); | |
| const playerMat = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: 0.6 }); | |
| playerMesh = new THREE.Mesh(playerGeo, playerMat); | |
| playerMesh.castShadow = true; | |
| playerMesh.receiveShadow = true; | |
| scene.add(playerMesh); | |
| } | |
| /** | |
| * Clears the world and resets the state to a default. | |
| */ | |
| function resetWorld() { | |
| console.log("Creating a new world."); | |
| // Clear existing objects | |
| worldObjects.forEach(objData => { | |
| const object = scene.getObjectByProperty('uuid', objData.uuid); | |
| if (object) { | |
| scene.remove(object); | |
| } | |
| }); | |
| worldObjects = []; | |
| // Clear ground planes | |
| for (const key in groundMeshes) { | |
| scene.remove(groundMeshes[key]); | |
| delete groundMeshes[key]; | |
| } | |
| // Create the initial ground plane at (0,0) | |
| createGroundPlane(0, 0); | |
| // Reset player position | |
| playerMesh.position.set(PLOT_WIDTH / 2, 0.4 + 0.8 / 2, PLOT_DEPTH / 2); | |
| updateCamera(true); // Force camera snap | |
| } | |
| /** | |
| * Populates the world based on data from a loaded file. | |
| * @param {object} worldData - The parsed JSON data from a world file. | |
| */ | |
| function populateWorld(worldData) { | |
| console.log("Loading world from file..."); | |
| resetWorld(); // Ensure the scene is clean before loading | |
| if (worldData.objects && Array.isArray(worldData.objects)) { | |
| worldData.objects.forEach(objData => { | |
| // Create and place the object in the scene | |
| const newObject = createObject(objData.type); | |
| if (newObject) { | |
| newObject.position.copy(objData.position); | |
| newObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order); | |
| newObject.scale.copy(objData.scale); | |
| newObject.userData.obj_id = objData.obj_id; // Preserve original ID | |
| scene.add(newObject); | |
| // Add its data to our source of truth, including its new scene UUID | |
| const newObjData = { ...objData, uuid: newObject.uuid }; | |
| worldObjects.push(newObjData); | |
| } | |
| }); | |
| } | |
| // Generate all necessary ground planes for the loaded objects | |
| generateGroundForLoadedWorld(); | |
| // Set player position if it exists in the save file | |
| if (worldData.playerPosition) { | |
| playerMesh.position.copy(worldData.playerPosition); | |
| } | |
| updateCamera(true); // Snap camera to new position | |
| console.log(`Loaded ${worldObjects.length} objects.`); | |
| } | |
| /** | |
| * Creates a ground plane at a specific grid coordinate. | |
| * @param {number} gridX - The x-coordinate in the grid. | |
| * @param {number} gridZ - The z-coordinate in the grid. | |
| */ | |
| function createGroundPlane(gridX, gridZ) { | |
| const gridKey = `${gridX}_${gridZ}`; | |
| if (groundMeshes[gridKey]) return; // Don't create if it already exists | |
| console.log(`Creating ground at ${gridX}, ${gridZ}`); | |
| const groundGeometry = new THREE.PlaneGeometry(PLOT_WIDTH, PLOT_DEPTH); | |
| const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial); | |
| groundMesh.rotation.x = -Math.PI / 2; | |
| groundMesh.position.y = -0.05; | |
| groundMesh.position.x = gridX * PLOT_WIDTH + PLOT_WIDTH / 2.0; | |
| groundMesh.position.z = gridZ * PLOT_DEPTH + PLOT_DEPTH / 2.0; | |
| groundMesh.receiveShadow = true; | |
| groundMesh.userData.gridKey = gridKey; | |
| scene.add(groundMesh); | |
| groundMeshes[gridKey] = groundMesh; | |
| } | |
| /** | |
| * After loading a world, this ensures all necessary ground tiles are created. | |
| */ | |
| function generateGroundForLoadedWorld() { | |
| const requiredGrids = new Set(); | |
| worldObjects.forEach(objData => { | |
| const gridX = Math.floor(objData.position.x / PLOT_WIDTH); | |
| const gridZ = Math.floor(objData.position.z / PLOT_DEPTH); | |
| requiredGrids.add(`${gridX}_${gridZ}`); | |
| }); | |
| // Also add grid for player | |
| const playerGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH); | |
| const playerGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH); | |
| requiredGrids.add(`${playerGridX}_${playerGridZ}`); | |
| requiredGrids.forEach(key => { | |
| const [gridX, gridZ] = key.split('_').map(Number); | |
| createGroundPlane(gridX, gridZ); | |
| }); | |
| } | |
| /** | |
| * Creates a generic base for any object, assigning a type and unique ID. | |
| * @param {string} type - The type name of the object. | |
| * @returns {object} - An object with userData containing type and a new UUID. | |
| */ | |
| function createObjectBase(type) { | |
| return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } }; | |
| } | |
| /** | |
| * Central function to create a 3D object based on its type string. | |
| * @param {string} type - The type of object to create. | |
| * @returns {THREE.Object3D|null} The created Three.js object or null if type is unknown. | |
| */ | |
| function createObject(type) { | |
| if (objectFactory[type]) { | |
| return objectFactory[type](); | |
| } | |
| console.warn("Unknown object type:", type); | |
| return null; | |
| } | |
| // --- All Object Creation Functions --- | |
| // (Abridged for brevity, the full list is included below this script block) | |
| 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 = true; m1.receiveShadow = true; | |
| group.add(m1); | |
| const m2 = new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4), mat2); | |
| m2.position.y = 1.5+1/2; | |
| m2.rotation.y = Math.PI/4; | |
| m2.castShadow = true; m2.receiveShadow = true; | |
| group.add(m2); | |
| return group; | |
| } | |
| function createTree() { | |
| const base = createObjectBase("Tree"); | |
| const group = new THREE.Group(); | |
| Object.assign(group, base); | |
| const mat1 = new THREE.MeshStandardMaterial({color:0x8B4513, roughness:0.9}); | |
| const mat2 = new THREE.MeshStandardMaterial({color:0x228B22, roughness:0.8}); | |
| const m1 = new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,2,8), mat1); | |
| m1.position.y = 1; | |
| m1.castShadow = true; m1.receiveShadow = true; | |
| group.add(m1); | |
| const m2 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2,0), mat2); | |
| m2.position.y = 2.8; | |
| m2.castShadow = true; m2.receiveShadow = true; | |
| group.add(m2); | |
| 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 = true; rock.receiveShadow = true; | |
| return rock; | |
| } | |
| // --- File and State Management --- | |
| /** | |
| * Gathers all world data and triggers a download of the world file. | |
| */ | |
| function saveWorldToFile() { | |
| console.log("Saving world to file..."); | |
| const dataToSave = { | |
| meta: { | |
| version: "1.0", | |
| plotWidth: PLOT_WIDTH, | |
| plotDepth: PLOT_DEPTH, | |
| savedAt: new Date().toISOString() | |
| }, | |
| playerPosition: playerMesh.position.clone(), | |
| objects: worldObjects.map(objData => { | |
| // We only need to store the data, not the live scene object reference | |
| const { uuid, ...rest } = objData; | |
| return rest; | |
| }) | |
| }; | |
| const jsonString = JSON.stringify(dataToSave, null, 2); | |
| const blob = new Blob([jsonString], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `world_${Date.now()}.json`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| console.log(`World saved with ${dataToSave.objects.length} objects.`); | |
| } | |
| /** | |
| * Handles the file selection for loading a world. | |
| * @param {Event} event - The file input change event. | |
| */ | |
| function handleWorldFileLoad(event) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| try { | |
| const worldData = JSON.parse(e.target.result); | |
| populateWorld(worldData); | |
| } catch (error) { | |
| console.error("Error parsing world file:", error); | |
| alert("Failed to load world file. It might be corrupted."); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // --- Event Handlers --- | |
| function addEventListeners() { | |
| document.addEventListener('mousemove', onMouseMove, false); | |
| document.addEventListener('click', onDocumentClick, false); | |
| window.addEventListener('resize', onWindowResize, false); | |
| document.addEventListener('keydown', onKeyDown); | |
| document.addEventListener('keyup', onKeyUp); | |
| newWorldButton.addEventListener('click', () => { | |
| if (confirm('Are you sure you want to start a new world? Any unsaved changes will be lost.')) { | |
| resetWorld(); | |
| } | |
| }); | |
| saveWorldButton.addEventListener('click', saveWorldToFile); | |
| loadWorldButton.addEventListener('click', () => loadWorldInput.click()); | |
| loadWorldInput.addEventListener('change', handleWorldFileLoad); | |
| } | |
| function onMouseMove(event) { | |
| // Adjust for UI offset if mouse is over it | |
| const uiRect = document.getElementById('ui-container').getBoundingClientRect(); | |
| if (event.clientX < uiRect.right && event.clientY < uiRect.bottom) { | |
| mouse.x = -99; // Prevent placement when mouse is over UI | |
| mouse.y = -99; | |
| return; | |
| } | |
| mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |
| mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |
| } | |
| function onDocumentClick(event) { | |
| if (mouse.x === -99) return; // Don't place if mouse was over UI | |
| const selectedObjectType = objectSelect.value; | |
| if (selectedObjectType === "None") return; | |
| 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; | |
| const newObject = createObject(selectedObjectType); | |
| if (newObject) { | |
| newObject.position.copy(intersectPoint); | |
| newObject.position.y += 0.01; // Prevent z-fighting | |
| const scale = parseFloat(scaleInput.value) || 1.0; | |
| const rotationY = THREE.MathUtils.degToRad(parseFloat(rotationInput.value) || 0); | |
| newObject.scale.setScalar(scale); | |
| newObject.rotation.y = rotationY; | |
| scene.add(newObject); | |
| // Add the new object's data to our main array | |
| const newObjectData = { | |
| obj_id: newObject.userData.obj_id, | |
| uuid: newObject.uuid, // Keep track of the live object in the scene | |
| type: newObject.userData.type, | |
| position: newObject.position.clone(), | |
| rotation: { _x: newObject.rotation.x, _y: newObject.rotation.y, _z: newObject.rotation.z, _order: newObject.rotation.order }, | |
| scale: newObject.scale.clone() | |
| }; | |
| worldObjects.push(newObjectData); | |
| console.log(`Placed new ${selectedObjectType}. Total objects: ${worldObjects.length}`); | |
| } | |
| } | |
| } | |
| function onKeyDown(event) { keysPressed[event.code] = true; } | |
| function onKeyUp(event) { keysPressed[event.code] = false; } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| // --- Game Loop and Updates --- | |
| function updatePlayerMovement() { | |
| if (!playerMesh) return; | |
| const moveDirection = new THREE.Vector3(0, 0, 0); | |
| if (keysPressed['KeyW'] || keysPressed['ArrowUp']) moveDirection.z -= 1; | |
| if (keysPressed['KeyS'] || keysPressed['ArrowDown']) moveDirection.z += 1; | |
| if (keysPressed['KeyA'] || keysPressed['ArrowLeft']) moveDirection.x -= 1; | |
| if (keysPressed['KeyD'] || keysPressed['ArrowRight']) moveDirection.x += 1; | |
| if (moveDirection.lengthSq() > 0) { | |
| moveDirection.normalize().multiplyScalar(playerSpeed); | |
| const forward = new THREE.Vector3(); | |
| camera.getWorldDirection(forward); | |
| forward.y = 0; | |
| forward.normalize(); | |
| const right = new THREE.Vector3().crossVectors(camera.up, forward).normalize(); | |
| const worldMove = new THREE.Vector3(); | |
| worldMove.add(forward.multiplyScalar(-moveDirection.z)); | |
| worldMove.add(right.multiplyScalar(-moveDirection.x)); | |
| worldMove.normalize().multiplyScalar(playerSpeed); | |
| playerMesh.position.add(worldMove); | |
| playerMesh.position.y = Math.max(playerMesh.position.y, 0.4 + 0.8/2); | |
| checkAndExpandGround(); | |
| } | |
| } | |
| function checkAndExpandGround() { | |
| if (!playerMesh) return; | |
| const currentGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH); | |
| const currentGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH); | |
| // Check a 3x3 area around the player | |
| for (let dx = -1; dx <= 1; dx++) { | |
| for (let dz = -1; dz <= 1; dz++) { | |
| createGroundPlane(currentGridX + dx, currentGridZ + dz); | |
| } | |
| } | |
| } | |
| function updateCamera(forceSnap = false) { | |
| if (!playerMesh) return; | |
| const offset = new THREE.Vector3(0, 15, 20); | |
| const targetPosition = playerMesh.position.clone().add(offset); | |
| if (forceSnap) { | |
| camera.position.copy(targetPosition); | |
| } else { | |
| camera.position.lerp(targetPosition, 0.08); | |
| } | |
| camera.lookAt(playerMesh.position); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| updatePlayerMovement(); | |
| updateCamera(); | |
| renderer.render(scene, camera); | |
| } | |
| // --- Full Object Factory --- | |
| function populateObjectFactory() { | |
| Object.assign(objectFactory, { | |
| "Simple House": createSimpleHouse, "Cyberpunk Wall Panel": createCyberpunkWallPanel, "Modular Hab Block": createModularHabBlock, "MegaCorp Skyscraper": createMegaCorpSkyscraper, | |
| "Castle Wall Section": createCastleWallSection, "Wooden Door": createWoodenDoor, "House Roof Section": createHouseRoofSection, "Concrete Bunker Wall": createConcreteBunkerWall, | |
| "Damaged House Facade": createDamagedHouseFacade, "Tree": createTree, "Rock": createRock, "Pine Tree": createPineTree, "Boulder": createBoulder, "Alien Plant": createAlienPlant, | |
| "Floating Rock Platform": createFloatingRockPlatform, "Rubble Pile": createRubblePile, "Fence Post": createFencePost, "Rooftop AC Unit": createRooftopACUnit, | |
| "Holographic Window Display": createHolographicWindowDisplay, "Jersey Barrier": createJerseyBarrier, "Oil Drum": createOilDrum, "Canned Food": createCannedFood, | |
| "Treasure Chest": createTreasureChest, "Wall Torch": createWallTorch, "Bone Pile": createBonePile, "King Figure": createKingFigure, "Soldier Figure": createSoldierFigure, | |
| "Mage Figure": createMageFigure, "Zombie Figure": createZombieFigure, "Survivor Figure": createSurvivorFigure, "Dwarf Miner Figure": createDwarfMinerFigure, | |
| "Undead Knight Figure": createUndeadKnightFigure, "Hero Figure": createHeroFigure, "Wooden Cart": createWoodenCart, "Ballista": createBallista, "Siege Tower": createSiegeTower, | |
| "Buggy Frame": createBuggyFrame, "Motorbike": createMotorbike, "Hover Bike": createHoverBike, "APC": createAPC, "Sand Boat": createSandBoat, "Makeshift Machete": createMakeshiftMachete, | |
| "Pistol Body": createPistolBody, "Scope Attachment": createScopeAttachment, "Laser Pistol": createLaserPistol, "Energy Sword": createEnergySword, "Dwarven Axe": createDwarvenAxe, | |
| "Magic Staff": createMagicStaff, "Candle Flame": createCandleFlame, "Dust Cloud": createDustCloud, "Blood Splat Decal": createBloodSplatDecal, | |
| "Burning Barrel Fire": createBurningBarrelFire, "Warp Tunnel Effect": createWarpTunnelEffect, "Laser Beam": createLaserBeam, "Gold Sparkle": createGoldSparkle, "Steam Vent": createSteamVent | |
| }); | |
| } | |
| function populateObjectSelector() { | |
| const options = ["None", ...Object.keys(objectFactory)]; | |
| options.forEach(name => { | |
| const option = document.createElement('option'); | |
| option.value = name; | |
| option.textContent = name; | |
| objectSelect.appendChild(option); | |
| }); | |
| } | |
| // --- All the create... functions from the original file go here --- | |
| function createFencePost(){const b=createObjectBase("Fence Post"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(.2,1.5,.2),a);return Object.assign(c,b),c.position.y=.75,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createCyberpunkWallPanel(){const b=createObjectBase("Cyberpunk Wall Panel"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,3,.2),a);return Object.assign(c,b),c.position.y=1.5,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createModularHabBlock(){const b=createObjectBase("Modular Hab Block"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:6710886,roughness:.7}),d=new THREE.Mesh(new THREE.BoxGeometry(3,2,3),c);return d.position.y=1,d.castShadow=!0,d.receiveShadow=!0,a.add(d),a} | |
| function createMegaCorpSkyscraper(){const b=createObjectBase("MegaCorp Skyscraper"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:3355545,roughness:.4,metalness:.9}),d=new THREE.Mesh(new THREE.BoxGeometry(4,10,4),c);return d.position.y=5,d.castShadow=!0,d.receiveShadow=!0,a.add(d),a} | |
| function createCastleWallSection(){const b=createObjectBase("Castle Wall Section"),a=new THREE.MeshStandardMaterial({color:8421504,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(5,3,1),a);return Object.assign(c,b),c.position.y=1.5,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createWoodenDoor(){const b=createObjectBase("Wooden Door"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(1,2,.2),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createHouseRoofSection(){const b=createObjectBase("House Roof Section"),a=new THREE.MeshStandardMaterial({color:8388608,roughness:.7}),c=new THREE.Mesh(new THREE.ConeGeometry(2,1,4),a);return Object.assign(c,b),c.position.y=1,c.rotation.y=Math.PI/4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createConcreteBunkerWall(){const b=createObjectBase("Concrete Bunker Wall"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(4,2,1),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createDamagedHouseFacade(){const b=createObjectBase("Damaged House Facade"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(3,2,.3),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createPineTree(){const c=createObjectBase("Pine Tree"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:9127187,roughness:.9}),b=new THREE.MeshStandardMaterial({color:25600,roughness:.8}),e=new THREE.Mesh(new THREE.CylinderGeometry(.2,.3,1.5,8),d);e.position.y=.75,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.ConeGeometry(1,2,8),b);return f.position.y=2,f.castShadow=!0,f.receiveShadow=!0,a.add(f),a} | |
| function createBoulder(){const b=createObjectBase("Boulder"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.8}),c=new THREE.Mesh(new THREE.IcosahedronGeometry(1,0),a);return Object.assign(c,b),c.position.y=.5,c.rotation.set(Math.random()*Math.PI,Math.random()*Math.PI,0),c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createAlienPlant(){const c=createObjectBase("Alien Plant"),a=new THREE.Group;Object.assign(a,c);const b=new THREE.MeshStandardMaterial({color:65280,roughness:.7}),d=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,1,8),b);d.position.y=.5,d.castShadow=!0,d.receiveShadow=!0,a.add(d);const e=new THREE.Mesh(new THREE.SphereGeometry(.3,8,8),b);return e.position.y=1,e.castShadow=!0,e.receiveShadow=!0,a.add(e),a} | |
| function createFloatingRockPlatform(){const b=createObjectBase("Floating Rock Platform"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(3,.5,3),a);return Object.assign(c,b),c.position.y=2,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createRubblePile(){const b=createObjectBase("Rubble Pile"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:7829367,roughness:.9});for(let d=0;d<5;d++){const e=new THREE.Mesh(new THREE.IcosahedronGeometry(.3,0),c);e.position.set(Math.random()*.5-.25,Math.random()*.3,Math.random()*.5-.25),e.castShadow=!0,e.receiveShadow=!0,a.add(e)}return a} | |
| function createRooftopACUnit(){const b=createObjectBase("Rooftop AC Unit"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(1,.8,1),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createHolographicWindowDisplay(){const b=createObjectBase("Holographic Window Display"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,transparent:!0,opacity:.7}),c=new THREE.Mesh(new THREE.PlaneGeometry(1,1),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!1,c.receiveShadow=!1,c} | |
| function createJerseyBarrier(){const b=createObjectBase("Jersey Barrier"),a=new THREE.MeshStandardMaterial({color:8947848,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.8,.5),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createOilDrum(){const b=createObjectBase("Oil Drum"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.3}),c=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,1,12),a);return Object.assign(c,b),c.position.y=.5,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createCannedFood(){const b=createObjectBase("Canned Food"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,.2,12),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createTreasureChest(){const b=createObjectBase("Treasure Chest"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1,.6,.8),a);return Object.assign(c,b),c.position.y=.3,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createWallTorch(){const c=createObjectBase("Wall Torch"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:14596231,roughness:.9}),b=new THREE.MeshStandardMaterial({color:16729344,roughness:.5}),e=new THREE.Mesh(new THREE.BoxGeometry(.1,.5,.1),d);e.position.y=.25,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),b);return f.position.y=.5,f.castShadow=!0,f.receiveShadow=!1,a.add(f),a} | |
| function createBonePile(){const b=createObjectBase("Bone Pile"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:14540253,roughness:.9});for(let d=0;d<3;d++){const e=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,.3,8),c);e.position.set(Math.random()*.2-.1,Math.random()*.1,Math.random()*.2-.1),e.rotation.set(Math.random()*Math.PI,Math.random()*Math.PI,Math.random()*Math.PI),e.castShadow=!0,e.receiveShadow=!0,a.add(e)}return a} | |
| function createKingFigure(){const b=createObjectBase("King Figure"),a=new THREE.MeshStandardMaterial({color:16766720,roughness:.6}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createSoldierFigure(){const b=createObjectBase("Soldier Figure"),a=new THREE.MeshStandardMaterial({color:2263842,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createMageFigure(){const b=createObjectBase("Mage Figure"),a=new THREE.MeshStandardMaterial({color:8388736,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createZombieFigure(){const b=createObjectBase("Zombie Figure"),a=new THREE.MeshStandardMaterial({color:9498256,roughness:.8}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createSurvivorFigure(){const b=createObjectBase("Survivor Figure"),a=new THREE.MeshStandardMaterial({color:4620980,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createDwarfMinerFigure(){const b=createObjectBase("Dwarf Miner Figure"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.5,4,8),a);return Object.assign(c,b),c.position.y=.35,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createUndeadKnightFigure(){const b=createObjectBase("Undead Knight Figure"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.8}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createHeroFigure(){const b=createObjectBase("Hero Figure"),a=new THREE.MeshStandardMaterial({color:16711680,roughness:.6}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createWoodenCart(){const b=createObjectBase("Wooden Cart"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.8,1),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createBallista(){const b=createObjectBase("Ballista"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.8,.5),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createSiegeTower(){const b=createObjectBase("Siege Tower"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,4,2),a);return Object.assign(c,b),c.position.y=2,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createBuggyFrame(){const b=createObjectBase("Buggy Frame"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.5,1),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createMotorbike(){const b=createObjectBase("Motorbike"),a=new THREE.MeshStandardMaterial({color:3355443,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.6,.4),a);return Object.assign(c,b),c.position.y=.3,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createHoverBike(){const b=createObjectBase("Hover Bike"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.4,.4),a);return Object.assign(c,b),c.position.y=.5,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createAPC(){const b=createObjectBase("APC"),a=new THREE.MeshStandardMaterial({color:3099951,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(3,1.5,1.5),a);return Object.assign(c,b),c.position.y=.75,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createSandBoat(){const b=createObjectBase("Sand Boat"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.5,1),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createMakeshiftMachete(){const b=createObjectBase("Makeshift Machete"),a=new THREE.MeshStandardMaterial({color:7829367,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.8,.05,.2),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createPistolBody(){const b=createObjectBase("Pistol Body"),a=new THREE.MeshStandardMaterial({color:3355443,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.2,.3),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createScopeAttachment(){const b=createObjectBase("Scope Attachment"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,.3,8),a);return Object.assign(c,b),c.position.y=.15,c.rotation.z=Math.PI/2,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createLaserPistol(){const b=createObjectBase("Laser Pistol"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.2,.3),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createEnergySword(){const b=createObjectBase("Energy Sword"),a=new THREE.MeshStandardMaterial({color:65280,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(.8,.05,.2),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createDwarvenAxe(){const b=createObjectBase("Dwarven Axe"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.3,.1),a);return Object.assign(c,b),c.position.y=.15,c.castShadow=!0,c.receiveShadow=!0,c} | |
| function createMagicStaff(){const c=createObjectBase("Magic Staff"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),b=new THREE.MeshStandardMaterial({color:16711935,roughness:.5}),e=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,1.5,8),d);e.position.y=.75,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),b);return f.position.y=1.5,f.castShadow=!0,f.receiveShadow=!0,a.add(f),a} | |
| function createCandleFlame(){const b=createObjectBase("Candle Flame"),a=new THREE.MeshStandardMaterial({color:16729344,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!1,c} | |
| function createDustCloud(){const b=createObjectBase("Dust Cloud"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9,transparent:!0,opacity:.5}),c=new THREE.Mesh(new THREE.SphereGeometry(.5,8,8),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!1,c.receiveShadow=!1,c} | |
| function createBloodSplatDecal(){const b=createObjectBase("Blood Splat Decal"),a=new THREE.MeshStandardMaterial({color:9109504,roughness:.9,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.PlaneGeometry(.5,.5),a);return Object.assign(c,b),c.position.y=.01,c.rotation.x=-Math.PI/2,c.castShadow=!1,c.receiveShadow=!0,c} | |
| function createBurningBarrelFire(){const c=createObjectBase("Burning Barrel Fire"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),b=new THREE.MeshStandardMaterial({color:16729344,roughness:.5,transparent:!0,opacity:.8}),e=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,1,12),d);e.position.y=.5,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.3,8,8),b);return f.position.y=1,f.castShadow=!0,f.receiveShadow=!1,a.add(f),a} | |
| function createWarpTunnelEffect(){const b=createObjectBase("Warp Tunnel Effect"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,transparent:!0,opacity:.7}),c=new THREE.Mesh(new THREE.CylinderGeometry(.5,.5,2,12),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!1,c.receiveShadow=!1,c} | |
| function createLaserBeam(){const b=createObjectBase("Laser Beam"),a=new THREE.MeshStandardMaterial({color:16711680,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,1,8),a);return Object.assign(c,b),c.position.y=.5,c.rotation.z=Math.PI/2,c.castShadow=!1,c.receiveShadow=!1,c} | |
| function createGoldSparkle(){const b=createObjectBase("Gold Sparkle"),a=new THREE.MeshStandardMaterial({color:16766720,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!1,c.receiveShadow=!1,c} | |
| function createSteamVent(){const b=createObjectBase("Steam Vent"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9,transparent:!0,opacity:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,.5,8),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!1,c.receiveShadow=!1,c} | |
| // --- Start the application --- | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |