Spaces:
Running
Running
File size: 44,037 Bytes
27e5fda 2c71111 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 |
<!DOCTYPE html>
<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>
|