Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Futuristic 3D City Simulation</title>
- <style>
- body {
- margin: 0;
- overflow: hidden;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background-color: #000;
- }
- canvas {
- display: block;
- }
- #controls {
- position: absolute;
- bottom: 20px;
- left: 20px;
- background: rgba(0, 0, 0, 0.7);
- padding: 15px;
- border-radius: 10px;
- color: white;
- backdrop-filter: blur(5px);
- box-shadow: 0 0 15px rgba(0, 100, 255, 0.5);
- border: 1px solid rgba(0, 150, 255, 0.3);
- z-index: 100;
- }
- #title {
- position: absolute;
- top: 20px;
- left: 20px;
- color: white;
- font-size: 24px;
- text-shadow: 0 0 10px rgba(0, 150, 255, 0.8);
- }
- .slider-container {
- margin: 15px 0;
- }
- label {
- display: inline-block;
- width: 150px;
- color: #00bfff;
- font-weight: 500;
- }
- input[type="range"] {
- width: 200px;
- vertical-align: middle;
- -webkit-appearance: none;
- background: rgba(0, 100, 200, 0.3);
- height: 8px;
- border-radius: 4px;
- }
- input[type="range"]::-webkit-slider-thumb {
- -webkit-appearance: none;
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: #00bfff;
- cursor: pointer;
- box-shadow: 0 0 5px rgba(0, 191, 255, 0.8);
- }
- .value-display {
- display: inline-block;
- width: 60px;
- text-align: right;
- margin-left: 10px;
- color: #fff;
- font-family: monospace;
- font-size: 14px;
- }
- #loading {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: black;
- display: flex;
- justify-content: center;
- align-items: center;
- color: #00bfff;
- font-size: 24px;
- z-index: 1000;
- }
- .power-by {
- position: absolute;
- bottom: 10px;
- right: 10px;
- color: rgba(255, 255, 255, 0.3);
- font-size: 12px;
- }
- </style>
- </head>
- <body>
- <div id="loading">Loading city simulation...</div>
- <div id="title">NEOCITY 2077</div>
- <div id="controls">
- <div class="slider-container">
- <label for="timeSlider">Time of Day:</label>
- <input type="range" id="timeSlider" min="0" max="24" value="12" step="0.1">
- <span class="value-display" id="timeValue">12:00</span>
- </div>
- <div class="slider-container">
- <label for="trafficSlider">Traffic Density:</label>
- <input type="range" id="trafficSlider" min="0" max="1" value="0.5" step="0.01">
- <span class="value-display" id="trafficValue">50%</span>
- </div>
- <div class="slider-container">
- <label for="fogSlider">Fog Intensity:</label>
- <input type="range" id="fogSlider" min="0" max="0.01" value="0.001" step="0.0001">
- <span class="value-display" id="fogValue">10%</span>
- </div>
- <div class="slider-container">
- <label for="neonSlider">Neon Intensity:</label>
- <input type="range" id="neonSlider" min="0" max="2" value="1" step="0.01">
- <span class="value-display" id="neonValue">50%</span>
- </div>
- <div class="slider-container">
- <label for="cameraHeightSlider">Camera Height:</label>
- <input type="range" id="cameraHeightSlider" min="50" max="600" value="200" step="10">
- <span class="value-display" id="cameraHeightValue">200</span>
- </div>
- <div class="slider-container">
- <label for="rotationSpeedSlider">Rotation Speed:</label>
- <input type="range" id="rotationSpeedSlider" min="0" max="0.01" value="0.0005" step="0.0001">
- <span class="value-display" id="rotationSpeedValue">50%</span>
- </div>
- </div>
- <div class="power-by">Powered by three.js</div>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
- <script>
- // Main variables
- let scene, camera, renderer, controls;
- let city = new THREE.Group();
- let vehicles = new THREE.Group();
- let neonLights = new THREE.Group();
- let skyDome;
- let sunLight, ambientLight, moonLight;
- let cameraAngle = 0;
- // Settings controlled by sliders
- const settings = {
- timeOfDay: 12, // 0-24 hours
- trafficDensity: 0.5, // 0-1
- fogIntensity: 0.001, // 0-0.01
- neonIntensity: 1, // 0-2
- cameraHeight: 200, // 50-600
- rotationSpeed: 0.0005 // 0-0.01
- };
- // City parameters
- const CITY_SIZE = 1000;
- const BLOCK_SIZE = 50;
- const STREET_WIDTH = 20;
- const MAX_BUILDING_HEIGHT = 300;
- const MIN_BUILDING_HEIGHT = 30;
- const BUILDING_DENSITY = 0.8;
- // Materials
- let buildingMaterials = [];
- let roadMaterial;
- let neonMaterials = [];
- // Initialize the scene
- function init() {
- // Create scene
- scene = new THREE.Scene();
- scene.fog = new THREE.FogExp2(0x93a5d6, settings.fogIntensity);
- // Create camera
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
- camera.position.set(0, settings.cameraHeight, 400);
- camera.lookAt(0, 100, 0);
- // Create 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);
- // Setup materials
- setupMaterials();
- // Create skybox
- createSkyDome();
- // Create lighting
- setupLighting();
- // Create ground plane
- createGround();
- // Generate city
- generateCity();
- scene.add(city);
- // Generate vehicles
- generateVehicles();
- scene.add(vehicles);
- // Add neon lights
- scene.add(neonLights);
- // Set up event listeners
- setupEventListeners();
- // Hide loading screen
- setTimeout(() => {
- document.getElementById('loading').style.display = 'none';
- }, 1000);
- // Start animation loop
- animate();
- }
- function setupMaterials() {
- // Building materials with different colors
- const buildingColors = [
- 0x505050, 0x606060, 0x707070, 0x808080, 0x909090,
- 0x404860, 0x384870, 0x2a3b62, 0x1e2d54, 0x152046
- ];
- buildingColors.forEach(color => {
- buildingMaterials.push(
- new THREE.MeshPhongMaterial({
- color: color,
- flatShading: true,
- shininess: 50
- })
- );
- });
- // Glass material for buildings
- buildingMaterials.push(
- new THREE.MeshPhongMaterial({
- color: 0x88ccff,
- specular: 0xffffff,
- shininess: 100,
- transparent: true,
- opacity: 0.3
- })
- );
- // Road material
- roadMaterial = new THREE.MeshPhongMaterial({
- color: 0x101010,
- shininess: 10
- });
- // Neon materials (for night lighting)
- const neonColors = [0xff0066, 0x00ffff, 0xffff00, 0x00ff00, 0xff00ff];
- neonColors.forEach(color => {
- neonMaterials.push(
- new THREE.MeshBasicMaterial({
- color: color,
- transparent: true,
- opacity: 0.9
- })
- );
- });
- }
- function createSkyDome() {
- const skyGeometry = new THREE.SphereGeometry(1500, 32, 32);
- // Invert the geometry so we see it from the inside
- skyGeometry.scale(-1, 1, 1);
- const skyMaterial = new THREE.ShaderMaterial({
- uniforms: {
- topColor: { value: new THREE.Color(0x0077ff) },
- bottomColor: { value: new THREE.Color(0xffffff) },
- offset: { value: 400 },
- exponent: { value: 0.6 },
- timeOfDay: { value: settings.timeOfDay }
- },
- vertexShader: `
- varying vec3 vWorldPosition;
- void main() {
- vec4 worldPosition = modelMatrix * vec4(position, 1.0);
- vWorldPosition = worldPosition.xyz;
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
- }
- `,
- fragmentShader: `
- uniform vec3 topColor;
- uniform vec3 bottomColor;
- uniform float offset;
- uniform float exponent;
- uniform float timeOfDay;
- varying vec3 vWorldPosition;
- void main() {
- float h = normalize(vWorldPosition + offset).y;
- // Night colors
- vec3 nightTop = vec3(0.0, 0.0, 0.1);
- vec3 nightBottom = vec3(0.0, 0.0, 0.2);
- // Dawn/dusk colors
- vec3 dawnTop = vec3(0.7, 0.3, 0.2);
- vec3 dawnBottom = vec3(0.4, 0.2, 0.3);
- // Day colors
- vec3 dayTop = topColor;
- vec3 dayBottom = bottomColor;
- vec3 finalTopColor, finalBottomColor;
- if (timeOfDay < 6.0 || timeOfDay > 18.0) {
- // Night
- finalTopColor = nightTop;
- finalBottomColor = nightBottom;
- } else if (timeOfDay < 8.0) {
- // Dawn
- float blend = (timeOfDay - 6.0) / 2.0;
- finalTopColor = mix(dawnTop, dayTop, blend);
- finalBottomColor = mix(dawnBottom, dayBottom, blend);
- } else if (timeOfDay > 16.0) {
- // Dusk
- float blend = (18.0 - timeOfDay) / 2.0;
- finalTopColor = mix(dawnTop, dayTop, blend);
- finalBottomColor = mix(dawnBottom, dayBottom, blend);
- } else {
- // Day
- finalTopColor = dayTop;
- finalBottomColor = dayBottom;
- }
- vec3 skyColor = mix(finalBottomColor, finalTopColor, pow(max(h, 0.0), exponent));
- // Add stars at night
- if (timeOfDay < 6.0 || timeOfDay > 18.0) {
- float r = fract(sin(dot(vWorldPosition.xyz, vec3(12.9898, 78.233, 53.539))) * 43758.5453);
- if (r > 0.997) {
- float starIntensity = (timeOfDay < 3.0 || timeOfDay > 21.0) ? 1.0 :
- (timeOfDay < 6.0) ? (3.0 - timeOfDay) / 3.0 :
- (24.0 - timeOfDay) / 3.0;
- skyColor += vec3(1.0, 1.0, 1.0) * starIntensity;
- }
- }
- gl_FragColor = vec4(skyColor, 1.0);
- }
- `,
- side: THREE.BackSide
- });
- skyDome = new THREE.Mesh(skyGeometry, skyMaterial);
- scene.add(skyDome);
- }
- function setupLighting() {
- // Ambient light
- ambientLight = new THREE.AmbientLight(0x404040);
- scene.add(ambientLight);
- // Sun directional light
- sunLight = new THREE.DirectionalLight(0xffffee, 1);
- sunLight.position.set(100, 300, 100);
- sunLight.castShadow = true;
- sunLight.shadow.camera.left = -500;
- sunLight.shadow.camera.right = 500;
- sunLight.shadow.camera.top = 500;
- sunLight.shadow.camera.bottom = -500;
- sunLight.shadow.camera.far = 1500;
- sunLight.shadow.mapSize.width = 2048;
- sunLight.shadow.mapSize.height = 2048;
- scene.add(sunLight);
- // Moon light
- moonLight = new THREE.DirectionalLight(0x8080aa, 0.5);
- moonLight.position.set(-100, 300, -100);
- moonLight.intensity = 0; // Start with moon turned off (daytime)
- scene.add(moonLight);
- updateLighting();
- }
- function createGround() {
- const groundGeometry = new THREE.PlaneGeometry(CITY_SIZE * 2, CITY_SIZE * 2);
- const groundMaterial = new THREE.MeshPhongMaterial({
- color: 0x1a1a1a,
- shininess: 10
- });
- const ground = new THREE.Mesh(groundGeometry, groundMaterial);
- ground.rotation.x = -Math.PI / 2;
- ground.position.y = -0.1;
- ground.receiveShadow = true;
- scene.add(ground);
- }
- function generateCity() {
- const halfCity = CITY_SIZE / 2;
- // Create grid of blocks
- for (let x = -halfCity; x < halfCity; x += BLOCK_SIZE + STREET_WIDTH) {
- for (let z = -halfCity; z < halfCity; z += BLOCK_SIZE + STREET_WIDTH) {
- createCityBlock(x, z);
- }
- }
- // Create main highways
- createHighways();
- }
- function createCityBlock(x, z) {
- const blockGroup = new THREE.Group();
- // Determine if this is a special district (downtown, residential, etc)
- const distanceFromCenter = Math.sqrt(x*x + z*z);
- let districtType;
- if (distanceFromCenter < 200) {
- districtType = 'downtown'; // Downtown - Tall skyscrapers
- } else if (distanceFromCenter < 400) {
- districtType = 'midtown'; // Midtown - Mix of heights
- } else {
- districtType = 'outskirts'; // Outskirts - Lower buildings
- }
- // Number of buildings to place in this block
- const maxBuildings = Math.floor(BLOCK_SIZE / 15) ** 2;
- const numBuildings = Math.floor(maxBuildings * BUILDING_DENSITY);
- // Create buildings within this block
- for (let i = 0; i < numBuildings; i++) {
- // Random position within block
- const bx = x + Math.random() * BLOCK_SIZE - BLOCK_SIZE/2;
- const bz = z + Math.random() * BLOCK_SIZE - BLOCK_SIZE/2;
- // Determine building properties based on district
- let height, width, depth;
- if (districtType === 'downtown') {
- // Downtown - Tall skyscrapers
- height = MIN_BUILDING_HEIGHT + Math.random() * (MAX_BUILDING_HEIGHT - MIN_BUILDING_HEIGHT);
- width = 10 + Math.random() * 20;
- depth = 10 + Math.random() * 20;
- // Special case for super tall buildings in the very center
- if (distanceFromCenter < 100 && Math.random() < 0.3) {
- height *= 1.5;
- // Make a fancy top for the tallest buildings
- createSkyscraper(bx, bz, width, depth, height, blockGroup);
- continue;
- }
- } else if (districtType === 'midtown') {
- // Midtown - Medium height
- height = MIN_BUILDING_HEIGHT + Math.random() * (MAX_BUILDING_HEIGHT/2);
- width = 15 + Math.random() * 15;
- depth = 15 + Math.random() * 15;
- } else {
- // Outskirts - Lower buildings
- height = MIN_BUILDING_HEIGHT/2 + Math.random() * MIN_BUILDING_HEIGHT;
- width = 10 + Math.random() * 25;
- depth = 10 + Math.random() * 25;
- }
- createBuilding(bx, bz, width, depth, height, blockGroup);
- }
- // Add neon signs in downtown and midtown
- if ((districtType === 'downtown' || districtType === 'midtown') && Math.random() < 0.3) {
- createNeonSign(x, z, blockGroup);
- }
- city.add(blockGroup);
- }
- function createBuilding(x, z, width, depth, height, parent) {
- // Main building body
- const geometry = new THREE.BoxGeometry(width, height, depth);
- // Randomly select material
- const materialIndex = Math.floor(Math.random() * buildingMaterials.length);
- const material = buildingMaterials[materialIndex];
- const building = new THREE.Mesh(geometry, material);
- building.position.set(x, height/2, z);
- building.castShadow = true;
- building.receiveShadow = true;
- parent.add(building);
- // Add windows (window texture would be better, but for simplicity we'll add some geometry)
- if (Math.random() < 0.7) {
- addBuildingWindows(building, width, depth, height);
- }
- // Sometimes add antennas or water towers to tops of buildings
- if (Math.random() < 0.3) {
- addBuildingFeatures(x, z, width, depth, height, parent);
- }
- return building;
- }
- function addBuildingWindows(building, width, depth, height) {
- // Window material - this will be affected by time of day
- const windowMaterial = new THREE.MeshPhongMaterial({
- color: 0xffffee,
- emissive: 0x333311,
- transparent: true,
- opacity: 0.7
- });
- // Track the window object for later updates
- building.windows = [];
- // Add windows on each side of the building
- const sides = [
- { dir: 'front', x: 0, z: depth/2 + 0.1, w: width-2, h: height-2 },
- { dir: 'back', x: 0, z: -depth/2 - 0.1, w: width-2, h: height-2 },
- { dir: 'left', x: width/2 + 0.1, z: 0, w: depth-2, h: height-2 },
- { dir: 'right', x: -width/2 - 0.1, z: 0, w: depth-2, h: height-2 }
- ];
- sides.forEach(side => {
- // Number of windows based on building size
- const rows = Math.floor(side.h / 6);
- const cols = Math.floor(side.w / 4);
- const windowWidth = 2;
- const windowHeight = 3;
- for (let row = 0; row < rows; row++) {
- for (let col = 0; col < cols; col++) {
- // Randomly skip some windows
- if (Math.random() < 0.2) continue;
- const windowGeom = new THREE.PlaneGeometry(windowWidth, windowHeight);
- const windowObj = new THREE.Mesh(windowGeom, windowMaterial.clone());
- // Position within the wall
- let wx, wy, wz, rx, ry, rz;
- wy = (row + 0.5) * (side.h / rows) - side.h/2 + 2;
- if (side.dir === 'front' || side.dir === 'back') {
- wx = (col + 0.5) * (side.w / cols) - side.w/2;
- wz = side.z;
- ry = Math.PI;
- if (side.dir === 'back') ry = 0;
- } else {
- wz = (col + 0.5) * (side.w / cols) - side.w/2;
- wx = side.x;
- ry = -Math.PI/2;
- if (side.dir === 'right') ry = Math.PI/2;
- }
- windowObj.position.set(wx, wy, wz);
- windowObj.rotation.y = ry;
- // Store window for day/night updates
- building.windows.push(windowObj);
- building.add(windowObj);
- }
- }
- });
- }
- function addBuildingFeatures(x, z, width, depth, height, parent) {
- const featureType = Math.random();
- if (featureType < 0.4) {
- // Antenna
- const antennaHeight = 10 + Math.random() * 20;
- const antennaGeom = new THREE.CylinderGeometry(0.5, 0.5, antennaHeight);
- const antennaMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
- const antenna = new THREE.Mesh(antennaGeom, antennaMat);
- antenna.position.set(x, height + antennaHeight/2, z);
- antenna.castShadow = true;
- parent.add(antenna);
- // Add blinking light at top
- const blinkingLight = new THREE.PointLight(0xff0000, 0.5, 30);
- blinkingLight.position.set(x, height + antennaHeight + 1, z);
- parent.add(blinkingLight);
- // Blinking animation
- const blinkRate = 0.5 + Math.random() * 0.5;
- blinkingLight.userData = { blinkRate };
- } else if (featureType < 0.7) {
- // Water tower
- const towerHeight = 5 + Math.random() * 5;
- const tankRadius = 3 + Math.random() * 2;
- const tankHeight = 4 + Math.random() * 3;
- // Legs
- const legMat = new THREE.MeshPhongMaterial({ color: 0x444444 });
- for (let i = 0; i < 4; i++) {
- const legGeom = new THREE.CylinderGeometry(0.5, 0.5, towerHeight);
- const leg = new THREE.Mesh(legGeom, legMat);
- const angle = (i / 4) * Math.PI * 2;
- const lx = x + Math.cos(angle) * tankRadius/1.5;
- const lz = z + Math.sin(angle) * tankRadius/1.5;
- leg.position.set(lx, height + towerHeight/2, lz);
- leg.castShadow = true;
- parent.add(leg);
- }
- // Tank
- const tankGeom = new THREE.CylinderGeometry(tankRadius, tankRadius, tankHeight, 16);
- const tankMat = new THREE.MeshPhongMaterial({ color: 0x777777 });
- const tank = new THREE.Mesh(tankGeom, tankMat);
- tank.position.set(x, height + towerHeight + tankHeight/2, z);
- tank.castShadow = true;
- parent.add(tank);
- } else {
- // HVAC units
- const numUnits = 1 + Math.floor(Math.random() * 4);
- const hvacMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
- for (let i = 0; i < numUnits; i++) {
- const unitSize = 2 + Math.random() * 3;
- const hvacGeom = new THREE.BoxGeometry(unitSize, unitSize, unitSize);
- const hvac = new THREE.Mesh(hvacGeom, hvacMat);
- const hx = x + (Math.random() - 0.5) * (width - unitSize);
- const hz = z + (Math.random() - 0.5) * (depth - unitSize);
- hvac.position.set(hx, height + unitSize/2, hz);
- hvac.castShadow = true;
- parent.add(hvac);
- }
- }
- }
- function createSkyscraper(x, z, width, depth, height, parent) {
- // Base building
- createBuilding(x, z, width, depth, height * 0.9, parent);
- // Add a tapered top
- const topHeight = height * 0.2;
- const topWidth = width * 0.6;
- const topDepth = depth * 0.6;
- const topGeometry = new THREE.BoxGeometry(topWidth, topHeight, topDepth);
- const topMaterial = buildingMaterials[Math.floor(Math.random() * buildingMaterials.length)];
- const top = new THREE.Mesh(topGeometry, topMaterial);
- top.position.set(x, height * 0.9 + topHeight/2, z);
- top.castShadow = true;
- parent.add(top);
- // Add spire
- const spireHeight = height * 0.15;
- const spireGeometry = new THREE.CylinderGeometry(0, topWidth/8, spireHeight, 4);
- const spireMaterial = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
- const spire = new THREE.Mesh(spireGeometry, spireMaterial);
- spire.position.set(x, height * 0.9 + topHeight + spireHeight/2, z);
- spire.castShadow = true;
- parent.add(spire);
- // Add aircraft warning light
- const light = new THREE.PointLight(0xff0000, 1, 50);
- light.position.set(x, height * 0.9 + topHeight + spireHeight + 2, z);
- parent.add(light);
- // Add light blinking animation
- light.userData = { blinkRate: 0.5 };
- }
- function createNeonSign(x, z, parent) {
- const height = 30 + Math.random() * 50;
- const width = 10 + Math.random() * 30;
- // Base structure
- const baseGeom = new THREE.BoxGeometry(width/10, height, width/10);
- const baseMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
- const base = new THREE.Mesh(baseGeom, baseMat);
- base.position.set(x, height/2, z);
- base.castShadow = true;
- parent.add(base);
- // Sign
- const signGeom = new THREE.BoxGeometry(width, height/4, width/15);
- const signMat = new THREE.MeshPhongMaterial({ color: 0x333333 });
- const sign = new THREE.Mesh(signGeom, signMat);
- sign.position.set(x, height * 0.8, z + width/20);
- sign.castShadow = true;
- parent.add(sign);
- // Neon text
- const neonMat = neonMaterials[Math.floor(Math.random() * neonMaterials.length)];
- // Generate random neon pattern
- const pattern = Math.floor(Math.random() * 5);
- let neonGeom;
- if (pattern === 0) {
- // Circle
- neonGeom = new THREE.TorusGeometry(width/3, width/30, 16, 32);
- const neon = new THREE.Mesh(neonGeom, neonMat);
- neon.position.set(x, height * 0.8, z + width/10);
- neon.userData = { type: 'neon' };
- neonLights.add(neon);
- } else if (pattern === 1) {
- // Straight lines
- const numLines = 3 + Math.floor(Math.random() * 3);
- for (let i = 0; i < numLines; i++) {
- neonGeom = new THREE.BoxGeometry(width * 0.8, width/30, width/30);
- const neon = new THREE.Mesh(neonGeom, neonMat);
- neon.position.set(x, height * 0.8 - i * width/8, z + width/10);
- neon.userData = { type: 'neon' };
- neonLights.add(neon);
- }
- } else if (pattern === 2) {
- // Cross
- neonGeom = new THREE.BoxGeometry(width * 0.6, width/30, width/30);
- const neonH = new THREE.Mesh(neonGeom, neonMat);
- neonH.position.set(x, height * 0.8, z + width/10);
- neonH.userData = { type: 'neon' };
- neonLights.add(neonH);
- neonGeom = new THREE.BoxGeometry(width/30, width * 0.6, width/30);
- const neonV = new THREE.Mesh(neonGeom, neonMat);
- neonV.position.set(x, height * 0.8, z + width/10);
- neonV.userData = { type: 'neon' };
- neonLights.add(neonV);
- } else if (pattern === 3) {
- // Triangle
- const triangleShape = new THREE.Shape();
- const s = width/3;
- triangleShape.moveTo(0, s);
- triangleShape.lineTo(-s, -s);
- triangleShape.lineTo(s, -s);
- triangleShape.lineTo(0, s);
- const triangleGeom = new THREE.ShapeGeometry(triangleShape);
- const triangleExtrudeSettings = {
- steps: 1,
- depth: width/30,
- bevelEnabled: false
- };
- neonGeom = new THREE.ExtrudeGeometry(triangleShape, triangleExtrudeSettings);
- const neon = new THREE.Mesh(neonGeom, neonMat);
- neon.position.set(x, height * 0.8, z + width/10);
- neon.userData = { type: 'neon' };
- neonLights.add(neon);
- } else {
- // Star
- const starShape = new THREE.Shape();
- const s = width/4;
- for (let i = 0; i < 5; i++) {
- const angleStar = (i * 2 * Math.PI / 5) - Math.PI/2;
- const angleStarInner = ((i + 0.5) * 2 * Math.PI / 5) - Math.PI/2;
- const x1 = Math.cos(angleStar) * s;
- const y1 = Math.sin(angleStar) * s;
- const x2 = Math.cos(angleStarInner) * (s/2);
- const y2 = Math.sin(angleStarInner) * (s/2);
- if (i === 0) {
- starShape.moveTo(x1, y1);
- } else {
- starShape.lineTo(x1, y1);
- }
- starShape.lineTo(x2, y2);
- }
- starShape.lineTo(Math.cos(-Math.PI/2) * s, Math.sin(-Math.PI/2) * s);
- const starExtrudeSettings = {
- steps: 1,
- depth: width/30,
- bevelEnabled: false
- };
- neonGeom = new THREE.ExtrudeGeometry(starShape, starExtrudeSettings);
- const neon = new THREE.Mesh(neonGeom, neonMat);
- neon.position.set(x, height * .8, z + width/10);
- neon.userData = { type: 'neon' };
- neonLights.add(neon);
- }
- }
- function createHighways() {
- const halfCity = CITY_SIZE / 2;
- // Main highway crossings
- const highways = [
- { start: { x: -halfCity, z: 0 }, end: { x: halfCity, z: 0 } },
- { start: { x: 0, z: -halfCity }, end: { x: 0, z: halfCity } },
- { start: { x: -halfCity, z: -halfCity/2 }, end: { x: halfCity, z: -halfCity/2 } },
- { start: { x: -halfCity, z: halfCity/2 }, end: { x: halfCity, z: halfCity/2 } },
- { start: { x: -halfCity/2, z: -halfCity }, end: { x: -halfCity/2, z: halfCity } },
- { start: { x: halfCity/2, z: -halfCity }, end: { x: halfCity/2, z: halfCity } }
- ];
- // Add circular highway
- const ringRadius = halfCity * 0.7;
- const ringSegments = 32;
- for (let i = 0; i < ringSegments; i++) {
- const angle1 = (i / ringSegments) * Math.PI * 2;
- const angle2 = ((i + 1) / ringSegments) * Math.PI * 2;
- const x1 = Math.cos(angle1) * ringRadius;
- const z1 = Math.sin(angle1) * ringRadius;
- const x2 = Math.cos(angle2) * ringRadius;
- const z2 = Math.sin(angle2) * ringRadius;
- highways.push({
- start: { x: x1, z: z1 },
- end: { x: x2, z: z2 }
- });
- }
- // Create each highway segment
- highways.forEach(highway => {
- createHighwaySegment(highway.start, highway.end);
- });
- }
- function createHighwaySegment(start, end) {
- // Calculate dimensions
- const dx = end.x - start.x;
- const dz = end.z - start.z;
- const length = Math.sqrt(dx * dx + dz * dz);
- const width = STREET_WIDTH * 1.5;
- // Create road surface
- const roadGeom = new THREE.PlaneGeometry(length, width);
- const road = new THREE.Mesh(roadGeom, roadMaterial);
- // Position and rotate to align with endpoints
- road.position.set((start.x + end.x) / 2, 1, (start.z + end.z) / 2);
- road.rotation.x = -Math.PI / 2;
- road.rotation.z = Math.atan2(dz, dx);
- road.receiveShadow = true;
- city.add(road);
- // Add lane markings
- const laneMarkingGeom = new THREE.PlaneGeometry(length - 4, 0.5);
- const laneMarkingMat = new THREE.MeshBasicMaterial({
- color: 0xFFFFFF,
- transparent: true,
- opacity: 0.8
- });
- const laneMarking = new THREE.Mesh(laneMarkingGeom, laneMarkingMat);
- laneMarking.position.set((start.x + end.x) / 2, 1.1, (start.z + end.z) / 2);
- laneMarking.rotation.x = -Math.PI / 2;
- laneMarking.rotation.z = Math.atan2(dz, dx);
- city.add(laneMarking);
- // Add barriers on either side
- const barrierGeom = new THREE.BoxGeometry(length, 2, 0.5);
- const barrierMat = new THREE.MeshPhongMaterial({ color: 0x999999 });
- const angle = Math.atan2(dz, dx);
- const offsetX = Math.sin(angle) * (width / 2);
- const offsetZ = -Math.cos(angle) * (width / 2);
- // Left barrier
- const leftBarrier = new THREE.Mesh(barrierGeom, barrierMat);
- leftBarrier.position.set(
- (start.x + end.x) / 2 + offsetX,
- 2,
- (start.z + end.z) / 2 + offsetZ
- );
- leftBarrier.rotation.y = angle;
- leftBarrier.castShadow = true;
- leftBarrier.receiveShadow = true;
- city.add(leftBarrier);
- // Right barrier
- const rightBarrier = new THREE.Mesh(barrierGeom, barrierMat);
- rightBarrier.position.set(
- (start.x + end.x) / 2 - offsetX,
- 2,
- (start.z + end.z) / 2 - offsetZ
- );
- rightBarrier.rotation.y = angle;
- rightBarrier.castShadow = true;
- rightBarrier.receiveShadow = true;
- city.add(rightBarrier);
- // Add support columns for elevated highway
- const numColumns = Math.floor(length / 50);
- for (let i = 0; i <= numColumns; i++) {
- const t = i / numColumns;
- const columnX = start.x + dx * t;
- const columnZ = start.z + dz * t;
- const columnHeight = 20 + Math.random() * 5;
- const columnGeom = new THREE.BoxGeometry(4, columnHeight, 4);
- const columnMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
- const column = new THREE.Mesh(columnGeom, columnMat);
- column.position.set(columnX, columnHeight / 2, columnZ);
- column.castShadow = true;
- column.receiveShadow = true;
- city.add(column);
- }
- // For wider highways, add light posts
- if (Math.random() < 0.8) {
- const numLights = Math.floor(length / 40);
- for (let i = 1; i < numLights; i++) {
- const t = i / numLights;
- const lightX = start.x + dx * t;
- const lightZ = start.z + dz * t;
- createLightPost(
- lightX + offsetX * 0.7,
- lightZ + offsetZ * 0.7,
- angle
- );
- }
- }
- return { start, end, length, angle };
- }
- function createLightPost(x, z, angle) {
- const postHeight = 15;
- const postGeom = new THREE.CylinderGeometry(0.3, 0.3, postHeight);
- const postMat = new THREE.MeshPhongMaterial({ color: 0x555555 });
- const post = new THREE.Mesh(postGeom, postMat);
- post.position.set(x, postHeight / 2, z);
- post.castShadow = true;
- city.add(post);
- // Arm extending over road
- const armLength = 6;
- const armGeom = new THREE.CylinderGeometry(0.2, 0.2, armLength);
- const arm = new THREE.Mesh(armGeom, postMat);
- arm.position.set(0, postHeight / 2, armLength / 2);
- arm.rotation.x = Math.PI / 2;
- post.add(arm);
- // Light
- const lightGeom = new THREE.SphereGeometry(0.8);
- const lightMat = new THREE.MeshBasicMaterial({
- color: 0xffffaa,
- transparent: true,
- opacity: 0.8
- });
- const lightBulb = new THREE.Mesh(lightGeom, lightMat);
- lightBulb.position.set(0, 0, armLength - 1);
- arm.add(lightBulb);
- // Point light
- const light = new THREE.PointLight(0xffffee, 0.8, 20);
- light.position.copy(lightBulb.position);
- light.userData = { type: 'streetLight' };
- arm.add(light);
- // Store for day/night updates
- if (!city.userData.streetLights) {
- city.userData.streetLights = [];
- }
- city.userData.streetLights.push(light);
- return post;
- }
- function generateVehicles() {
- // Clear existing vehicles
- while (vehicles.children.length > 0) {
- vehicles.remove(vehicles.children[0]);
- }
- const targetVehicleCount = Math.floor(200 * settings.trafficDensity);
- // Create new vehicles
- for (let i = 0; i < targetVehicleCount; i++) {
- createVehicle();
- }
- }
- function createVehicle() {
- // Random vehicle type
- const vehicleType = Math.random();
- let length, width, height, color;
- if (vehicleType < 0.7) {
- // Car
- length = 4 + Math.random();
- width = 2;
- height = 1.5;
- // Random car color
- const carColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff, 0xffffff, 0x000000];
- color = carColors[Math.floor(Math.random() * carColors.length)];
- } else if (vehicleType < 0.9) {
- // Truck
- length = 8 + Math.random() * 4;
- width = 2.5;
- height = 3;
- color = 0x999999;
- } else {
- // Bus
- length = 12;
- width = 2.5;
- height = 3;
- color = 0x3388ff;
- }
- // Create vehicle body
- const bodyGeom = new THREE.BoxGeometry(length, height, width);
- const bodyMat = new THREE.MeshPhongMaterial({
- color: color,
- shininess: 80
- });
- const vehicle = new THREE.Mesh(bodyGeom, bodyMat);
- // Add wheels (4 or 6 depending on length)
- const wheelRadius = 0.7;
- const wheelGeom = new THREE.CylinderGeometry(wheelRadius, wheelRadius, 0.5, 8);
- const wheelMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
- const wheelPositions = [
- { x: -length/2 + wheelRadius, z: -width/2 - 0.2 },
- { x: -length/2 + wheelRadius, z: width/2 + 0.2 },
- { x: length/2 - wheelRadius, z: -width/2 - 0.2 },
- { x: length/2 - wheelRadius, z: width/2 + 0.2 }
- ];
- // Add middle wheels for longer vehicles
- if (length > 6) {
- wheelPositions.push({ x: 0, z: -width/2 - 0.2 });
- wheelPositions.push({ x: 0, z: width/2 + 0.2 });
- }
- wheelPositions.forEach(pos => {
- const wheel = new THREE.Mesh(wheelGeom, wheelMat);
- wheel.position.set(pos.x, -height/2, pos.z);
- wheel.rotation.z = Math.PI / 2;
- vehicle.add(wheel);
- });
- // Add windows
- if (vehicleType < 0.9) {
- // Windshield and rear window
- const windowMat = new THREE.MeshPhongMaterial({
- color: 0x88ccff,
- transparent: true,
- opacity: 0.7
- });
- const frontWindowGeom = new THREE.PlaneGeometry(2, 1.2);
- const frontWindow = new THREE.Mesh(frontWindowGeom, windowMat);
- frontWindow.position.set(length/2 - 0.6, 0.2, 0);
- frontWindow.rotation.y = Math.PI;
- vehicle.add(frontWindow);
- const rearWindowGeom = new THREE.PlaneGeometry(1.8, 1);
- const rearWindow = new THREE.Mesh(rearWindowGeom, windowMat);
- rearWindow.position.set(-length/2 + 0.5, 0.2, 0);
- vehicle.add(rearWindow);
- }
- // Add vehicle to scene
- vehicle.castShadow = true;
- vehicle.receiveShadow = true;
- vehicles.add(vehicle);
- // Set random position on a road
- const halfCity = CITY_SIZE / 2;
- let roadX, roadZ, angle;
- // 70% chance to place on main roads, 30% on ring highway
- if (Math.random() < 0.7) {
- // Position on grid roads
- if (Math.random() < 0.5) {
- // East-west roads
- roadX = (Math.random() - 0.5) * CITY_SIZE;
- roadZ = Math.floor(Math.random() * 5 - 2) * (CITY_SIZE / 4);
- angle = 0;
- } else {
- // North-south roads
- roadX = Math.floor(Math.random() * 5 - 2) * (CITY_SIZE / 4);
- roadZ = (Math.random() - 0.5) * CITY_SIZE;
- angle = Math.PI / 2;
- }
- } else {
- // Position on ring highway
- const ringAngle = Math.random() * Math.PI * 2;
- const ringRadius = halfCity * 0.7;
- roadX = Math.cos(ringAngle) * ringRadius;
- roadZ = Math.sin(ringAngle) * ringRadius;
- angle = ringAngle + Math.PI / 2;
- }
- // Adjust height to be on top of road
- vehicle.position.set(roadX, height/2 + 1.1, roadZ);
- vehicle.rotation.y = angle;
- // Add movement info
- vehicle.userData = {
- speed: 0.2 + Math.random() * 0.4,
- angle: angle
- };
- // Add lights
- const headlightGeom = new THREE.SphereGeometry(0.3);
- const headlightMat = new THREE.MeshBasicMaterial({ color: 0xffffee });
- // Front headlights
- const leftHeadlight = new THREE.Mesh(headlightGeom, headlightMat);
- leftHeadlight.position.set(length/2 + 0.1, -0.2, width/2 - 0.5);
- vehicle.add(leftHeadlight);
- const rightHeadlight = new THREE.Mesh(headlightGeom, headlightMat);
- rightHeadlight.position.set(length/2 + 0.1, -0.2, -width/2 + 0.5);
- vehicle.add(rightHeadlight);
- // Rear lights
- const rearlightGeom = new THREE.SphereGeometry(0.2);
- const rearlightMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
- const leftRearlight = new THREE.Mesh(rearlightGeom, rearlightMat);
- leftRearlight.position.set(-length/2 - 0.1, -0.2, width/2 - 0.5);
- vehicle.add(leftRearlight);
- const rightRearlight = new THREE.Mesh(rearlightGeom, rearlightMat);
- rightRearlight.position.set(-length/2 - 0.1, -0.2, -width/2 + 0.5);
- vehicle.add(rightRearlight);
- // Add actual lights
- const headlight1 = new THREE.PointLight(0xffffee, 1, 15);
- headlight1.position.copy(leftHeadlight.position);
- vehicle.add(headlight1);
- const headlight2 = new THREE.PointLight(0xffffee, 1, 15);
- headlight2.position.copy(rightHeadlight.position);
- vehicle.add(headlight2);
- // Keep track of lights for day/night
- vehicle.userData.lights = [headlight1, headlight2];
- return vehicle;
- }
- function setupEventListeners() {
- // Handle window resize
- window.addEventListener('resize', () => {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- });
- // Setup slider controls
- document.getElementById('timeSlider').addEventListener('input', (e) => {
- settings.timeOfDay = parseFloat(e.target.value);
- document.getElementById('timeValue').textContent = formatTime(settings.timeOfDay);
- updateLighting();
- });
- document.getElementById('trafficSlider').addEventListener('input', (e) => {
- settings.trafficDensity = parseFloat(e.target.value);
- document.getElementById('trafficValue').textContent = Math.round(settings.trafficDensity * 100) + '%';
- generateVehicles();
- });
- document.getElementById('fogSlider').addEventListener('input', (e) => {
- settings.fogIntensity = parseFloat(e.target.value);
- document.getElementById('fogValue').textContent = Math.round(settings.fogIntensity * 10000) / 100 + '%';
- scene.fog.density = settings.fogIntensity;
- });
- document.getElementById('neonSlider').addEventListener('input', (e) => {
- settings.neonIntensity = parseFloat(e.target.value);
- document.getElementById('neonValue').textContent = Math.round(settings.neonIntensity * 50) + '%';
- updateNeonLights();
- });
- document.getElementById('cameraHeightSlider').addEventListener('input', (e) => {
- settings.cameraHeight = parseFloat(e.target.value);
- document.getElementById('cameraHeightValue').textContent = settings.cameraHeight;
- });
- document.getElementById('rotationSpeedSlider').addEventListener('input', (e) => {
- settings.rotationSpeed = parseFloat(e.target.value);
- document.getElementById('rotationSpeedValue').textContent = Math.round(settings.rotationSpeed * 100000) / 1000 + '%';
- });
- }
- function formatTime(hours) {
- const h = Math.floor(hours);
- const m = Math.floor((hours * 60) % 60);
- return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
- }
- function updateLighting() {
- // Update sky colors based on time of day
- if (skyDome && skyDome.material.uniforms) {
- skyDome.material.uniforms.timeOfDay.value = settings.timeOfDay;
- }
- // Calculate sun position and intensity based on time
- const sunAngle = ((settings.timeOfDay - 6) / 12) * Math.PI;
- const sunHeight = Math.sin(sunAngle) * 500;
- const sunDistance = Math.cos(sunAngle) * 500;
- sunLight.position.set(sunDistance, sunHeight, 0);
- // Set sun intensity based on height (day/night)
- let sunIntensity = 0;
- if (settings.timeOfDay > 6 && settings.timeOfDay < 18) {
- // Day time
- sunIntensity = 1;
- if (settings.timeOfDay < 8) {
- // Dawn transition
- sunIntensity = (settings.timeOfDay - 6) / 2;
- } else if (settings.timeOfDay > 16) {
- // Dusk transition
- sunIntensity = (18 - settings.timeOfDay) / 2;
- }
- }
- sunLight.intensity = sunIntensity;
- // Set moon at opposite position to sun
- moonLight.position.set(-sunDistance, -sunHeight, 0);
- // Moon intensity is inverse of sun
- let moonIntensity = 0;
- if (settings.timeOfDay < 6 || settings.timeOfDay > 18) {
- moonIntensity = 0.3;
- if (settings.timeOfDay > 18 && settings.timeOfDay < 20) {
- // Dusk to night transition
- moonIntensity = 0.3 * ((settings.timeOfDay - 18) / 2);
- } else if (settings.timeOfDay > 4 && settings.timeOfDay < 6) {
- // Night to dawn transition
- moonIntensity = 0.3 * ((6 - settings.timeOfDay) / 2);
- }
- }
- moonLight.intensity = moonIntensity;
- // Update ambient light
- const ambientIntensity = Math.max(0.1, Math.min(0.6, sunIntensity * 0.6));
- ambientLight.intensity = ambientIntensity;
- // Update fog color based on time of day
- if (settings.timeOfDay > 6 && settings.timeOfDay < 18) {
- // Day
- scene.fog.color.setHex(0x93a5d6);
- } else {
- // Night
- scene.fog.color.setHex(0x000030);
- }
- // Handle street lights
- updateStreetLights();
- // Handle building windows
- updateBuildingWindows();
- // Handle vehicle headlights
- updateVehicleLights();
- // Update neon signs
- updateNeonLights();
- }
- function updateStreetLights() {
- if (city.userData.streetLights) {
- const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
- city.userData.streetLights.forEach(light => {
- light.intensity = isNight ? 1 : 0;
- });
- }
- }
- function updateBuildingWindows() {
- // Go through all buildings with windows
- city.traverse(obj => {
- if (obj.windows) {
- const timeOfDay = settings.timeOfDay;
- let windowsLit = 0;
- // Night time - most windows should be lit
- if (timeOfDay >= 17 || timeOfDay <= 7) {
- windowsLit = 0.7 + Math.random() * 0.2;
- }
- // Early morning - some windows lit
- else if (timeOfDay > 7 && timeOfDay < 9) {
- windowsLit = 0.3 + Math.random() * 0.2;
- }
- // Late afternoon - some windows lit
- else if (timeOfDay > 15 && timeOfDay < 17) {
- windowsLit = 0.5 + Math.random() * 0.2;
- }
- // Daytime - few windows lit
- else {
- windowsLit = 0.1 + Math.random() * 0.1;
- }
- // Update window materials
- obj.windows.forEach(window => {
- if (Math.random() < windowsLit) {
- window.material.emissive.setHex(0x555511);
- window.material.emissiveIntensity = 1;
- } else {
- window.material.emissive.setHex(0x000000);
- window.material.emissiveIntensity = 0;
- }
- });
- }
- });
- }
- function updateVehicleLights() {
- // Update all vehicle lights based on time of day
- const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
- vehicles.children.forEach(vehicle => {
- if (vehicle.userData.lights) {
- vehicle.userData.lights.forEach(light => {
- light.intensity = isNight ? 1 : 0;
- });
- }
- });
- }
- function updateNeonLights() {
- // Only show neon lights at night, but power depends on neonIntensity slider
- const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
- const intensity = isNight ? settings.neonIntensity : 0;
- neonLights.children.forEach(neon => {
- if (neon.userData.type === 'neon') {
- neon.material.opacity = 0.6 * intensity;
- neon.material.emissiveIntensity = intensity;
- }
- });
- }
- function animate() {
- requestAnimationFrame(animate);
- // Update camera position
- cameraAngle += settings.rotationSpeed;
- const cameraDistance = 700;
- camera.position.x = Math.cos(cameraAngle) * cameraDistance;
- camera.position.z = Math.sin(cameraAngle) * cameraDistance;
- camera.position.y = settings.cameraHeight;
- camera.lookAt(0, 0, 0);
- // Update vehicle positions
- updateVehicles();
- // Animate blinking lights (aircraft warnings)
- animateBlinkingLights();
- renderer.render(scene, camera);
- }
- function updateVehicles() {
- vehicles.children.forEach(vehicle => {
- const speed = vehicle.userData.speed;
- const angle = vehicle.userData.angle;
- // Move vehicle
- vehicle.position.x += Math.cos(angle) * speed;
- vehicle.position.z += Math.sin(angle) * speed;
- // Check if vehicle has gone off the map and reset
- const halfCity = CITY_SIZE / 2;
- if (
- vehicle.position.x > halfCity + 50 ||
- vehicle.position.x < -halfCity - 50 ||
- vehicle.position.z > halfCity + 50 ||
- vehicle.position.z < -halfCity - 50
- ) {
- // Reset to opposite side
- if (Math.abs(vehicle.position.x) > Math.abs(vehicle.position.z)) {
- vehicle.position.x = -Math.sign(vehicle.position.x) * halfCity;
- } else {
- vehicle.position.z = -Math.sign(vehicle.position.z) * halfCity;
- }
- }
- });
- }
- function animateBlinkingLights() {
- // Animation for blinking lights (aircraft warnings, etc.)
- const time = Date.now() * 0.001;
- // Check all objects with blinking lights
- scene.traverse(obj => {
- if (obj.userData && obj.userData.blinkRate) {
- const rate = obj.userData.blinkRate;
- if (obj.type === 'PointLight') {
- const blinkState = Math.sin(time * rate * Math.PI) > 0;
- obj.intensity = blinkState ? 1 : 0;
- }
- }
- });
- }
- // Initialize on load
- window.addEventListener('load', init);
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement