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/[email protected]/build/three.module.js", | |
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/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> | |