Spaces:
Running
Running
Update index.html
Browse files- index.html +678 -18
index.html
CHANGED
@@ -1,19 +1,679 @@
|
|
1 |
-
<!
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
</html>
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Three.js Infinite World Builder</title>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
margin: 0;
|
10 |
+
overflow: hidden;
|
11 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
12 |
+
background-color: #333;
|
13 |
+
}
|
14 |
+
canvas {
|
15 |
+
display: block;
|
16 |
+
}
|
17 |
+
#ui-container {
|
18 |
+
position: absolute;
|
19 |
+
top: 10px;
|
20 |
+
left: 10px;
|
21 |
+
z-index: 10;
|
22 |
+
background: rgba(0,0,0,0.6);
|
23 |
+
padding: 10px;
|
24 |
+
border-radius: 8px;
|
25 |
+
display: flex;
|
26 |
+
flex-direction: column;
|
27 |
+
gap: 10px;
|
28 |
+
color: white;
|
29 |
+
max-width: 250px;
|
30 |
+
}
|
31 |
+
.ui-row {
|
32 |
+
display: flex;
|
33 |
+
justify-content: space-between;
|
34 |
+
align-items: center;
|
35 |
+
gap: 10px;
|
36 |
+
}
|
37 |
+
.ui-row label {
|
38 |
+
white-space: nowrap;
|
39 |
+
}
|
40 |
+
button, select, input[type="number"] {
|
41 |
+
background-color: #555;
|
42 |
+
color: white;
|
43 |
+
border: 1px solid #777;
|
44 |
+
padding: 8px;
|
45 |
+
border-radius: 5px;
|
46 |
+
cursor: pointer;
|
47 |
+
width: 100%;
|
48 |
+
box-sizing: border-box;
|
49 |
+
}
|
50 |
+
button:hover {
|
51 |
+
background-color: #666;
|
52 |
+
}
|
53 |
+
select, input[type="number"] {
|
54 |
+
-webkit-appearance: none;
|
55 |
+
-moz-appearance: none;
|
56 |
+
appearance: none;
|
57 |
+
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');
|
58 |
+
background-repeat: no-repeat;
|
59 |
+
background-position: right 10px top 50%;
|
60 |
+
background-size: .65em auto;
|
61 |
+
padding-right: 2em;
|
62 |
+
}
|
63 |
+
</style>
|
64 |
+
</head>
|
65 |
+
<body>
|
66 |
+
<!-- UI Elements for User Interaction -->
|
67 |
+
<div id="ui-container">
|
68 |
+
<div class="ui-row">
|
69 |
+
<button id="newWorldButton">New World</button>
|
70 |
+
</div>
|
71 |
+
<div class="ui-row">
|
72 |
+
<button id="loadWorldButton">Load World</button>
|
73 |
+
<input type="file" id="loadWorldInput" accept=".json" style="display: none;">
|
74 |
+
</div>
|
75 |
+
<div class="ui-row">
|
76 |
+
<button id="saveWorldButton">Save World</button>
|
77 |
+
</div>
|
78 |
+
<hr style="border-color: #555; width:100%;">
|
79 |
+
<div class="ui-row">
|
80 |
+
<label for="objectSelect">Object:</label>
|
81 |
+
<select id="objectSelect"></select>
|
82 |
+
</div>
|
83 |
+
<div class="ui-row">
|
84 |
+
<label for="scaleInput">Scale:</label>
|
85 |
+
<input type="number" id="scaleInput" value="1.0" step="0.1" min="0.1">
|
86 |
+
</div>
|
87 |
+
<div class="ui-row">
|
88 |
+
<label for="rotationInput">Rotation (Y):</label>
|
89 |
+
<input type="number" id="rotationInput" value="0" step="15" min="0" max="345">
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
+
|
93 |
+
<!-- Import map for Three.js -->
|
94 |
+
<script type="importmap">
|
95 |
+
{
|
96 |
+
"imports": {
|
97 |
+
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
|
98 |
+
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
|
99 |
+
}
|
100 |
+
}
|
101 |
+
</script>
|
102 |
+
|
103 |
+
<!-- Main Application Logic -->
|
104 |
+
<script type="module">
|
105 |
+
import * as THREE from 'three';
|
106 |
+
|
107 |
+
// --- Core Scene Variables ---
|
108 |
+
let scene, camera, renderer, playerMesh;
|
109 |
+
let raycaster, mouse;
|
110 |
+
const keysPressed = {};
|
111 |
+
const playerSpeed = 0.15;
|
112 |
+
|
113 |
+
// --- World State Management ---
|
114 |
+
let worldObjects = []; // This is the single source of truth for all placed objects
|
115 |
+
const groundMeshes = {}; // Store ground mesh references by grid key
|
116 |
+
|
117 |
+
// --- World Configuration ---
|
118 |
+
const PLOT_WIDTH = 50.0;
|
119 |
+
const PLOT_DEPTH = 50.0;
|
120 |
+
|
121 |
+
// --- Materials ---
|
122 |
+
const groundMaterial = new THREE.MeshStandardMaterial({
|
123 |
+
color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide
|
124 |
+
});
|
125 |
+
|
126 |
+
// --- Object Creation Map ---
|
127 |
+
const objectFactory = {};
|
128 |
+
|
129 |
+
// --- UI Elements ---
|
130 |
+
const newWorldButton = document.getElementById('newWorldButton');
|
131 |
+
const saveWorldButton = document.getElementById('saveWorldButton');
|
132 |
+
const loadWorldButton = document.getElementById('loadWorldButton');
|
133 |
+
const loadWorldInput = document.getElementById('loadWorldInput');
|
134 |
+
const objectSelect = document.getElementById('objectSelect');
|
135 |
+
const scaleInput = document.getElementById('scaleInput');
|
136 |
+
const rotationInput = document.getElementById('rotationInput');
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Initializes the entire application.
|
140 |
+
*/
|
141 |
+
function init() {
|
142 |
+
scene = new THREE.Scene();
|
143 |
+
scene.background = new THREE.Color(0xabcdef);
|
144 |
+
|
145 |
+
const aspect = window.innerWidth / window.innerHeight;
|
146 |
+
camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 4000);
|
147 |
+
camera.position.set(0, 15, 20);
|
148 |
+
camera.lookAt(0, 0, 0);
|
149 |
+
scene.add(camera);
|
150 |
+
|
151 |
+
setupLighting();
|
152 |
+
setupPlayer();
|
153 |
+
populateObjectFactory();
|
154 |
+
populateObjectSelector();
|
155 |
+
|
156 |
+
raycaster = new THREE.Raycaster();
|
157 |
+
mouse = new THREE.Vector2();
|
158 |
+
|
159 |
+
renderer = new THREE.WebGLRenderer({ antialias: true });
|
160 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
161 |
+
renderer.shadowMap.enabled = true;
|
162 |
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
163 |
+
document.body.appendChild(renderer.domElement);
|
164 |
+
|
165 |
+
addEventListeners();
|
166 |
+
|
167 |
+
resetWorld(); // Start with a fresh world
|
168 |
+
|
169 |
+
console.log("Three.js Initialized. World ready.");
|
170 |
+
animate();
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Sets up lights for the scene.
|
175 |
+
*/
|
176 |
+
function setupLighting() {
|
177 |
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
178 |
+
scene.add(ambientLight);
|
179 |
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
180 |
+
directionalLight.position.set(50, 150, 100);
|
181 |
+
directionalLight.castShadow = true;
|
182 |
+
directionalLight.shadow.mapSize.width = 4096;
|
183 |
+
directionalLight.shadow.mapSize.height = 4096;
|
184 |
+
directionalLight.shadow.camera.near = 0.5;
|
185 |
+
directionalLight.shadow.camera.far = 500;
|
186 |
+
directionalLight.shadow.camera.left = -150;
|
187 |
+
directionalLight.shadow.camera.right = 150;
|
188 |
+
directionalLight.shadow.camera.top = 150;
|
189 |
+
directionalLight.shadow.camera.bottom = -150;
|
190 |
+
directionalLight.shadow.bias = -0.001;
|
191 |
+
scene.add(directionalLight);
|
192 |
+
}
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Creates the player representation in the scene.
|
196 |
+
*/
|
197 |
+
function setupPlayer() {
|
198 |
+
const playerGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
|
199 |
+
const playerMat = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: 0.6 });
|
200 |
+
playerMesh = new THREE.Mesh(playerGeo, playerMat);
|
201 |
+
playerMesh.castShadow = true;
|
202 |
+
playerMesh.receiveShadow = true;
|
203 |
+
scene.add(playerMesh);
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Clears the world and resets the state to a default.
|
208 |
+
*/
|
209 |
+
function resetWorld() {
|
210 |
+
console.log("Creating a new world.");
|
211 |
+
// Clear existing objects
|
212 |
+
worldObjects.forEach(objData => {
|
213 |
+
const object = scene.getObjectByProperty('uuid', objData.uuid);
|
214 |
+
if (object) {
|
215 |
+
scene.remove(object);
|
216 |
+
}
|
217 |
+
});
|
218 |
+
worldObjects = [];
|
219 |
+
|
220 |
+
// Clear ground planes
|
221 |
+
for (const key in groundMeshes) {
|
222 |
+
scene.remove(groundMeshes[key]);
|
223 |
+
delete groundMeshes[key];
|
224 |
+
}
|
225 |
+
|
226 |
+
// Create the initial ground plane at (0,0)
|
227 |
+
createGroundPlane(0, 0);
|
228 |
+
|
229 |
+
// Reset player position
|
230 |
+
playerMesh.position.set(PLOT_WIDTH / 2, 0.4 + 0.8 / 2, PLOT_DEPTH / 2);
|
231 |
+
updateCamera(true); // Force camera snap
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Populates the world based on data from a loaded file.
|
236 |
+
* @param {object} worldData - The parsed JSON data from a world file.
|
237 |
+
*/
|
238 |
+
function populateWorld(worldData) {
|
239 |
+
console.log("Loading world from file...");
|
240 |
+
resetWorld(); // Ensure the scene is clean before loading
|
241 |
+
|
242 |
+
if (worldData.objects && Array.isArray(worldData.objects)) {
|
243 |
+
worldData.objects.forEach(objData => {
|
244 |
+
// Create and place the object in the scene
|
245 |
+
const newObject = createObject(objData.type);
|
246 |
+
if (newObject) {
|
247 |
+
newObject.position.copy(objData.position);
|
248 |
+
newObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order);
|
249 |
+
newObject.scale.copy(objData.scale);
|
250 |
+
newObject.userData.obj_id = objData.obj_id; // Preserve original ID
|
251 |
+
scene.add(newObject);
|
252 |
+
|
253 |
+
// Add its data to our source of truth, including its new scene UUID
|
254 |
+
const newObjData = { ...objData, uuid: newObject.uuid };
|
255 |
+
worldObjects.push(newObjData);
|
256 |
+
}
|
257 |
+
});
|
258 |
+
}
|
259 |
+
|
260 |
+
// Generate all necessary ground planes for the loaded objects
|
261 |
+
generateGroundForLoadedWorld();
|
262 |
+
|
263 |
+
// Set player position if it exists in the save file
|
264 |
+
if (worldData.playerPosition) {
|
265 |
+
playerMesh.position.copy(worldData.playerPosition);
|
266 |
+
}
|
267 |
+
|
268 |
+
updateCamera(true); // Snap camera to new position
|
269 |
+
console.log(`Loaded ${worldObjects.length} objects.`);
|
270 |
+
}
|
271 |
+
|
272 |
+
/**
|
273 |
+
* Creates a ground plane at a specific grid coordinate.
|
274 |
+
* @param {number} gridX - The x-coordinate in the grid.
|
275 |
+
* @param {number} gridZ - The z-coordinate in the grid.
|
276 |
+
*/
|
277 |
+
function createGroundPlane(gridX, gridZ) {
|
278 |
+
const gridKey = `${gridX}_${gridZ}`;
|
279 |
+
if (groundMeshes[gridKey]) return; // Don't create if it already exists
|
280 |
+
|
281 |
+
console.log(`Creating ground at ${gridX}, ${gridZ}`);
|
282 |
+
const groundGeometry = new THREE.PlaneGeometry(PLOT_WIDTH, PLOT_DEPTH);
|
283 |
+
const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
|
284 |
+
groundMesh.rotation.x = -Math.PI / 2;
|
285 |
+
groundMesh.position.y = -0.05;
|
286 |
+
groundMesh.position.x = gridX * PLOT_WIDTH + PLOT_WIDTH / 2.0;
|
287 |
+
groundMesh.position.z = gridZ * PLOT_DEPTH + PLOT_DEPTH / 2.0;
|
288 |
+
groundMesh.receiveShadow = true;
|
289 |
+
groundMesh.userData.gridKey = gridKey;
|
290 |
+
scene.add(groundMesh);
|
291 |
+
groundMeshes[gridKey] = groundMesh;
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* After loading a world, this ensures all necessary ground tiles are created.
|
296 |
+
*/
|
297 |
+
function generateGroundForLoadedWorld() {
|
298 |
+
const requiredGrids = new Set();
|
299 |
+
worldObjects.forEach(objData => {
|
300 |
+
const gridX = Math.floor(objData.position.x / PLOT_WIDTH);
|
301 |
+
const gridZ = Math.floor(objData.position.z / PLOT_DEPTH);
|
302 |
+
requiredGrids.add(`${gridX}_${gridZ}`);
|
303 |
+
});
|
304 |
+
|
305 |
+
// Also add grid for player
|
306 |
+
const playerGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH);
|
307 |
+
const playerGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH);
|
308 |
+
requiredGrids.add(`${playerGridX}_${playerGridZ}`);
|
309 |
+
|
310 |
+
requiredGrids.forEach(key => {
|
311 |
+
const [gridX, gridZ] = key.split('_').map(Number);
|
312 |
+
createGroundPlane(gridX, gridZ);
|
313 |
+
});
|
314 |
+
}
|
315 |
+
|
316 |
+
/**
|
317 |
+
* Creates a generic base for any object, assigning a type and unique ID.
|
318 |
+
* @param {string} type - The type name of the object.
|
319 |
+
* @returns {object} - An object with userData containing type and a new UUID.
|
320 |
+
*/
|
321 |
+
function createObjectBase(type) {
|
322 |
+
return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
|
323 |
+
}
|
324 |
+
|
325 |
+
/**
|
326 |
+
* Central function to create a 3D object based on its type string.
|
327 |
+
* @param {string} type - The type of object to create.
|
328 |
+
* @returns {THREE.Object3D|null} The created Three.js object or null if type is unknown.
|
329 |
+
*/
|
330 |
+
function createObject(type) {
|
331 |
+
if (objectFactory[type]) {
|
332 |
+
return objectFactory[type]();
|
333 |
+
}
|
334 |
+
console.warn("Unknown object type:", type);
|
335 |
+
return null;
|
336 |
+
}
|
337 |
+
|
338 |
+
// --- All Object Creation Functions ---
|
339 |
+
// (Abridged for brevity, the full list is included below this script block)
|
340 |
+
function createSimpleHouse() {
|
341 |
+
const base = createObjectBase("Simple House");
|
342 |
+
const group = new THREE.Group();
|
343 |
+
Object.assign(group, base);
|
344 |
+
const mat1 = new THREE.MeshStandardMaterial({color:0xffccaa, roughness:0.8});
|
345 |
+
const mat2 = new THREE.MeshStandardMaterial({color:0xaa5533, roughness:0.7});
|
346 |
+
const m1 = new THREE.Mesh(new THREE.BoxGeometry(2,1.5,2.5), mat1);
|
347 |
+
m1.position.y = 1.5/2;
|
348 |
+
m1.castShadow = true; m1.receiveShadow = true;
|
349 |
+
group.add(m1);
|
350 |
+
const m2 = new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4), mat2);
|
351 |
+
m2.position.y = 1.5+1/2;
|
352 |
+
m2.rotation.y = Math.PI/4;
|
353 |
+
m2.castShadow = true; m2.receiveShadow = true;
|
354 |
+
group.add(m2);
|
355 |
+
return group;
|
356 |
+
}
|
357 |
+
|
358 |
+
function createTree() {
|
359 |
+
const base = createObjectBase("Tree");
|
360 |
+
const group = new THREE.Group();
|
361 |
+
Object.assign(group, base);
|
362 |
+
const mat1 = new THREE.MeshStandardMaterial({color:0x8B4513, roughness:0.9});
|
363 |
+
const mat2 = new THREE.MeshStandardMaterial({color:0x228B22, roughness:0.8});
|
364 |
+
const m1 = new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,2,8), mat1);
|
365 |
+
m1.position.y = 1;
|
366 |
+
m1.castShadow = true; m1.receiveShadow = true;
|
367 |
+
group.add(m1);
|
368 |
+
const m2 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2,0), mat2);
|
369 |
+
m2.position.y = 2.8;
|
370 |
+
m2.castShadow = true; m2.receiveShadow = true;
|
371 |
+
group.add(m2);
|
372 |
+
return group;
|
373 |
+
}
|
374 |
+
|
375 |
+
function createRock() {
|
376 |
+
const base = createObjectBase("Rock");
|
377 |
+
const mat = new THREE.MeshStandardMaterial({color:0xaaaaaa, roughness:0.8, metalness:0.1});
|
378 |
+
const rock = new THREE.Mesh(new THREE.IcosahedronGeometry(0.7,0), mat);
|
379 |
+
Object.assign(rock, base);
|
380 |
+
rock.position.y = 0.35;
|
381 |
+
rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
|
382 |
+
rock.castShadow = true; rock.receiveShadow = true;
|
383 |
+
return rock;
|
384 |
+
}
|
385 |
+
|
386 |
+
// --- File and State Management ---
|
387 |
+
|
388 |
+
/**
|
389 |
+
* Gathers all world data and triggers a download of the world file.
|
390 |
+
*/
|
391 |
+
function saveWorldToFile() {
|
392 |
+
console.log("Saving world to file...");
|
393 |
+
const dataToSave = {
|
394 |
+
meta: {
|
395 |
+
version: "1.0",
|
396 |
+
plotWidth: PLOT_WIDTH,
|
397 |
+
plotDepth: PLOT_DEPTH,
|
398 |
+
savedAt: new Date().toISOString()
|
399 |
+
},
|
400 |
+
playerPosition: playerMesh.position.clone(),
|
401 |
+
objects: worldObjects.map(objData => {
|
402 |
+
// We only need to store the data, not the live scene object reference
|
403 |
+
const { uuid, ...rest } = objData;
|
404 |
+
return rest;
|
405 |
+
})
|
406 |
+
};
|
407 |
+
|
408 |
+
const jsonString = JSON.stringify(dataToSave, null, 2);
|
409 |
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
410 |
+
const url = URL.createObjectURL(blob);
|
411 |
+
|
412 |
+
const a = document.createElement('a');
|
413 |
+
a.href = url;
|
414 |
+
a.download = `world_${Date.now()}.json`;
|
415 |
+
document.body.appendChild(a);
|
416 |
+
a.click();
|
417 |
+
document.body.removeChild(a);
|
418 |
+
URL.revokeObjectURL(url);
|
419 |
+
console.log(`World saved with ${dataToSave.objects.length} objects.`);
|
420 |
+
}
|
421 |
+
|
422 |
+
/**
|
423 |
+
* Handles the file selection for loading a world.
|
424 |
+
* @param {Event} event - The file input change event.
|
425 |
+
*/
|
426 |
+
function handleWorldFileLoad(event) {
|
427 |
+
const file = event.target.files[0];
|
428 |
+
if (!file) return;
|
429 |
+
|
430 |
+
const reader = new FileReader();
|
431 |
+
reader.onload = (e) => {
|
432 |
+
try {
|
433 |
+
const worldData = JSON.parse(e.target.result);
|
434 |
+
populateWorld(worldData);
|
435 |
+
} catch (error) {
|
436 |
+
console.error("Error parsing world file:", error);
|
437 |
+
alert("Failed to load world file. It might be corrupted.");
|
438 |
+
}
|
439 |
+
};
|
440 |
+
reader.readAsText(file);
|
441 |
+
}
|
442 |
+
|
443 |
+
// --- Event Handlers ---
|
444 |
+
|
445 |
+
function addEventListeners() {
|
446 |
+
document.addEventListener('mousemove', onMouseMove, false);
|
447 |
+
document.addEventListener('click', onDocumentClick, false);
|
448 |
+
window.addEventListener('resize', onWindowResize, false);
|
449 |
+
document.addEventListener('keydown', onKeyDown);
|
450 |
+
document.addEventListener('keyup', onKeyUp);
|
451 |
+
|
452 |
+
newWorldButton.addEventListener('click', () => {
|
453 |
+
if (confirm('Are you sure you want to start a new world? Any unsaved changes will be lost.')) {
|
454 |
+
resetWorld();
|
455 |
+
}
|
456 |
+
});
|
457 |
+
saveWorldButton.addEventListener('click', saveWorldToFile);
|
458 |
+
loadWorldButton.addEventListener('click', () => loadWorldInput.click());
|
459 |
+
loadWorldInput.addEventListener('change', handleWorldFileLoad);
|
460 |
+
}
|
461 |
+
|
462 |
+
function onMouseMove(event) {
|
463 |
+
// Adjust for UI offset if mouse is over it
|
464 |
+
const uiRect = document.getElementById('ui-container').getBoundingClientRect();
|
465 |
+
if (event.clientX < uiRect.right && event.clientY < uiRect.bottom) {
|
466 |
+
mouse.x = -99; // Prevent placement when mouse is over UI
|
467 |
+
mouse.y = -99;
|
468 |
+
return;
|
469 |
+
}
|
470 |
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
471 |
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
472 |
+
}
|
473 |
+
|
474 |
+
function onDocumentClick(event) {
|
475 |
+
if (mouse.x === -99) return; // Don't place if mouse was over UI
|
476 |
+
|
477 |
+
const selectedObjectType = objectSelect.value;
|
478 |
+
if (selectedObjectType === "None") return;
|
479 |
+
|
480 |
+
const groundCandidates = Object.values(groundMeshes);
|
481 |
+
if (groundCandidates.length === 0) return;
|
482 |
+
|
483 |
+
raycaster.setFromCamera(mouse, camera);
|
484 |
+
const intersects = raycaster.intersectObjects(groundCandidates);
|
485 |
+
|
486 |
+
if (intersects.length > 0) {
|
487 |
+
const intersectPoint = intersects[0].point;
|
488 |
+
const newObject = createObject(selectedObjectType);
|
489 |
+
|
490 |
+
if (newObject) {
|
491 |
+
newObject.position.copy(intersectPoint);
|
492 |
+
newObject.position.y += 0.01; // Prevent z-fighting
|
493 |
+
|
494 |
+
const scale = parseFloat(scaleInput.value) || 1.0;
|
495 |
+
const rotationY = THREE.MathUtils.degToRad(parseFloat(rotationInput.value) || 0);
|
496 |
+
|
497 |
+
newObject.scale.setScalar(scale);
|
498 |
+
newObject.rotation.y = rotationY;
|
499 |
+
|
500 |
+
scene.add(newObject);
|
501 |
+
|
502 |
+
// Add the new object's data to our main array
|
503 |
+
const newObjectData = {
|
504 |
+
obj_id: newObject.userData.obj_id,
|
505 |
+
uuid: newObject.uuid, // Keep track of the live object in the scene
|
506 |
+
type: newObject.userData.type,
|
507 |
+
position: newObject.position.clone(),
|
508 |
+
rotation: { _x: newObject.rotation.x, _y: newObject.rotation.y, _z: newObject.rotation.z, _order: newObject.rotation.order },
|
509 |
+
scale: newObject.scale.clone()
|
510 |
+
};
|
511 |
+
worldObjects.push(newObjectData);
|
512 |
+
|
513 |
+
console.log(`Placed new ${selectedObjectType}. Total objects: ${worldObjects.length}`);
|
514 |
+
}
|
515 |
+
}
|
516 |
+
}
|
517 |
+
|
518 |
+
function onKeyDown(event) { keysPressed[event.code] = true; }
|
519 |
+
function onKeyUp(event) { keysPressed[event.code] = false; }
|
520 |
+
function onWindowResize() {
|
521 |
+
camera.aspect = window.innerWidth / window.innerHeight;
|
522 |
+
camera.updateProjectionMatrix();
|
523 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
524 |
+
}
|
525 |
+
|
526 |
+
// --- Game Loop and Updates ---
|
527 |
+
|
528 |
+
function updatePlayerMovement() {
|
529 |
+
if (!playerMesh) return;
|
530 |
+
const moveDirection = new THREE.Vector3(0, 0, 0);
|
531 |
+
if (keysPressed['KeyW'] || keysPressed['ArrowUp']) moveDirection.z -= 1;
|
532 |
+
if (keysPressed['KeyS'] || keysPressed['ArrowDown']) moveDirection.z += 1;
|
533 |
+
if (keysPressed['KeyA'] || keysPressed['ArrowLeft']) moveDirection.x -= 1;
|
534 |
+
if (keysPressed['KeyD'] || keysPressed['ArrowRight']) moveDirection.x += 1;
|
535 |
+
|
536 |
+
if (moveDirection.lengthSq() > 0) {
|
537 |
+
moveDirection.normalize().multiplyScalar(playerSpeed);
|
538 |
+
|
539 |
+
const forward = new THREE.Vector3();
|
540 |
+
camera.getWorldDirection(forward);
|
541 |
+
forward.y = 0;
|
542 |
+
forward.normalize();
|
543 |
+
|
544 |
+
const right = new THREE.Vector3().crossVectors(camera.up, forward).normalize();
|
545 |
+
|
546 |
+
const worldMove = new THREE.Vector3();
|
547 |
+
worldMove.add(forward.multiplyScalar(-moveDirection.z));
|
548 |
+
worldMove.add(right.multiplyScalar(-moveDirection.x));
|
549 |
+
worldMove.normalize().multiplyScalar(playerSpeed);
|
550 |
+
|
551 |
+
playerMesh.position.add(worldMove);
|
552 |
+
playerMesh.position.y = Math.max(playerMesh.position.y, 0.4 + 0.8/2);
|
553 |
+
|
554 |
+
checkAndExpandGround();
|
555 |
+
}
|
556 |
+
}
|
557 |
+
|
558 |
+
function checkAndExpandGround() {
|
559 |
+
if (!playerMesh) return;
|
560 |
+
const currentGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH);
|
561 |
+
const currentGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH);
|
562 |
+
|
563 |
+
// Check a 3x3 area around the player
|
564 |
+
for (let dx = -1; dx <= 1; dx++) {
|
565 |
+
for (let dz = -1; dz <= 1; dz++) {
|
566 |
+
createGroundPlane(currentGridX + dx, currentGridZ + dz);
|
567 |
+
}
|
568 |
+
}
|
569 |
+
}
|
570 |
+
|
571 |
+
function updateCamera(forceSnap = false) {
|
572 |
+
if (!playerMesh) return;
|
573 |
+
const offset = new THREE.Vector3(0, 15, 20);
|
574 |
+
const targetPosition = playerMesh.position.clone().add(offset);
|
575 |
+
|
576 |
+
if (forceSnap) {
|
577 |
+
camera.position.copy(targetPosition);
|
578 |
+
} else {
|
579 |
+
camera.position.lerp(targetPosition, 0.08);
|
580 |
+
}
|
581 |
+
|
582 |
+
camera.lookAt(playerMesh.position);
|
583 |
+
}
|
584 |
+
|
585 |
+
function animate() {
|
586 |
+
requestAnimationFrame(animate);
|
587 |
+
updatePlayerMovement();
|
588 |
+
updateCamera();
|
589 |
+
renderer.render(scene, camera);
|
590 |
+
}
|
591 |
+
|
592 |
+
// --- Full Object Factory ---
|
593 |
+
function populateObjectFactory() {
|
594 |
+
Object.assign(objectFactory, {
|
595 |
+
"Simple House": createSimpleHouse, "Cyberpunk Wall Panel": createCyberpunkWallPanel, "Modular Hab Block": createModularHabBlock, "MegaCorp Skyscraper": createMegaCorpSkyscraper,
|
596 |
+
"Castle Wall Section": createCastleWallSection, "Wooden Door": createWoodenDoor, "House Roof Section": createHouseRoofSection, "Concrete Bunker Wall": createConcreteBunkerWall,
|
597 |
+
"Damaged House Facade": createDamagedHouseFacade, "Tree": createTree, "Rock": createRock, "Pine Tree": createPineTree, "Boulder": createBoulder, "Alien Plant": createAlienPlant,
|
598 |
+
"Floating Rock Platform": createFloatingRockPlatform, "Rubble Pile": createRubblePile, "Fence Post": createFencePost, "Rooftop AC Unit": createRooftopACUnit,
|
599 |
+
"Holographic Window Display": createHolographicWindowDisplay, "Jersey Barrier": createJerseyBarrier, "Oil Drum": createOilDrum, "Canned Food": createCannedFood,
|
600 |
+
"Treasure Chest": createTreasureChest, "Wall Torch": createWallTorch, "Bone Pile": createBonePile, "King Figure": createKingFigure, "Soldier Figure": createSoldierFigure,
|
601 |
+
"Mage Figure": createMageFigure, "Zombie Figure": createZombieFigure, "Survivor Figure": createSurvivorFigure, "Dwarf Miner Figure": createDwarfMinerFigure,
|
602 |
+
"Undead Knight Figure": createUndeadKnightFigure, "Hero Figure": createHeroFigure, "Wooden Cart": createWoodenCart, "Ballista": createBallista, "Siege Tower": createSiegeTower,
|
603 |
+
"Buggy Frame": createBuggyFrame, "Motorbike": createMotorbike, "Hover Bike": createHoverBike, "APC": createAPC, "Sand Boat": createSandBoat, "Makeshift Machete": createMakeshiftMachete,
|
604 |
+
"Pistol Body": createPistolBody, "Scope Attachment": createScopeAttachment, "Laser Pistol": createLaserPistol, "Energy Sword": createEnergySword, "Dwarven Axe": createDwarvenAxe,
|
605 |
+
"Magic Staff": createMagicStaff, "Candle Flame": createCandleFlame, "Dust Cloud": createDustCloud, "Blood Splat Decal": createBloodSplatDecal,
|
606 |
+
"Burning Barrel Fire": createBurningBarrelFire, "Warp Tunnel Effect": createWarpTunnelEffect, "Laser Beam": createLaserBeam, "Gold Sparkle": createGoldSparkle, "Steam Vent": createSteamVent
|
607 |
+
});
|
608 |
+
}
|
609 |
+
|
610 |
+
function populateObjectSelector() {
|
611 |
+
const options = ["None", ...Object.keys(objectFactory)];
|
612 |
+
options.forEach(name => {
|
613 |
+
const option = document.createElement('option');
|
614 |
+
option.value = name;
|
615 |
+
option.textContent = name;
|
616 |
+
objectSelect.appendChild(option);
|
617 |
+
});
|
618 |
+
}
|
619 |
+
|
620 |
+
// --- All the create... functions from the original file go here ---
|
621 |
+
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}
|
622 |
+
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}
|
623 |
+
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}
|
624 |
+
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}
|
625 |
+
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}
|
626 |
+
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}
|
627 |
+
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}
|
628 |
+
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}
|
629 |
+
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}
|
630 |
+
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}
|
631 |
+
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}
|
632 |
+
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}
|
633 |
+
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}
|
634 |
+
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}
|
635 |
+
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}
|
636 |
+
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}
|
637 |
+
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}
|
638 |
+
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}
|
639 |
+
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}
|
640 |
+
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}
|
641 |
+
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}
|
642 |
+
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}
|
643 |
+
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}
|
644 |
+
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}
|
645 |
+
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}
|
646 |
+
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}
|
647 |
+
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}
|
648 |
+
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}
|
649 |
+
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}
|
650 |
+
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}
|
651 |
+
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}
|
652 |
+
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}
|
653 |
+
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}
|
654 |
+
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}
|
655 |
+
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}
|
656 |
+
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}
|
657 |
+
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}
|
658 |
+
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}
|
659 |
+
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}
|
660 |
+
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}
|
661 |
+
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}
|
662 |
+
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}
|
663 |
+
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}
|
664 |
+
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}
|
665 |
+
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}
|
666 |
+
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}
|
667 |
+
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}
|
668 |
+
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}
|
669 |
+
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}
|
670 |
+
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}
|
671 |
+
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}
|
672 |
+
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}
|
673 |
+
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}
|
674 |
+
|
675 |
+
// --- Start the application ---
|
676 |
+
init();
|
677 |
+
</script>
|
678 |
+
</body>
|
679 |
</html>
|