Single-shot · temp 0.7 · real votes · identical prompts·How we test →
Single-shot · temp 0.7 · real votes · identical prompts·How we test →
Create a 3D building and driving game in a single self-contained HTML file using Three.js. Tests an AI's ability to combine 3D graphics, game logic, and UI interactions on the web.
Create a three.js 3D game where I can place buildings of various designs and sizes, and drive through the town I've created. Add traffic to the road as well. In a single-page self-container HTML.
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
```
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
# 3D City Builder Game with Traffic
Here's a complete, self-contained HTML page for a Three.js city builder game where you can place buildings and drive through your city with traffic:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>City Builder Game</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.5);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
}
#building-selector {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
display: flex;
gap: 10px;
z-index: 100;
}
.building-btn {
padding: 8px 15px;
background: #444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.building-btn:hover {
background: #666;
}
.building-btn.active {
background: #0078ff;
}
#help {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.5);
color: white;
padding: 10px;
border-radius: 5px;
max-width: 250px;
z-index: 100;
}
</style>
</head>
<body>
<div id="ui">
<div>Buildings Placed: <span id="building-count">0</span></div>
<div>Current Mode: <span id="current-mode">Building</span></div>
</div>
<div id="building-selector">
<button class="building-btn" data-type="small">Small Building</button>
<button class="building-btn" data-type="medium">Medium Building</button>
<button class="building-btn" data-type="large">Large Building</button>
<button class="building-btn" data-type="skyscraper">Skyscraper</button>
<button class="building-btn" data-type="house">House</button>
<button class="building-btn" id="drive-btn">Drive Mode</button>
</div>
<div id="help">
<h3>Controls:</h3>
<p><strong>Building Mode:</strong><br>
- Click to place buildings<br>
- Scroll to rotate building</p>
<p><strong>Drive Mode:</strong><br>
- WASD to drive<br>
- Mouse to look around</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/PointerLockControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<script>
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 20);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
scene.add(directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(200, 200);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a5f0b,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Grid helper
const gridHelper = new THREE.GridHelper(200, 20, 0x555555, 0x333333);
scene.add(gridHelper);
// Roads
const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const roads = [];
// Main roads
const mainRoadWidth = 4;
const mainRoadLength = 200;
// Horizontal road
const horizontalRoadGeometry = new THREE.BoxGeometry(mainRoadLength, 0.1, mainRoadWidth);
const horizontalRoad = new THREE.Mesh(horizontalRoadGeometry, roadMaterial);
horizontalRoad.position.set(0, 0.01, 0);
horizontalRoad.receiveShadow = true;
scene.add(horizontalRoad);
roads.push(horizontalRoad);
// Vertical road
const verticalRoadGeometry = new THREE.BoxGeometry(mainRoadWidth, 0.1, mainRoadLength);
const verticalRoad = new THREE.Mesh(verticalRoadGeometry, roadMaterial);
verticalRoad.position.set(0, 0.01, 0);
verticalRoad.receiveShadow = true;
scene.add(verticalRoad);
roads.push(verticalRoad);
// Side roads
const sideRoadWidth = 2;
const sideRoadLength = 50;
// Add cross streets every 20 units
for (let i = -80; i <= 80; i += 20) {
if (i === 0) continue; // Skip center
// Horizontal side roads
const sideRoadH = new THREE.Mesh(
new THREE.BoxGeometry(sideRoadLength, 0.1, sideRoadWidth),
roadMaterial
);
sideRoadH.position.set(i, 0.01, 0);
sideRoadH.receiveShadow = true;
scene.add(sideRoadH);
roads.push(sideRoadH);
// Vertical side roads
const sideRoadV = new THREE.Mesh(
new THREE.BoxGeometry(sideRoadWidth, 0.1, sideRoadLength),
roadMaterial
);
sideRoadV.position.set(0, 0.01, i);
sideRoadV.receiveShadow = true;
scene.add(sideRoadV);
roads.push(sideRoadV);
}
// Road markings
const roadMarkingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
// Add center lines
const centerLineGeometry = new THREE.BoxGeometry(mainRoadLength, 0.11, 0.2);
const centerLine = new THREE.Mesh(centerLineGeometry, roadMarkingMaterial);
centerLine.position.set(0, 0.02, 0);
scene.add(centerLine);
const centerLineVGeometry = new THREE.BoxGeometry(0.2, 0.11, mainRoadLength);
const centerLineV = new THREE.Mesh(centerLineVGeometry, roadMarkingMaterial);
centerLineV.position.set(0, 0.02, 0);
scene.add(centerLineV);
// Add crosswalk markings
const crosswalkGeometry = new THREE.BoxGeometry(4, 0.11, 0.5);
for (let i = -2; i <= 2; i += 1) {
const crosswalkH = new THREE.Mesh(crosswalkGeometry, roadMarkingMaterial);
crosswalkH.position.set(i * 2, 0.02, 0);
scene.add(crosswalkH);
const crosswalkV = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 0.11, 4),
roadMarkingMaterial
);
crosswalkV.position.set(0, 0.02, i * 2);
scene.add(crosswalkV);
}
// Buildings
const buildings = [];
let currentBuildingType = 'small';
// Building materials
const buildingMaterials = {
wall: new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.7 }),
window: new THREE.MeshStandardMaterial({ color: 0x87CEEB, roughness: 0.1, metalness: 0.9 }),
roof: new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.8 })
};
// Create a preview building
let previewBuilding = createBuilding(currentBuildingType);
previewBuilding.visible = false;
scene.add(previewBuilding);
// Function to create different types of buildings
function createBuilding(type) {
const building = new THREE.Group();
switch(type) {
case 'small':
// Small office building
const smallBase = new THREE.Mesh(
new THREE.BoxGeometry(4, 4, 4),
buildingMaterials.wall
);
smallBase.castShadow = true;
smallBase.receiveShadow = true;
building.add(smallBase);
// Windows
for (let i = 0; i < 4; i++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.8, 0.1),
buildingMaterials.window
);
window.position.set(-1.5 + (i % 2) * 3, 1.5 - Math.floor(i / 2) * 2, 2.01);
building.add(window);
}
// Roof
const smallRoof = new THREE.Mesh(
new THREE.BoxGeometry(4.5, 0.5, 4.5),
buildingMaterials.roof
);
smallRoof.position.y = 4.25;
building.add(smallRoof);
break;
case 'medium':
// Medium apartment building
const mediumBase = new THREE.Mesh(
new THREE.BoxGeometry(6, 8, 6),
buildingMaterials.wall
);
mediumBase.castShadow = true;
mediumBase.receiveShadow = true;
building.add(mediumBase);
// Windows
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 3; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 0.1),
buildingMaterials.window
);
window.position.set(-2.5 + (i % 2) * 5, 3 - j * 3, 3.01);
building.add(window);
}
}
// Roof
const mediumRoof = new THREE.Mesh(
new THREE.BoxGeometry(6.5, 0.5, 6.5),
buildingMaterials.roof
);
mediumRoof.position.y = 8.25;
building.add(mediumRoof);
break;
case 'large':
// Large commercial building
const largeBase = new THREE.Mesh(
new THREE.BoxGeometry(10, 6, 10),
buildingMaterials.wall
);
largeBase.castShadow = true;
largeBase.receiveShadow = true;
building.add(largeBase);
// Windows
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 3; j++) {
for (let k = 0; k < 2; k++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1.5, 1.5, 0.1),
buildingMaterials.window
);
window.position.set(
-radek4 + i * 2,
2.5 - j * 2,
k === 0 ? 5.01 : -5.01
);
building.add(window);
}
}
}
// Roof
const largeRoof = new THREE.Mesh(
new THREE.ConeGeometry(7, 2, 4),
buildingMaterials.roof
);
largeRoof.position.y = 6;
largeRoof.rotation.y = Math.PI / 4;
building.add(largeRoof);
break;
case 'skyscraper':
// Skyscraper
const skyscraperBase = new THREE.Mesh(
new THREE.BoxGeometry(5, 20, 5),
buildingMaterials.wall
);
skyscraperBase.castShadow = true;
skyscraperBase.receiveShadow = true;
building.add(skyscraperBase);
// Windows
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 10; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 1.5, 0.1),
buildingMaterials.window
);
window.position.set(
-2 + (i % 2) * 4,
9 - j * 1.8,
2.51
);
building.add(window);
}
}
// Roof
const skyscraperRoof = new THREE.Mesh(
new THREE.ConeGeometry(3, 3, 4),
buildingMaterials.roof
);
skyscraperRoof.position.y = 20 + 1.5;
skyscraperRoof.rotation.y = Math.PI / 4;
building.add(skyscraperRoof);
break;
case 'house':
// Residential house
const houseBase = new THREE.Mesh(
new THREE.BoxGeometry(5, 3, 5),
buildingMaterials.wall
);
houseBase.castShadow = true;
houseBase.receiveShadow = true;
building.add(houseBase);
// Door
const door = new THREE.Mesh(
new THREE.BoxGeometry(1.5, 2, 0.2),
new THREE.MeshStandardMaterial({ color: 0x8B4513 })
);
door.position.set(0, -0.5, 2.51);
building.add(door);
// Windows
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 0.1),
buildingMaterials.window
);
window.position.set(
-2 + i * 4,
1,
j === 0 ? 2.51 : -2.51
);
building.add(window);
}
}
// Roof
const roofShape = new THREE.Shape();
roofShape.moveTo(-3, 0);
roofShape.lineTo(0, 3);
roofShape.lineTo(3, 0);
const roofGeometry = new THREE.ExtrudeGeometry(roofShape, {
depth: 5.2,
bevelEnabled: false
});
const houseRoof = new THREE.Mesh(
roofGeometry,
buildingMaterials.roof
);
houseRoof.position.y = 3;
houseRoof.rotation.y = Math.PI / 2;
building.add(houseRoof);
break;
}
return building;
}
// Place a building
function placeBuilding(position, type) {
const building = createBuilding(type);
building.position.copy(position);
scene.add(building);
buildings.push(building);
document.getElementById('building-count').textContent = buildings.length;
}
// Vehicles
const vehicles = [];
const vehicleMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
function createCar() {
const car = new THREE.Group();
// Car body
const body = new THREE.Mesh(
new THREE.BoxGeometry(2, 1, 4),
vehicleMaterial
);
body.castShadow = true;
body.receiveShadow = true;
car.add(body);
// Wheels
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 16);
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
for (let i = 0; i < 4; i++) {
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheel.castShadow = true;
wheel.receiveShadow = true;
if (i < 2) {
wheel.position.set(-0.8, -0.5, 1.5 - i * 3);
} else {
wheel.position.set(0.8, -0.5, 1.5 - (i - 2) * 3);
}
wheel.rotation.z = Math.PI / 2;
car.add(wheel);
}
// Windows
const window = new THREE.Mesh(
new THREE.BoxGeometry(1.8, 0.8, 1.5),
buildingMaterials.window
);
window.position.set(0, 0.5, -0.5);
car.add(window);
return car;
}
// Add some initial traffic
function addTraffic() {
const carCount = 10;
for (let i = 0; i < carCount; i++) {
const car = createCar();
let road;
let lane = Math.random() > 0.5 ? 1 : -1;
// Choose a random road
if (Math.random() > 0.5) {
// Horizontal road
road = roads[0];
car.position.x = Math.random() * 180 - 90;
car.position.z = lane * (mainRoadWidth / 2 - 0.5);
} else {
// Vertical road
road = roads[1];
car.position.z = Math.random() * 180 - 90;
car.position.x = lane * (mainRoadWidth / 2 - 0.5);
car.rotation.y = Math.PI / 2;
}
car.position.y = 0.5;
car.userData = {
speed: 0.05 + Math.random() * 0.05,
direction: Math.random() > 0.5 ? 1 : -1,
road: roads.indexOf(road)
};
if (road === roads[1]) {
car.userData.direction *= -1; // Reverse direction for vertical roads
}
scene.add(car);
vehicles.push(car);
}
}
// Update traffic movement
function updateTraffic() {
for (const car of vehicles) {
const road = roads[car.userData.road];
const isHorizontal = road === roads[0];
if (isHorizontal) {
car.position.x += car.userData.speed * car.userData.direction;
// Teleport car to other side if it goes off the road
if ((car.userData.direction > 0 && car.position.x > 100) ||
(car.userData.direction < 0 && car.position.x < -100)) {
car.position.x = car.userData.direction > 0 ? -100 : 100;
}
} else {
car.position.z += car.userData.speed * car.userData.direction;
// Teleport car to other side if it goes off the road
if ((car.userData.direction > 0 && car.position.z > 100) ||
(car.userData.direction < 0 && car.position.z < -100)) {
car.position.z = car.userData.direction > 0 ? -100 : 100;
}
}
}
}
// Add initial traffic
addTraffic();
// Player vehicle
let playerVehicle = null;
let playerControls = null;
let isDrivingMode = false;
function enterDriveMode() {
isDrivingMode = true;
document.getElementById('current-mode').textContent = 'Driving';
// Create player vehicle
playerVehicle = createCar();
playerVehicle.position.set(0, 0.5, -10);
scene.add(playerVehicle);
// Setup controls
camera.position.set(0,We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
Here is a complete, single-file HTML solution. It includes the Three.js library via CDN, sets up a city grid, allows you to place different types of buildings, implements a drivable car with arcade physics, and adds AI traffic. Save this code as an `.html` file (e.g., `city_driver.html`) and open it in any modern browser. ```html
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
I'll create a single-page HTML file with Three.js that lets you place buildings, drive through the town, and includes traffic. This will be a complete self-contained solution.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Town Builder</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
pointer-events: none;
}
#controls {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
pointer-events: all;
}
button {
margin: 5px;
padding: 8px 12px;
cursor: pointer;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background: #45a049;
}
.control-group {
margin-bottom: 10px;
}
.control-group label {
display: block;
margin-bottom: 5px;
}
#roadWidth {
width: 60px;
}
#buildingType {
width: 100px;
}
#buildingSize {
width: 60px;
}
#speedControl {
width: 100px;
}
</style>
</head>
<body>
<div id="info">3D Town Builder<br>Click to place buildings. WASD to drive. Mouse to look.</div>
<div id="controls">
<h3>Town Builder Controls</h3>
<div class="control-group">
<label for="buildingType">Building Type:</label>
<select id="buildingType">
<option value="house">House</option>
<option value="apartment">Apartment</option>
<option value="store">Store</option>
<option value="factory">Factory</option>
<option value="church">Church</option>
<option value="skyscraper">Skyscraper</option>
</select>
</div>
<div class="control-group">
<label for="buildingSize">Size:</label>
<select id="buildingSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
<div class="control-group">
<label for="roadWidth">Road Width:</label>
<select id="roadWidth">
<option value="10">Narrow (10)</option>
<option value="15" selected>Standard (15)</option>
<option value="20">Wide (20)</option>
</select>
</div>
<div class="control-group">
<label for="speedControl">Traffic Speed:</label>
<select id="speedControl">
<option value="0.5">Slow (0.5x)</option>
<option value="1.0" selected>Normal (1.0x)</option>
<option value="1.5">Fast (1.5x)</option>
</select>
</div>
<button id="placeRoad">Place Road</button>
<button id="clearAll">Clear All</button>
<button id="toggleTraffic">Toggle Traffic</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script>
// Main application
let scene, camera, renderer, controls;
let player;
let buildings = [];
let roads = [];
let traffic = [];
let roadWidth = 15;
let trafficSpeed = 1.0;
let trafficEnabled = true;
let currentBuildingType = 'house';
let currentBuildingSize = 'medium';
let isPlacingRoad = false;
let grid = {};
let gridSize = 50;
let cellSize = 5;
// Initialize the scene
init();
animate();
function init() {
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 20);
// Renderer
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);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 50;
// Lights
const ambientLight = new THREE.AmbientLight(0x404040, 1);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -25;
directionalLight.shadow.camera.right = 25;
directionalLight.shadow.camera.top = 25;
directionalLight.shadow.camera.bottom = -25;
scene.add(directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x555555,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Create the grid for road placement
createRoadGrid();
// Create player (first-person controller)
createPlayer();
// Create initial buildings
createInitialBuildings();
// Create initial roads
createInitialRoads();
// Event listeners
window.addEventListener('resize', onWindowResize);
renderer.domElement.addEventListener('click', onClick);
// Control button events
document.getElementById('buildingType').addEventListener('change', function(e) {
currentBuildingType = e.target.value;
});
document.getElementById('buildingSize').addEventListener('change', function(e) {
currentBuildingSize = e.target.value;
});
document.getElementById('roadWidth').addEventListener('change', function(e) {
roadWidth = parseFloat(e.target.value);
});
document.getElementById('speedControl').addEventListener('change', function(e) {
trafficSpeed = parseFloat(e.target.value);
});
document.getElementById('placeRoad').addEventListener('click', function() {
isPlacingRoad = !isPlacingRoad;
this.textContent = isPlacingRoad ? 'Cancel Road Placement' : 'Place Road';
document.getElementById('info').innerHTML = isPlacingRoad ?
'Click to place road segments. Click to end road.' :
'Click to place buildings. WASD to drive. Mouse to look.';
});
document.getElementById('clearAll').addEventListener('click', function() {
clearAll();
});
document.getElementById('toggleTraffic').addEventListener('click', function() {
trafficEnabled = !trafficEnabled;
this.textContent = trafficEnabled ? 'Disable Traffic' : 'Enable Traffic';
});
// Keyboard controls
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
// Mouse controls
document.addEventListener('mousemove', onMouseMove);
mouse = { x: 0, y: 0 };
}
// Mouse position for raycasting
let mouse = { x: 0, y: 0 };
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onClick(event) {
// Raycaster for object picking
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// Check for intersections with the ground
const intersects = raycaster.intersectObjects([ground]);
if (intersects.length > 0) {
const point = intersects[0].point;
const x = Math.round(point.x / cellSize) * cellSize;
const z = Math.round(point.z / cellSize) * cellSize;
if (isPlacingRoad) {
placeRoadAt(x, z);
} else {
placeBuildingAt(x, z);
}
}
}
function placeBuildingAt(x, z) {
const buildingType = currentBuildingType;
const size = currentBuildingSize;
// Check if there's already a building or road here
const key = `${x},${z}`;
if (grid[key]) {
return; // Can't place on existing road or building
}
let sizeScale;
switch(size) {
case 'small': sizeScale = 0.7; break;
case 'medium': sizeScale = 1.0; break;
case 'large': sizeScale = 1.5; break;
default: sizeScale = 1.0;
}
let building;
const height = 5 * sizeScale;
const width = 4 * sizeScale;
const depth = 4 * sizeScale;
switch(buildingType) {
case 'house':
building = createHouse(x, z, width, height, depth);
break;
case 'apartment':
building = createApartment(x, z, width, height, depth);
break;
case 'store':
building = createStore(x, z, width, height, depth);
break;
case 'factory':
building = createFactory(x, z, width, height, depth);
break;
case 'church':
building = createChurch(x, z, width, height, depth);
break;
case 'skyscraper':
building = createSkyscraper(x, z, width, height, depth);
break;
}
if (building) {
scene.add(building);
buildings.push({
mesh: building,
x: x,
z: z,
type: buildingType,
size: size
});
grid[key] = 'building';
}
}
function placeRoadAt(x, z) {
const key = `${x},${z}`;
// Check if there's already a building here
if (grid[key] === 'building') {
return; // Can't place road on building
}
// Create road segment
const roadGeometry = new THREE.PlaneGeometry(roadWidth, cellSize);
const roadMaterial = new THREE.MeshStandardMaterial({
color: 0x222222,
roughness: 0.9,
metalness: 0.1
});
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.set(x, 0.01, z);
road.receiveShadow = true;
scene.add(road);
// Add lane markings
const laneGeometry = new THREE.PlaneGeometry(0.2, cellSize);
const laneMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });
const lane1 = new THREE.Mesh(laneGeometry, laneMaterial);
const lane2 = new THREE.Mesh(laneGeometry, laneMaterial);
lane1.rotation.x = -Math.PI / 2;
lane2.rotation.x = -Math.PI / 2;
lane1.position.set(x, 0.02, z);
lane2.position.set(x, 0.02, z);
lane1.position.x -= roadWidth/4;
lane2.position.x += roadWidth/4;
scene.add(lane1);
scene.add(lane2);
roads.push({
mesh: road,
lane1: lane1,
lane2: lane2,
x: x,
z: z,
width: roadWidth
});
grid[key] = 'road';
// Check for adjacent road segments and connect them visually
const adjacentPositions = [
{x: x + cellSize, z: z},
{x: x - cellSize, z: z},
{x: x, z: z + cellSize},
{x: x, z: z - cellSize}
];
for (const pos of adjacentPositions) {
const adjKey = `${pos.x},${pos.z}`;
if (grid[adjKey] === 'road') {
// Find the road segment
const adjacentRoad = roads.find(r => r.x === pos.x && r.z === pos.z);
if (adjacentRoad) {
// We'll handle connections in the render loop for simplicity
}
}
}
}
function createHouse(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0xE0B070 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.ConeGeometry(width, depth/2, 4);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xA05030 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.rotation.z = Math.PI / 4;
roof.position.set(x, height + depth/4, z);
roof.castShadow = true;
roof.receiveShadow = true;
group.add(roof);
// Door
const doorGeometry = new THREE.BoxGeometry(0.5, 1.5, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, height/2, z + depth/2 - 0.05);
door.castShadow = true;
group.add(door);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.7, 0.7, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(x - width/3, height/2, z - depth/3);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(x + width/3, height/2, z - depth/3);
window2.castShadow = true;
group.add(window2);
// Garden
const gardenGeometry = new THREE.BoxGeometry(width+1, 0.1, depth+1);
const gardenMaterial = new THREE.MeshStandardMaterial({ color: 0x4E9C4B });
const garden = new THREE.Mesh(gardenGeometry, gardenMaterial);
garden.position.set(x, 0.05, z);
garden.receiveShadow = true;
scene.add(garden);
return group;
}
function createApartment(x, z, width, height, depth) {
const group = new THREE.Group();
// Building body
const bodyGeometry = new THREE.BoxGeometry(width, height, depth);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.set(x, height/2, z);
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.5, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.25, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Create multiple windows
const numRows = 4;
const numCols = 3;
const windowSpacing = width / (numCols + 1);
const rowSpacing = height / (numRows + 1);
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2 + rowSpacing * (j + 1) - 0.5,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2 + rowSpacing * (j + 1) - 0.5,
z + depth/2 - 0.1
);
window2.castShadow = true;
group.add(window2);
}
}
// Entrance
const entranceGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const entranceMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const entrance = new THREE.Mesh(entranceGeometry, entranceMaterial);
entrance.position.set(x, 1, z + depth/2 - 0.05);
entrance.castShadow = true;
group.add(entrance);
return group;
}
function createStore(x, z, width, height, depth) {
const group = new THREE.Group();
// Building body
const bodyGeometry = new THREE.BoxGeometry(width, height, depth);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xE0E0E0 });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.set(x, height/2, z);
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.8, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.4, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(1.2, 1.0, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Store windows
const numWindows = 3;
const windowSpacing = width / (numWindows + 1);
for (let i = 0; i < numWindows; i++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
}
// Entrance
const entranceGeometry = new THREE.BoxGeometry(2, 2.5, 0.1);
const entranceMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const entrance = new THREE.Mesh(entranceGeometry, entranceMaterial);
entrance.position.set(x, 1.25, z + depth/2 - 0.05);
entrance.castShadow = true;
group.add(entrance);
// Sign
const signGeometry = new THREE.BoxGeometry(2, 0.5, 0.1);
const signMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const sign = new THREE.Mesh(signGeometry, signMaterial);
sign.position.set(x, height/2 + 1, z - depth/2 + 0.1);
sign.castShadow = true;
group.add(sign);
return group;
}
function createFactory(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.6, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x606060 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.3, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.8, 0.6, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
const numWindows = 2;
const windowSpacing = width / (numWindows + 1);
for (let i = 0; i < numWindows; i++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
}
// Door
const doorGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, 1, z + depth/2 - 0.05);
door.castShadow = true;
group.add(door);
// Chimney
const chimneyGeometry = new THREE.BoxGeometry(0.8, 2, 0.8);
const chimneyMaterial = new THREE.MeshStandardMaterial({ color: 0x5D5D5D });
const chimney = new THREE.Mesh(chimneyGeometry, chimneyMaterial);
chimney.position.set(x, height/2 + 2, z);
chimney.castShadow = true;
group.add(chimney);
return group;
}
function createChurch(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0xE5E5E5 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.ConeGeometry(width, depth/2, 4);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xA52A2A });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.rotation.z = Math.PI / 4;
roof.position.set(x, height + depth/4, z);
roof.castShadow = true;
group.add(roof);
// Tower
const towerGeometry = new THREE.BoxGeometry(1.5, 5, 1.5);
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0xE5E5E5 });
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.set(x, height/2 + 2.5, z + depth/2 - 0.5);
tower.castShadow = true;
group.add(tower);
// Spire
const spireGeometry = new THREE.ConeGeometry(0.4, 2, 8);
const spireMaterial = new THREE.MeshStandardMaterial({ color: 0xC0C0C0 });
const spire = new THREE.Mesh(spireGeometry, spireMaterial);
spire.position.set(x, height/2 + 2.5 + 5/2 + 1, z + depth/2 - 0.5);
spire.castShadow = true;
group.add(spire);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.8, 1.2, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Side windows
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(x - width/3, height/2, z - depth/3);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(x + width/3, height/2, z - depth/3);
window2.castShadow = true;
group.add(window2);
// Front window
const frontWindowGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const frontWindow = new THREE.Mesh(frontWindowGeometry, windowMaterial);
frontWindow.position.set(x, height/2, z + depth/2 - 0.1);
frontWindow.castShadow = true;
group.add(frontWindow);
// Door
const doorGeometry = new THREE.BoxGeometry(1, 2, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, 1, z - depth/2 + 0.1);
door.castShadow = true;
group.add(door);
return group;
}
function createSkyscraper(x, z, width, height, depth) {
const group = new THREE.Group();
// Main tower
const towerGeometry = new THREE.BoxGeometry(width, height * 2, depth);
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0x4F4F4F });
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.set(x, height, z);
tower.castShadow = true;
tower.receiveShadow = true;
group.add(tower);
// Roof
const roofGeometry = new THREE.BoxGeometry(width * 0.8, height * 0.3, depth * 0.8);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + height * 2 + height * 0.15, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.05);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0xE0F7FF });
const numRows = 10;
const numCols = 6;
const windowSpacingX = width / (numCols + 1);
const windowSpacingZ = depth / (numRows + 1);
// Front side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z + depth/2 - 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Left side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z - depth/2 + 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Right side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x + width/2 - windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z + depth/2 - 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Back side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z - depth/2 + 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Antenna
const antennaGeometry = new THREE.CylinderGeometry(0.1, 0.05, 2, 8);
const antennaMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
const antenna = new THREE.Mesh(antennaGeometry, antennaMaterial);
antenna.position.set(x, height + height * 2 + 1, z);
antenna.castShadow = true;
group.add(antenna);
return group;
}
function createPlayer() {
// Create a simple player object (camera with a slight offset)
player = new THREE.Group();
scene.add(player);
// Camera is already defined, we'll just parent it to player
player.add(camera);
// Add a simple representation of the player
const playerGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8);
const playerMaterial = new THREE.MeshStandardMaterial({ color: 0x3498db });
const playerBody = new THREE.Mesh(playerGeometry, playerMaterial);
playerBody.rotation.x = Math.PI / 2;
playerBody.position.set(0, 0.4, 0);
player.add(playerBody);
// Initial position
player.position.set(0, 2, 5);
}
function createInitialBuildings() {
// Add some initial buildings to give the town a start
const buildingsData = [
{type: 'house', x: 10, z: 10, size: 'medium'},
{type: 'store', x: -12, z: 8, size: 'medium'},
{type: 'apartment', x: 8, z: -12, size: 'medium'},
{type: 'church', x: -10, z: -10, size: 'medium'},
{type: 'house', x: 15, z: -5, size: 'small'},
{type: 'factory', x: -15, z: 5, size: 'medium'},
{type: 'skyscraper', x: 0, z: -18, size: 'large'},
{type: 'house', x: 5, z: -18, size: 'small'}
];
buildingsData.forEach(building => {
const key = `${building.x},${building.z}`;
if (!grid[key]) {
const height = 5;
const width = 4;
const depth = 4;
let buildingMesh;
switch(building.type) {
case 'house':
buildingMesh = createHouse(building.x, building.z, width, height, depth);
break;
case 'store':
buildingMesh = createStore(building.x, building.z, width, height, depth);
break;
case 'apartment':
buildingMesh = createApartment(building.x, building.z, width, height, depth);
break;
case 'factory':
buildingMesh = createFactory(building.x, building.z, width, height, depth);
break;
case 'church':
buildingMesh = createChurch(building.x, building.z, width, height, depth);
break;
case 'skyscraper':
buildingMesh = createSkyscraper(building.x, building.z, width, height, depth);
break;
}
if (buildingMesh) {
scene.add(buildingMesh);
buildings.push({
mesh: buildingMesh,
x: building.x,
z: building.z,
type: building.type,
size: building.size
});
grid[key] = 'building';
}
}
});
}
function createInitialRoads() {
// Create a basic road network
const roadSegments = [
// Main road
{x: -20, z: 0},
{x: -15, z: 0},
{x: -10, z: 0},
{x: -5, z: 0},
{x: 0, z: 0},
{x: 5, z: 0},
{x: 10, z: 0},
{x: 15, z: 0},
{x: 20, z: 0},
// Crossroads
{x: 0, z: -20},
{x: 0, z: -15},
{x: 0, z: -10},
{x: 0, z: -5},
{x: 0, z: 5},
{x: 0, z: 10},
{x: 0, z: 15},
{x: 0, z: 20},
// Side streets
{x: -10, z: -10},
{x: -5, z: -10},
{x: 0, z: -10},
{x: 5, z: -10},
{x: 10, z: -10},
{x: -10, z: 10},
{x: -5, z: 10},
{x: 0, z: 10},
{x: 5, z: 10},
{x: 10, z: 10}
];
roadSegments.forEach(segment => {
placeRoadAt(segment.x, segment.z);
});
}
function createRoadGrid() {
// Create a grid for placing roads
for (let x = -gridSize; x <= gridSize; x += cellSize) {
for (let z = -gridSize; z <= gridSize; z += cellSize) {
const key = `${x},${z}`;
grid[key] = null;
}
}
}
function createTraffic() {
// Create traffic vehicles
const trafficCount = 8;
for (let i = 0; i < trafficCount; i++) {
const roadSegment = roads[Math.floor(Math.random() * roads.length)];
if (!roadSegment) continue;
// Create a car
const carGeometry = new THREE.BoxGeometry(0.8, 0.5, 1.5);
const carMaterial = new THREE.MeshStandardMaterial({
color: Math.random() * 0xffffff
});
const car = new THREE.Mesh(carGeometry, carMaterial);
// Position the car on the road
car.position.set(roadSegment.x, 0.25, roadSegment.z);
// Get a random direction based on road segment
let direction;
const roadIndex = roads.indexOf(roadSegment);
const nextRoadIndex = (roadIndex + 1) % roads.length;
const nextRoad = roads[nextRoadIndex];
if (nextRoad) {
// Calculate direction vector
const dx = nextRoad.x - roadSegment.x;
const dz = nextRoad.z - roadSegment.z;
direction = new THREE.Vector3(dx, 0, dz).normalize();
} else {
// Default direction
direction = new THREE.Vector3(1, 0, 0);
}
// Rotate car to face direction
const angle = Math.atan2(direction.z, direction.x);
car.rotation.y = angle;
// Add to scene
scene.add(car);
// Store traffic data
traffic.push({
mesh: car,
position: new THREE.Vector3(roadSegment.x, 0.25, roadSegment.z),
direction: direction,
speed: 0.05 * trafficSpeed,
roadIndex: roadIndex
});
}
}
function updateTraffic() {
if (!trafficEnabled) return;
traffic.forEach(car => {
// Move car along its direction
car.position.x += car.direction.x * car.speed;
car.position.z += car.direction.z * car.speed;
// Update car rotation to face direction
const angle = Math.atan2(car.direction.z, car.direction.x);
car.mesh.rotation.y = angle;
// Check if car has reached the end of a road segment
// This is a simplified approach - in a real game, you'd use pathfinding
const road = roads.find(r =>
Math.abs(r.x - car.position.x) < 1 &&
Math.abs(r.z - car.position.z) < 1
);
if (road) {
const roadIndex = roads.indexOf(road);
// Check for next road segment in the network
const adjacentRoads = roads.filter(r =>
(Math.abs(r.x - road.x) < cellSize * 1.5 && Math.abs(r.z - road.z) < cellSize * 1.5) &&
(!(Math.abs(r.x - road.x) < 0.5 && Math.abs(r.z - road.z) < 0.5)) // Not same road
);
if (adjacentRoads.length > 0) {
// Simple pathfinding: pick a random adjacent road
const nextRoad = adjacentRoads[Math.floor(Math.random() * adjacentRoads.length)];
const dx = nextRoad.x - car.position.x;
const dz = nextRoad.z - car.position.z;
car.direction.set(dx, 0, dz).normalize();
}
}
// Check if car is out of bounds and needs to be repositioned
if (Math.abs(car.position.x) > gridSize + 5 || Math.abs(car.position.z) > gridSize + 5) {
// Reposition car to a random road segment
const randomRoad = roads[Math.floor(Math.random() * roads.length)];
if (randomRoad) {
car.position.set(randomRoad.x, 0.25, randomRoad.z);
const nextRoad = roads[(roads.indexOf(randomRoad) + 1) % roads.length];
if (nextRoad) {
const dx = nextRoad.x - randomRoad.x;
const dz = nextRoad.z - randomRoad.z;
car.direction.set(dx, 0, dz).normalize();
}
}
}
});
}
function clearAll() {
// Remove all buildings
buildings.forEach(building => {
scene.remove(building.mesh);
});
buildings = [];
// Remove all roads
roads.forEach(road => {
scene.remove(road.mesh);
scene.remove(road.lane1);
scene.remove(road.lane2);
});
roads = [];Create a 3D building and driving game in a single self-contained HTML file using Three.js. Tests an AI's ability to combine 3D graphics, game logic, and UI interactions on the web.
Create a three.js 3D game where I can place buildings of various designs and sizes, and drive through the town I've created. Add traffic to the road as well. In a single-page self-container HTML.
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
```
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
# 3D City Builder Game with Traffic
Here's a complete, self-contained HTML page for a Three.js city builder game where you can place buildings and drive through your city with traffic:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>City Builder Game</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.5);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
}
#building-selector {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
display: flex;
gap: 10px;
z-index: 100;
}
.building-btn {
padding: 8px 15px;
background: #444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.building-btn:hover {
background: #666;
}
.building-btn.active {
background: #0078ff;
}
#help {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.5);
color: white;
padding: 10px;
border-radius: 5px;
max-width: 250px;
z-index: 100;
}
</style>
</head>
<body>
<div id="ui">
<div>Buildings Placed: <span id="building-count">0</span></div>
<div>Current Mode: <span id="current-mode">Building</span></div>
</div>
<div id="building-selector">
<button class="building-btn" data-type="small">Small Building</button>
<button class="building-btn" data-type="medium">Medium Building</button>
<button class="building-btn" data-type="large">Large Building</button>
<button class="building-btn" data-type="skyscraper">Skyscraper</button>
<button class="building-btn" data-type="house">House</button>
<button class="building-btn" id="drive-btn">Drive Mode</button>
</div>
<div id="help">
<h3>Controls:</h3>
<p><strong>Building Mode:</strong><br>
- Click to place buildings<br>
- Scroll to rotate building</p>
<p><strong>Drive Mode:</strong><br>
- WASD to drive<br>
- Mouse to look around</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/PointerLockControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<script>
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 20);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
scene.add(directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(200, 200);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a5f0b,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Grid helper
const gridHelper = new THREE.GridHelper(200, 20, 0x555555, 0x333333);
scene.add(gridHelper);
// Roads
const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const roads = [];
// Main roads
const mainRoadWidth = 4;
const mainRoadLength = 200;
// Horizontal road
const horizontalRoadGeometry = new THREE.BoxGeometry(mainRoadLength, 0.1, mainRoadWidth);
const horizontalRoad = new THREE.Mesh(horizontalRoadGeometry, roadMaterial);
horizontalRoad.position.set(0, 0.01, 0);
horizontalRoad.receiveShadow = true;
scene.add(horizontalRoad);
roads.push(horizontalRoad);
// Vertical road
const verticalRoadGeometry = new THREE.BoxGeometry(mainRoadWidth, 0.1, mainRoadLength);
const verticalRoad = new THREE.Mesh(verticalRoadGeometry, roadMaterial);
verticalRoad.position.set(0, 0.01, 0);
verticalRoad.receiveShadow = true;
scene.add(verticalRoad);
roads.push(verticalRoad);
// Side roads
const sideRoadWidth = 2;
const sideRoadLength = 50;
// Add cross streets every 20 units
for (let i = -80; i <= 80; i += 20) {
if (i === 0) continue; // Skip center
// Horizontal side roads
const sideRoadH = new THREE.Mesh(
new THREE.BoxGeometry(sideRoadLength, 0.1, sideRoadWidth),
roadMaterial
);
sideRoadH.position.set(i, 0.01, 0);
sideRoadH.receiveShadow = true;
scene.add(sideRoadH);
roads.push(sideRoadH);
// Vertical side roads
const sideRoadV = new THREE.Mesh(
new THREE.BoxGeometry(sideRoadWidth, 0.1, sideRoadLength),
roadMaterial
);
sideRoadV.position.set(0, 0.01, i);
sideRoadV.receiveShadow = true;
scene.add(sideRoadV);
roads.push(sideRoadV);
}
// Road markings
const roadMarkingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
// Add center lines
const centerLineGeometry = new THREE.BoxGeometry(mainRoadLength, 0.11, 0.2);
const centerLine = new THREE.Mesh(centerLineGeometry, roadMarkingMaterial);
centerLine.position.set(0, 0.02, 0);
scene.add(centerLine);
const centerLineVGeometry = new THREE.BoxGeometry(0.2, 0.11, mainRoadLength);
const centerLineV = new THREE.Mesh(centerLineVGeometry, roadMarkingMaterial);
centerLineV.position.set(0, 0.02, 0);
scene.add(centerLineV);
// Add crosswalk markings
const crosswalkGeometry = new THREE.BoxGeometry(4, 0.11, 0.5);
for (let i = -2; i <= 2; i += 1) {
const crosswalkH = new THREE.Mesh(crosswalkGeometry, roadMarkingMaterial);
crosswalkH.position.set(i * 2, 0.02, 0);
scene.add(crosswalkH);
const crosswalkV = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 0.11, 4),
roadMarkingMaterial
);
crosswalkV.position.set(0, 0.02, i * 2);
scene.add(crosswalkV);
}
// Buildings
const buildings = [];
let currentBuildingType = 'small';
// Building materials
const buildingMaterials = {
wall: new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.7 }),
window: new THREE.MeshStandardMaterial({ color: 0x87CEEB, roughness: 0.1, metalness: 0.9 }),
roof: new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.8 })
};
// Create a preview building
let previewBuilding = createBuilding(currentBuildingType);
previewBuilding.visible = false;
scene.add(previewBuilding);
// Function to create different types of buildings
function createBuilding(type) {
const building = new THREE.Group();
switch(type) {
case 'small':
// Small office building
const smallBase = new THREE.Mesh(
new THREE.BoxGeometry(4, 4, 4),
buildingMaterials.wall
);
smallBase.castShadow = true;
smallBase.receiveShadow = true;
building.add(smallBase);
// Windows
for (let i = 0; i < 4; i++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.8, 0.1),
buildingMaterials.window
);
window.position.set(-1.5 + (i % 2) * 3, 1.5 - Math.floor(i / 2) * 2, 2.01);
building.add(window);
}
// Roof
const smallRoof = new THREE.Mesh(
new THREE.BoxGeometry(4.5, 0.5, 4.5),
buildingMaterials.roof
);
smallRoof.position.y = 4.25;
building.add(smallRoof);
break;
case 'medium':
// Medium apartment building
const mediumBase = new THREE.Mesh(
new THREE.BoxGeometry(6, 8, 6),
buildingMaterials.wall
);
mediumBase.castShadow = true;
mediumBase.receiveShadow = true;
building.add(mediumBase);
// Windows
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 3; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 0.1),
buildingMaterials.window
);
window.position.set(-2.5 + (i % 2) * 5, 3 - j * 3, 3.01);
building.add(window);
}
}
// Roof
const mediumRoof = new THREE.Mesh(
new THREE.BoxGeometry(6.5, 0.5, 6.5),
buildingMaterials.roof
);
mediumRoof.position.y = 8.25;
building.add(mediumRoof);
break;
case 'large':
// Large commercial building
const largeBase = new THREE.Mesh(
new THREE.BoxGeometry(10, 6, 10),
buildingMaterials.wall
);
largeBase.castShadow = true;
largeBase.receiveShadow = true;
building.add(largeBase);
// Windows
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 3; j++) {
for (let k = 0; k < 2; k++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1.5, 1.5, 0.1),
buildingMaterials.window
);
window.position.set(
-radek4 + i * 2,
2.5 - j * 2,
k === 0 ? 5.01 : -5.01
);
building.add(window);
}
}
}
// Roof
const largeRoof = new THREE.Mesh(
new THREE.ConeGeometry(7, 2, 4),
buildingMaterials.roof
);
largeRoof.position.y = 6;
largeRoof.rotation.y = Math.PI / 4;
building.add(largeRoof);
break;
case 'skyscraper':
// Skyscraper
const skyscraperBase = new THREE.Mesh(
new THREE.BoxGeometry(5, 20, 5),
buildingMaterials.wall
);
skyscraperBase.castShadow = true;
skyscraperBase.receiveShadow = true;
building.add(skyscraperBase);
// Windows
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 10; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 1.5, 0.1),
buildingMaterials.window
);
window.position.set(
-2 + (i % 2) * 4,
9 - j * 1.8,
2.51
);
building.add(window);
}
}
// Roof
const skyscraperRoof = new THREE.Mesh(
new THREE.ConeGeometry(3, 3, 4),
buildingMaterials.roof
);
skyscraperRoof.position.y = 20 + 1.5;
skyscraperRoof.rotation.y = Math.PI / 4;
building.add(skyscraperRoof);
break;
case 'house':
// Residential house
const houseBase = new THREE.Mesh(
new THREE.BoxGeometry(5, 3, 5),
buildingMaterials.wall
);
houseBase.castShadow = true;
houseBase.receiveShadow = true;
building.add(houseBase);
// Door
const door = new THREE.Mesh(
new THREE.BoxGeometry(1.5, 2, 0.2),
new THREE.MeshStandardMaterial({ color: 0x8B4513 })
);
door.position.set(0, -0.5, 2.51);
building.add(door);
// Windows
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
const window = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 0.1),
buildingMaterials.window
);
window.position.set(
-2 + i * 4,
1,
j === 0 ? 2.51 : -2.51
);
building.add(window);
}
}
// Roof
const roofShape = new THREE.Shape();
roofShape.moveTo(-3, 0);
roofShape.lineTo(0, 3);
roofShape.lineTo(3, 0);
const roofGeometry = new THREE.ExtrudeGeometry(roofShape, {
depth: 5.2,
bevelEnabled: false
});
const houseRoof = new THREE.Mesh(
roofGeometry,
buildingMaterials.roof
);
houseRoof.position.y = 3;
houseRoof.rotation.y = Math.PI / 2;
building.add(houseRoof);
break;
}
return building;
}
// Place a building
function placeBuilding(position, type) {
const building = createBuilding(type);
building.position.copy(position);
scene.add(building);
buildings.push(building);
document.getElementById('building-count').textContent = buildings.length;
}
// Vehicles
const vehicles = [];
const vehicleMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
function createCar() {
const car = new THREE.Group();
// Car body
const body = new THREE.Mesh(
new THREE.BoxGeometry(2, 1, 4),
vehicleMaterial
);
body.castShadow = true;
body.receiveShadow = true;
car.add(body);
// Wheels
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 16);
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
for (let i = 0; i < 4; i++) {
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheel.castShadow = true;
wheel.receiveShadow = true;
if (i < 2) {
wheel.position.set(-0.8, -0.5, 1.5 - i * 3);
} else {
wheel.position.set(0.8, -0.5, 1.5 - (i - 2) * 3);
}
wheel.rotation.z = Math.PI / 2;
car.add(wheel);
}
// Windows
const window = new THREE.Mesh(
new THREE.BoxGeometry(1.8, 0.8, 1.5),
buildingMaterials.window
);
window.position.set(0, 0.5, -0.5);
car.add(window);
return car;
}
// Add some initial traffic
function addTraffic() {
const carCount = 10;
for (let i = 0; i < carCount; i++) {
const car = createCar();
let road;
let lane = Math.random() > 0.5 ? 1 : -1;
// Choose a random road
if (Math.random() > 0.5) {
// Horizontal road
road = roads[0];
car.position.x = Math.random() * 180 - 90;
car.position.z = lane * (mainRoadWidth / 2 - 0.5);
} else {
// Vertical road
road = roads[1];
car.position.z = Math.random() * 180 - 90;
car.position.x = lane * (mainRoadWidth / 2 - 0.5);
car.rotation.y = Math.PI / 2;
}
car.position.y = 0.5;
car.userData = {
speed: 0.05 + Math.random() * 0.05,
direction: Math.random() > 0.5 ? 1 : -1,
road: roads.indexOf(road)
};
if (road === roads[1]) {
car.userData.direction *= -1; // Reverse direction for vertical roads
}
scene.add(car);
vehicles.push(car);
}
}
// Update traffic movement
function updateTraffic() {
for (const car of vehicles) {
const road = roads[car.userData.road];
const isHorizontal = road === roads[0];
if (isHorizontal) {
car.position.x += car.userData.speed * car.userData.direction;
// Teleport car to other side if it goes off the road
if ((car.userData.direction > 0 && car.position.x > 100) ||
(car.userData.direction < 0 && car.position.x < -100)) {
car.position.x = car.userData.direction > 0 ? -100 : 100;
}
} else {
car.position.z += car.userData.speed * car.userData.direction;
// Teleport car to other side if it goes off the road
if ((car.userData.direction > 0 && car.position.z > 100) ||
(car.userData.direction < 0 && car.position.z < -100)) {
car.position.z = car.userData.direction > 0 ? -100 : 100;
}
}
}
}
// Add initial traffic
addTraffic();
// Player vehicle
let playerVehicle = null;
let playerControls = null;
let isDrivingMode = false;
function enterDriveMode() {
isDrivingMode = true;
document.getElementById('current-mode').textContent = 'Driving';
// Create player vehicle
playerVehicle = createCar();
playerVehicle.position.set(0, 0.5, -10);
scene.add(playerVehicle);
// Setup controls
camera.position.set(0,We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
Here is a complete, single-file HTML solution. It includes the Three.js library via CDN, sets up a city grid, allows you to place different types of buildings, implements a drivable car with arcade physics, and adds AI traffic. Save this code as an `.html` file (e.g., `city_driver.html`) and open it in any modern browser. ```html
We can't display this one. It's not us. Well... it might be us.
The model was asked for a web page. It produced something else. We're not going to define what.
I'll create a single-page HTML file with Three.js that lets you place buildings, drive through the town, and includes traffic. This will be a complete self-contained solution.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Town Builder</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
pointer-events: none;
}
#controls {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
pointer-events: all;
}
button {
margin: 5px;
padding: 8px 12px;
cursor: pointer;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background: #45a049;
}
.control-group {
margin-bottom: 10px;
}
.control-group label {
display: block;
margin-bottom: 5px;
}
#roadWidth {
width: 60px;
}
#buildingType {
width: 100px;
}
#buildingSize {
width: 60px;
}
#speedControl {
width: 100px;
}
</style>
</head>
<body>
<div id="info">3D Town Builder<br>Click to place buildings. WASD to drive. Mouse to look.</div>
<div id="controls">
<h3>Town Builder Controls</h3>
<div class="control-group">
<label for="buildingType">Building Type:</label>
<select id="buildingType">
<option value="house">House</option>
<option value="apartment">Apartment</option>
<option value="store">Store</option>
<option value="factory">Factory</option>
<option value="church">Church</option>
<option value="skyscraper">Skyscraper</option>
</select>
</div>
<div class="control-group">
<label for="buildingSize">Size:</label>
<select id="buildingSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
<div class="control-group">
<label for="roadWidth">Road Width:</label>
<select id="roadWidth">
<option value="10">Narrow (10)</option>
<option value="15" selected>Standard (15)</option>
<option value="20">Wide (20)</option>
</select>
</div>
<div class="control-group">
<label for="speedControl">Traffic Speed:</label>
<select id="speedControl">
<option value="0.5">Slow (0.5x)</option>
<option value="1.0" selected>Normal (1.0x)</option>
<option value="1.5">Fast (1.5x)</option>
</select>
</div>
<button id="placeRoad">Place Road</button>
<button id="clearAll">Clear All</button>
<button id="toggleTraffic">Toggle Traffic</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script>
// Main application
let scene, camera, renderer, controls;
let player;
let buildings = [];
let roads = [];
let traffic = [];
let roadWidth = 15;
let trafficSpeed = 1.0;
let trafficEnabled = true;
let currentBuildingType = 'house';
let currentBuildingSize = 'medium';
let isPlacingRoad = false;
let grid = {};
let gridSize = 50;
let cellSize = 5;
// Initialize the scene
init();
animate();
function init() {
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 20);
// Renderer
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);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 50;
// Lights
const ambientLight = new THREE.AmbientLight(0x404040, 1);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -25;
directionalLight.shadow.camera.right = 25;
directionalLight.shadow.camera.top = 25;
directionalLight.shadow.camera.bottom = -25;
scene.add(directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x555555,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Create the grid for road placement
createRoadGrid();
// Create player (first-person controller)
createPlayer();
// Create initial buildings
createInitialBuildings();
// Create initial roads
createInitialRoads();
// Event listeners
window.addEventListener('resize', onWindowResize);
renderer.domElement.addEventListener('click', onClick);
// Control button events
document.getElementById('buildingType').addEventListener('change', function(e) {
currentBuildingType = e.target.value;
});
document.getElementById('buildingSize').addEventListener('change', function(e) {
currentBuildingSize = e.target.value;
});
document.getElementById('roadWidth').addEventListener('change', function(e) {
roadWidth = parseFloat(e.target.value);
});
document.getElementById('speedControl').addEventListener('change', function(e) {
trafficSpeed = parseFloat(e.target.value);
});
document.getElementById('placeRoad').addEventListener('click', function() {
isPlacingRoad = !isPlacingRoad;
this.textContent = isPlacingRoad ? 'Cancel Road Placement' : 'Place Road';
document.getElementById('info').innerHTML = isPlacingRoad ?
'Click to place road segments. Click to end road.' :
'Click to place buildings. WASD to drive. Mouse to look.';
});
document.getElementById('clearAll').addEventListener('click', function() {
clearAll();
});
document.getElementById('toggleTraffic').addEventListener('click', function() {
trafficEnabled = !trafficEnabled;
this.textContent = trafficEnabled ? 'Disable Traffic' : 'Enable Traffic';
});
// Keyboard controls
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
// Mouse controls
document.addEventListener('mousemove', onMouseMove);
mouse = { x: 0, y: 0 };
}
// Mouse position for raycasting
let mouse = { x: 0, y: 0 };
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onClick(event) {
// Raycaster for object picking
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// Check for intersections with the ground
const intersects = raycaster.intersectObjects([ground]);
if (intersects.length > 0) {
const point = intersects[0].point;
const x = Math.round(point.x / cellSize) * cellSize;
const z = Math.round(point.z / cellSize) * cellSize;
if (isPlacingRoad) {
placeRoadAt(x, z);
} else {
placeBuildingAt(x, z);
}
}
}
function placeBuildingAt(x, z) {
const buildingType = currentBuildingType;
const size = currentBuildingSize;
// Check if there's already a building or road here
const key = `${x},${z}`;
if (grid[key]) {
return; // Can't place on existing road or building
}
let sizeScale;
switch(size) {
case 'small': sizeScale = 0.7; break;
case 'medium': sizeScale = 1.0; break;
case 'large': sizeScale = 1.5; break;
default: sizeScale = 1.0;
}
let building;
const height = 5 * sizeScale;
const width = 4 * sizeScale;
const depth = 4 * sizeScale;
switch(buildingType) {
case 'house':
building = createHouse(x, z, width, height, depth);
break;
case 'apartment':
building = createApartment(x, z, width, height, depth);
break;
case 'store':
building = createStore(x, z, width, height, depth);
break;
case 'factory':
building = createFactory(x, z, width, height, depth);
break;
case 'church':
building = createChurch(x, z, width, height, depth);
break;
case 'skyscraper':
building = createSkyscraper(x, z, width, height, depth);
break;
}
if (building) {
scene.add(building);
buildings.push({
mesh: building,
x: x,
z: z,
type: buildingType,
size: size
});
grid[key] = 'building';
}
}
function placeRoadAt(x, z) {
const key = `${x},${z}`;
// Check if there's already a building here
if (grid[key] === 'building') {
return; // Can't place road on building
}
// Create road segment
const roadGeometry = new THREE.PlaneGeometry(roadWidth, cellSize);
const roadMaterial = new THREE.MeshStandardMaterial({
color: 0x222222,
roughness: 0.9,
metalness: 0.1
});
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.set(x, 0.01, z);
road.receiveShadow = true;
scene.add(road);
// Add lane markings
const laneGeometry = new THREE.PlaneGeometry(0.2, cellSize);
const laneMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });
const lane1 = new THREE.Mesh(laneGeometry, laneMaterial);
const lane2 = new THREE.Mesh(laneGeometry, laneMaterial);
lane1.rotation.x = -Math.PI / 2;
lane2.rotation.x = -Math.PI / 2;
lane1.position.set(x, 0.02, z);
lane2.position.set(x, 0.02, z);
lane1.position.x -= roadWidth/4;
lane2.position.x += roadWidth/4;
scene.add(lane1);
scene.add(lane2);
roads.push({
mesh: road,
lane1: lane1,
lane2: lane2,
x: x,
z: z,
width: roadWidth
});
grid[key] = 'road';
// Check for adjacent road segments and connect them visually
const adjacentPositions = [
{x: x + cellSize, z: z},
{x: x - cellSize, z: z},
{x: x, z: z + cellSize},
{x: x, z: z - cellSize}
];
for (const pos of adjacentPositions) {
const adjKey = `${pos.x},${pos.z}`;
if (grid[adjKey] === 'road') {
// Find the road segment
const adjacentRoad = roads.find(r => r.x === pos.x && r.z === pos.z);
if (adjacentRoad) {
// We'll handle connections in the render loop for simplicity
}
}
}
}
function createHouse(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0xE0B070 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.ConeGeometry(width, depth/2, 4);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xA05030 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.rotation.z = Math.PI / 4;
roof.position.set(x, height + depth/4, z);
roof.castShadow = true;
roof.receiveShadow = true;
group.add(roof);
// Door
const doorGeometry = new THREE.BoxGeometry(0.5, 1.5, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, height/2, z + depth/2 - 0.05);
door.castShadow = true;
group.add(door);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.7, 0.7, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(x - width/3, height/2, z - depth/3);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(x + width/3, height/2, z - depth/3);
window2.castShadow = true;
group.add(window2);
// Garden
const gardenGeometry = new THREE.BoxGeometry(width+1, 0.1, depth+1);
const gardenMaterial = new THREE.MeshStandardMaterial({ color: 0x4E9C4B });
const garden = new THREE.Mesh(gardenGeometry, gardenMaterial);
garden.position.set(x, 0.05, z);
garden.receiveShadow = true;
scene.add(garden);
return group;
}
function createApartment(x, z, width, height, depth) {
const group = new THREE.Group();
// Building body
const bodyGeometry = new THREE.BoxGeometry(width, height, depth);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.set(x, height/2, z);
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.5, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.25, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Create multiple windows
const numRows = 4;
const numCols = 3;
const windowSpacing = width / (numCols + 1);
const rowSpacing = height / (numRows + 1);
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2 + rowSpacing * (j + 1) - 0.5,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2 + rowSpacing * (j + 1) - 0.5,
z + depth/2 - 0.1
);
window2.castShadow = true;
group.add(window2);
}
}
// Entrance
const entranceGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const entranceMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const entrance = new THREE.Mesh(entranceGeometry, entranceMaterial);
entrance.position.set(x, 1, z + depth/2 - 0.05);
entrance.castShadow = true;
group.add(entrance);
return group;
}
function createStore(x, z, width, height, depth) {
const group = new THREE.Group();
// Building body
const bodyGeometry = new THREE.BoxGeometry(width, height, depth);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xE0E0E0 });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.set(x, height/2, z);
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.8, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.4, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(1.2, 1.0, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Store windows
const numWindows = 3;
const windowSpacing = width / (numWindows + 1);
for (let i = 0; i < numWindows; i++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
}
// Entrance
const entranceGeometry = new THREE.BoxGeometry(2, 2.5, 0.1);
const entranceMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const entrance = new THREE.Mesh(entranceGeometry, entranceMaterial);
entrance.position.set(x, 1.25, z + depth/2 - 0.05);
entrance.castShadow = true;
group.add(entrance);
// Sign
const signGeometry = new THREE.BoxGeometry(2, 0.5, 0.1);
const signMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const sign = new THREE.Mesh(signGeometry, signMaterial);
sign.position.set(x, height/2 + 1, z - depth/2 + 0.1);
sign.castShadow = true;
group.add(sign);
return group;
}
function createFactory(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.BoxGeometry(width + 0.5, 0.6, depth + 0.5);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x606060 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + 0.3, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.8, 0.6, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
const numWindows = 2;
const windowSpacing = width / (numWindows + 1);
for (let i = 0; i < numWindows; i++) {
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(
x - width/2 + windowSpacing * (i + 1),
height/2,
z - depth/2 + 0.1
);
window1.castShadow = true;
group.add(window1);
}
// Door
const doorGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, 1, z + depth/2 - 0.05);
door.castShadow = true;
group.add(door);
// Chimney
const chimneyGeometry = new THREE.BoxGeometry(0.8, 2, 0.8);
const chimneyMaterial = new THREE.MeshStandardMaterial({ color: 0x5D5D5D });
const chimney = new THREE.Mesh(chimneyGeometry, chimneyMaterial);
chimney.position.set(x, height/2 + 2, z);
chimney.castShadow = true;
group.add(chimney);
return group;
}
function createChurch(x, z, width, height, depth) {
const group = new THREE.Group();
// Main building
const mainGeometry = new THREE.BoxGeometry(width, height, depth);
const mainMaterial = new THREE.MeshStandardMaterial({ color: 0xE5E5E5 });
const main = new THREE.Mesh(mainGeometry, mainMaterial);
main.position.set(x, height/2, z);
main.castShadow = true;
main.receiveShadow = true;
group.add(main);
// Roof
const roofGeometry = new THREE.ConeGeometry(width, depth/2, 4);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xA52A2A });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.rotation.z = Math.PI / 4;
roof.position.set(x, height + depth/4, z);
roof.castShadow = true;
group.add(roof);
// Tower
const towerGeometry = new THREE.BoxGeometry(1.5, 5, 1.5);
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0xE5E5E5 });
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.set(x, height/2 + 2.5, z + depth/2 - 0.5);
tower.castShadow = true;
group.add(tower);
// Spire
const spireGeometry = new THREE.ConeGeometry(0.4, 2, 8);
const spireMaterial = new THREE.MeshStandardMaterial({ color: 0xC0C0C0 });
const spire = new THREE.Mesh(spireGeometry, spireMaterial);
spire.position.set(x, height/2 + 2.5 + 5/2 + 1, z + depth/2 - 0.5);
spire.castShadow = true;
group.add(spire);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.8, 1.2, 0.1);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
// Side windows
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(x - width/3, height/2, z - depth/3);
window1.castShadow = true;
group.add(window1);
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
window2.position.set(x + width/3, height/2, z - depth/3);
window2.castShadow = true;
group.add(window2);
// Front window
const frontWindowGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const frontWindow = new THREE.Mesh(frontWindowGeometry, windowMaterial);
frontWindow.position.set(x, height/2, z + depth/2 - 0.1);
frontWindow.castShadow = true;
group.add(frontWindow);
// Door
const doorGeometry = new THREE.BoxGeometry(1, 2, 0.1);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x5D3A22 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(x, 1, z - depth/2 + 0.1);
door.castShadow = true;
group.add(door);
return group;
}
function createSkyscraper(x, z, width, height, depth) {
const group = new THREE.Group();
// Main tower
const towerGeometry = new THREE.BoxGeometry(width, height * 2, depth);
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0x4F4F4F });
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.set(x, height, z);
tower.castShadow = true;
tower.receiveShadow = true;
group.add(tower);
// Roof
const roofGeometry = new THREE.BoxGeometry(width * 0.8, height * 0.3, depth * 0.8);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.set(x, height + height * 2 + height * 0.15, z);
roof.castShadow = true;
group.add(roof);
// Windows
const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.05);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0xE0F7FF });
const numRows = 10;
const numCols = 6;
const windowSpacingX = width / (numCols + 1);
const windowSpacingZ = depth / (numRows + 1);
// Front side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z + depth/2 - 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Left side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z - depth/2 + 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Right side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x + width/2 - windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z + depth/2 - 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Back side
for (let i = 0; i < numCols; i++) {
for (let j = 0; j < numRows; j++) {
if (j % 2 === 0) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(
x - width/2 + windowSpacingX * (i + 1),
height/2 + height * 0.4 + windowSpacingZ * (j + 1),
z - depth/2 + 0.1
);
window.castShadow = true;
group.add(window);
}
}
}
// Antenna
const antennaGeometry = new THREE.CylinderGeometry(0.1, 0.05, 2, 8);
const antennaMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
const antenna = new THREE.Mesh(antennaGeometry, antennaMaterial);
antenna.position.set(x, height + height * 2 + 1, z);
antenna.castShadow = true;
group.add(antenna);
return group;
}
function createPlayer() {
// Create a simple player object (camera with a slight offset)
player = new THREE.Group();
scene.add(player);
// Camera is already defined, we'll just parent it to player
player.add(camera);
// Add a simple representation of the player
const playerGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8);
const playerMaterial = new THREE.MeshStandardMaterial({ color: 0x3498db });
const playerBody = new THREE.Mesh(playerGeometry, playerMaterial);
playerBody.rotation.x = Math.PI / 2;
playerBody.position.set(0, 0.4, 0);
player.add(playerBody);
// Initial position
player.position.set(0, 2, 5);
}
function createInitialBuildings() {
// Add some initial buildings to give the town a start
const buildingsData = [
{type: 'house', x: 10, z: 10, size: 'medium'},
{type: 'store', x: -12, z: 8, size: 'medium'},
{type: 'apartment', x: 8, z: -12, size: 'medium'},
{type: 'church', x: -10, z: -10, size: 'medium'},
{type: 'house', x: 15, z: -5, size: 'small'},
{type: 'factory', x: -15, z: 5, size: 'medium'},
{type: 'skyscraper', x: 0, z: -18, size: 'large'},
{type: 'house', x: 5, z: -18, size: 'small'}
];
buildingsData.forEach(building => {
const key = `${building.x},${building.z}`;
if (!grid[key]) {
const height = 5;
const width = 4;
const depth = 4;
let buildingMesh;
switch(building.type) {
case 'house':
buildingMesh = createHouse(building.x, building.z, width, height, depth);
break;
case 'store':
buildingMesh = createStore(building.x, building.z, width, height, depth);
break;
case 'apartment':
buildingMesh = createApartment(building.x, building.z, width, height, depth);
break;
case 'factory':
buildingMesh = createFactory(building.x, building.z, width, height, depth);
break;
case 'church':
buildingMesh = createChurch(building.x, building.z, width, height, depth);
break;
case 'skyscraper':
buildingMesh = createSkyscraper(building.x, building.z, width, height, depth);
break;
}
if (buildingMesh) {
scene.add(buildingMesh);
buildings.push({
mesh: buildingMesh,
x: building.x,
z: building.z,
type: building.type,
size: building.size
});
grid[key] = 'building';
}
}
});
}
function createInitialRoads() {
// Create a basic road network
const roadSegments = [
// Main road
{x: -20, z: 0},
{x: -15, z: 0},
{x: -10, z: 0},
{x: -5, z: 0},
{x: 0, z: 0},
{x: 5, z: 0},
{x: 10, z: 0},
{x: 15, z: 0},
{x: 20, z: 0},
// Crossroads
{x: 0, z: -20},
{x: 0, z: -15},
{x: 0, z: -10},
{x: 0, z: -5},
{x: 0, z: 5},
{x: 0, z: 10},
{x: 0, z: 15},
{x: 0, z: 20},
// Side streets
{x: -10, z: -10},
{x: -5, z: -10},
{x: 0, z: -10},
{x: 5, z: -10},
{x: 10, z: -10},
{x: -10, z: 10},
{x: -5, z: 10},
{x: 0, z: 10},
{x: 5, z: 10},
{x: 10, z: 10}
];
roadSegments.forEach(segment => {
placeRoadAt(segment.x, segment.z);
});
}
function createRoadGrid() {
// Create a grid for placing roads
for (let x = -gridSize; x <= gridSize; x += cellSize) {
for (let z = -gridSize; z <= gridSize; z += cellSize) {
const key = `${x},${z}`;
grid[key] = null;
}
}
}
function createTraffic() {
// Create traffic vehicles
const trafficCount = 8;
for (let i = 0; i < trafficCount; i++) {
const roadSegment = roads[Math.floor(Math.random() * roads.length)];
if (!roadSegment) continue;
// Create a car
const carGeometry = new THREE.BoxGeometry(0.8, 0.5, 1.5);
const carMaterial = new THREE.MeshStandardMaterial({
color: Math.random() * 0xffffff
});
const car = new THREE.Mesh(carGeometry, carMaterial);
// Position the car on the road
car.position.set(roadSegment.x, 0.25, roadSegment.z);
// Get a random direction based on road segment
let direction;
const roadIndex = roads.indexOf(roadSegment);
const nextRoadIndex = (roadIndex + 1) % roads.length;
const nextRoad = roads[nextRoadIndex];
if (nextRoad) {
// Calculate direction vector
const dx = nextRoad.x - roadSegment.x;
const dz = nextRoad.z - roadSegment.z;
direction = new THREE.Vector3(dx, 0, dz).normalize();
} else {
// Default direction
direction = new THREE.Vector3(1, 0, 0);
}
// Rotate car to face direction
const angle = Math.atan2(direction.z, direction.x);
car.rotation.y = angle;
// Add to scene
scene.add(car);
// Store traffic data
traffic.push({
mesh: car,
position: new THREE.Vector3(roadSegment.x, 0.25, roadSegment.z),
direction: direction,
speed: 0.05 * trafficSpeed,
roadIndex: roadIndex
});
}
}
function updateTraffic() {
if (!trafficEnabled) return;
traffic.forEach(car => {
// Move car along its direction
car.position.x += car.direction.x * car.speed;
car.position.z += car.direction.z * car.speed;
// Update car rotation to face direction
const angle = Math.atan2(car.direction.z, car.direction.x);
car.mesh.rotation.y = angle;
// Check if car has reached the end of a road segment
// This is a simplified approach - in a real game, you'd use pathfinding
const road = roads.find(r =>
Math.abs(r.x - car.position.x) < 1 &&
Math.abs(r.z - car.position.z) < 1
);
if (road) {
const roadIndex = roads.indexOf(road);
// Check for next road segment in the network
const adjacentRoads = roads.filter(r =>
(Math.abs(r.x - road.x) < cellSize * 1.5 && Math.abs(r.z - road.z) < cellSize * 1.5) &&
(!(Math.abs(r.x - road.x) < 0.5 && Math.abs(r.z - road.z) < 0.5)) // Not same road
);
if (adjacentRoads.length > 0) {
// Simple pathfinding: pick a random adjacent road
const nextRoad = adjacentRoads[Math.floor(Math.random() * adjacentRoads.length)];
const dx = nextRoad.x - car.position.x;
const dz = nextRoad.z - car.position.z;
car.direction.set(dx, 0, dz).normalize();
}
}
// Check if car is out of bounds and needs to be repositioned
if (Math.abs(car.position.x) > gridSize + 5 || Math.abs(car.position.z) > gridSize + 5) {
// Reposition car to a random road segment
const randomRoad = roads[Math.floor(Math.random() * roads.length)];
if (randomRoad) {
car.position.set(randomRoad.x, 0.25, randomRoad.z);
const nextRoad = roads[(roads.indexOf(randomRoad) + 1) % roads.length];
if (nextRoad) {
const dx = nextRoad.x - randomRoad.x;
const dz = nextRoad.z - randomRoad.z;
car.direction.set(dx, 0, dz).normalize();
}
}
}
});
}
function clearAll() {
// Remove all buildings
buildings.forEach(building => {
scene.remove(building.mesh);
});
buildings = [];
// Remove all roads
roads.forEach(road => {
scene.remove(road.mesh);
scene.remove(road.lane1);
scene.remove(road.lane2);
});
roads = [];