Advertisement
Guest User

3D City code

a guest
Mar 1st, 2025
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 58.91 KB | Help | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Futuristic 3D City Simulation</title>
  7. <style>
  8. body {
  9. margin: 0;
  10. overflow: hidden;
  11. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  12. background-color: #000;
  13. }
  14. canvas {
  15. display: block;
  16. }
  17. #controls {
  18. position: absolute;
  19. bottom: 20px;
  20. left: 20px;
  21. background: rgba(0, 0, 0, 0.7);
  22. padding: 15px;
  23. border-radius: 10px;
  24. color: white;
  25. backdrop-filter: blur(5px);
  26. box-shadow: 0 0 15px rgba(0, 100, 255, 0.5);
  27. border: 1px solid rgba(0, 150, 255, 0.3);
  28. z-index: 100;
  29. }
  30. #title {
  31. position: absolute;
  32. top: 20px;
  33. left: 20px;
  34. color: white;
  35. font-size: 24px;
  36. text-shadow: 0 0 10px rgba(0, 150, 255, 0.8);
  37. }
  38. .slider-container {
  39. margin: 15px 0;
  40. }
  41. label {
  42. display: inline-block;
  43. width: 150px;
  44. color: #00bfff;
  45. font-weight: 500;
  46. }
  47. input[type="range"] {
  48. width: 200px;
  49. vertical-align: middle;
  50. -webkit-appearance: none;
  51. background: rgba(0, 100, 200, 0.3);
  52. height: 8px;
  53. border-radius: 4px;
  54. }
  55. input[type="range"]::-webkit-slider-thumb {
  56. -webkit-appearance: none;
  57. width: 16px;
  58. height: 16px;
  59. border-radius: 50%;
  60. background: #00bfff;
  61. cursor: pointer;
  62. box-shadow: 0 0 5px rgba(0, 191, 255, 0.8);
  63. }
  64. .value-display {
  65. display: inline-block;
  66. width: 60px;
  67. text-align: right;
  68. margin-left: 10px;
  69. color: #fff;
  70. font-family: monospace;
  71. font-size: 14px;
  72. }
  73. #loading {
  74. position: fixed;
  75. top: 0;
  76. left: 0;
  77. width: 100%;
  78. height: 100%;
  79. background: black;
  80. display: flex;
  81. justify-content: center;
  82. align-items: center;
  83. color: #00bfff;
  84. font-size: 24px;
  85. z-index: 1000;
  86. }
  87. .power-by {
  88. position: absolute;
  89. bottom: 10px;
  90. right: 10px;
  91. color: rgba(255, 255, 255, 0.3);
  92. font-size: 12px;
  93. }
  94. </style>
  95. </head>
  96. <body>
  97. <div id="loading">Loading city simulation...</div>
  98. <div id="title">NEOCITY 2077</div>
  99. <div id="controls">
  100. <div class="slider-container">
  101. <label for="timeSlider">Time of Day:</label>
  102. <input type="range" id="timeSlider" min="0" max="24" value="12" step="0.1">
  103. <span class="value-display" id="timeValue">12:00</span>
  104. </div>
  105. <div class="slider-container">
  106. <label for="trafficSlider">Traffic Density:</label>
  107. <input type="range" id="trafficSlider" min="0" max="1" value="0.5" step="0.01">
  108. <span class="value-display" id="trafficValue">50%</span>
  109. </div>
  110. <div class="slider-container">
  111. <label for="fogSlider">Fog Intensity:</label>
  112. <input type="range" id="fogSlider" min="0" max="0.01" value="0.001" step="0.0001">
  113. <span class="value-display" id="fogValue">10%</span>
  114. </div>
  115. <div class="slider-container">
  116. <label for="neonSlider">Neon Intensity:</label>
  117. <input type="range" id="neonSlider" min="0" max="2" value="1" step="0.01">
  118. <span class="value-display" id="neonValue">50%</span>
  119. </div>
  120. <div class="slider-container">
  121. <label for="cameraHeightSlider">Camera Height:</label>
  122. <input type="range" id="cameraHeightSlider" min="50" max="600" value="200" step="10">
  123. <span class="value-display" id="cameraHeightValue">200</span>
  124. </div>
  125. <div class="slider-container">
  126. <label for="rotationSpeedSlider">Rotation Speed:</label>
  127. <input type="range" id="rotationSpeedSlider" min="0" max="0.01" value="0.0005" step="0.0001">
  128. <span class="value-display" id="rotationSpeedValue">50%</span>
  129. </div>
  130. </div>
  131. <div class="power-by">Powered by three.js</div>
  132.  
  133. <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  134. <script>
  135. // Main variables
  136. let scene, camera, renderer, controls;
  137. let city = new THREE.Group();
  138. let vehicles = new THREE.Group();
  139. let neonLights = new THREE.Group();
  140. let skyDome;
  141. let sunLight, ambientLight, moonLight;
  142. let cameraAngle = 0;
  143.  
  144. // Settings controlled by sliders
  145. const settings = {
  146. timeOfDay: 12, // 0-24 hours
  147. trafficDensity: 0.5, // 0-1
  148. fogIntensity: 0.001, // 0-0.01
  149. neonIntensity: 1, // 0-2
  150. cameraHeight: 200, // 50-600
  151. rotationSpeed: 0.0005 // 0-0.01
  152. };
  153.  
  154. // City parameters
  155. const CITY_SIZE = 1000;
  156. const BLOCK_SIZE = 50;
  157. const STREET_WIDTH = 20;
  158. const MAX_BUILDING_HEIGHT = 300;
  159. const MIN_BUILDING_HEIGHT = 30;
  160. const BUILDING_DENSITY = 0.8;
  161.  
  162. // Materials
  163. let buildingMaterials = [];
  164. let roadMaterial;
  165. let neonMaterials = [];
  166.  
  167. // Initialize the scene
  168. function init() {
  169. // Create scene
  170. scene = new THREE.Scene();
  171. scene.fog = new THREE.FogExp2(0x93a5d6, settings.fogIntensity);
  172.  
  173. // Create camera
  174. camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
  175. camera.position.set(0, settings.cameraHeight, 400);
  176. camera.lookAt(0, 100, 0);
  177.  
  178. // Create renderer
  179. renderer = new THREE.WebGLRenderer({ antialias: true });
  180. renderer.setSize(window.innerWidth, window.innerHeight);
  181. renderer.shadowMap.enabled = true;
  182. renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  183. document.body.appendChild(renderer.domElement);
  184.  
  185. // Setup materials
  186. setupMaterials();
  187.  
  188. // Create skybox
  189. createSkyDome();
  190.  
  191. // Create lighting
  192. setupLighting();
  193.  
  194. // Create ground plane
  195. createGround();
  196.  
  197. // Generate city
  198. generateCity();
  199. scene.add(city);
  200.  
  201. // Generate vehicles
  202. generateVehicles();
  203. scene.add(vehicles);
  204.  
  205. // Add neon lights
  206. scene.add(neonLights);
  207.  
  208. // Set up event listeners
  209. setupEventListeners();
  210.  
  211. // Hide loading screen
  212. setTimeout(() => {
  213. document.getElementById('loading').style.display = 'none';
  214. }, 1000);
  215.  
  216. // Start animation loop
  217. animate();
  218. }
  219.  
  220. function setupMaterials() {
  221. // Building materials with different colors
  222. const buildingColors = [
  223. 0x505050, 0x606060, 0x707070, 0x808080, 0x909090,
  224. 0x404860, 0x384870, 0x2a3b62, 0x1e2d54, 0x152046
  225. ];
  226.  
  227. buildingColors.forEach(color => {
  228. buildingMaterials.push(
  229. new THREE.MeshPhongMaterial({
  230. color: color,
  231. flatShading: true,
  232. shininess: 50
  233. })
  234. );
  235. });
  236.  
  237. // Glass material for buildings
  238. buildingMaterials.push(
  239. new THREE.MeshPhongMaterial({
  240. color: 0x88ccff,
  241. specular: 0xffffff,
  242. shininess: 100,
  243. transparent: true,
  244. opacity: 0.3
  245. })
  246. );
  247.  
  248. // Road material
  249. roadMaterial = new THREE.MeshPhongMaterial({
  250. color: 0x101010,
  251. shininess: 10
  252. });
  253.  
  254. // Neon materials (for night lighting)
  255. const neonColors = [0xff0066, 0x00ffff, 0xffff00, 0x00ff00, 0xff00ff];
  256. neonColors.forEach(color => {
  257. neonMaterials.push(
  258. new THREE.MeshBasicMaterial({
  259. color: color,
  260. transparent: true,
  261. opacity: 0.9
  262. })
  263. );
  264. });
  265. }
  266.  
  267. function createSkyDome() {
  268. const skyGeometry = new THREE.SphereGeometry(1500, 32, 32);
  269. // Invert the geometry so we see it from the inside
  270. skyGeometry.scale(-1, 1, 1);
  271.  
  272. const skyMaterial = new THREE.ShaderMaterial({
  273. uniforms: {
  274. topColor: { value: new THREE.Color(0x0077ff) },
  275. bottomColor: { value: new THREE.Color(0xffffff) },
  276. offset: { value: 400 },
  277. exponent: { value: 0.6 },
  278. timeOfDay: { value: settings.timeOfDay }
  279. },
  280. vertexShader: `
  281. varying vec3 vWorldPosition;
  282. void main() {
  283. vec4 worldPosition = modelMatrix * vec4(position, 1.0);
  284. vWorldPosition = worldPosition.xyz;
  285. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  286. }
  287. `,
  288. fragmentShader: `
  289. uniform vec3 topColor;
  290. uniform vec3 bottomColor;
  291. uniform float offset;
  292. uniform float exponent;
  293. uniform float timeOfDay;
  294. varying vec3 vWorldPosition;
  295. void main() {
  296. float h = normalize(vWorldPosition + offset).y;
  297.  
  298. // Night colors
  299. vec3 nightTop = vec3(0.0, 0.0, 0.1);
  300. vec3 nightBottom = vec3(0.0, 0.0, 0.2);
  301.  
  302. // Dawn/dusk colors
  303. vec3 dawnTop = vec3(0.7, 0.3, 0.2);
  304. vec3 dawnBottom = vec3(0.4, 0.2, 0.3);
  305.  
  306. // Day colors
  307. vec3 dayTop = topColor;
  308. vec3 dayBottom = bottomColor;
  309.  
  310. vec3 finalTopColor, finalBottomColor;
  311.  
  312. if (timeOfDay < 6.0 || timeOfDay > 18.0) {
  313. // Night
  314. finalTopColor = nightTop;
  315. finalBottomColor = nightBottom;
  316. } else if (timeOfDay < 8.0) {
  317. // Dawn
  318. float blend = (timeOfDay - 6.0) / 2.0;
  319. finalTopColor = mix(dawnTop, dayTop, blend);
  320. finalBottomColor = mix(dawnBottom, dayBottom, blend);
  321. } else if (timeOfDay > 16.0) {
  322. // Dusk
  323. float blend = (18.0 - timeOfDay) / 2.0;
  324. finalTopColor = mix(dawnTop, dayTop, blend);
  325. finalBottomColor = mix(dawnBottom, dayBottom, blend);
  326. } else {
  327. // Day
  328. finalTopColor = dayTop;
  329. finalBottomColor = dayBottom;
  330. }
  331.  
  332. vec3 skyColor = mix(finalBottomColor, finalTopColor, pow(max(h, 0.0), exponent));
  333.  
  334. // Add stars at night
  335. if (timeOfDay < 6.0 || timeOfDay > 18.0) {
  336. float r = fract(sin(dot(vWorldPosition.xyz, vec3(12.9898, 78.233, 53.539))) * 43758.5453);
  337. if (r > 0.997) {
  338. float starIntensity = (timeOfDay < 3.0 || timeOfDay > 21.0) ? 1.0 :
  339. (timeOfDay < 6.0) ? (3.0 - timeOfDay) / 3.0 :
  340. (24.0 - timeOfDay) / 3.0;
  341.  
  342. skyColor += vec3(1.0, 1.0, 1.0) * starIntensity;
  343. }
  344. }
  345.  
  346. gl_FragColor = vec4(skyColor, 1.0);
  347. }
  348. `,
  349. side: THREE.BackSide
  350. });
  351.  
  352. skyDome = new THREE.Mesh(skyGeometry, skyMaterial);
  353. scene.add(skyDome);
  354. }
  355.  
  356. function setupLighting() {
  357. // Ambient light
  358. ambientLight = new THREE.AmbientLight(0x404040);
  359. scene.add(ambientLight);
  360.  
  361. // Sun directional light
  362. sunLight = new THREE.DirectionalLight(0xffffee, 1);
  363. sunLight.position.set(100, 300, 100);
  364. sunLight.castShadow = true;
  365. sunLight.shadow.camera.left = -500;
  366. sunLight.shadow.camera.right = 500;
  367. sunLight.shadow.camera.top = 500;
  368. sunLight.shadow.camera.bottom = -500;
  369. sunLight.shadow.camera.far = 1500;
  370. sunLight.shadow.mapSize.width = 2048;
  371. sunLight.shadow.mapSize.height = 2048;
  372. scene.add(sunLight);
  373.  
  374. // Moon light
  375. moonLight = new THREE.DirectionalLight(0x8080aa, 0.5);
  376. moonLight.position.set(-100, 300, -100);
  377. moonLight.intensity = 0; // Start with moon turned off (daytime)
  378. scene.add(moonLight);
  379.  
  380. updateLighting();
  381. }
  382.  
  383. function createGround() {
  384. const groundGeometry = new THREE.PlaneGeometry(CITY_SIZE * 2, CITY_SIZE * 2);
  385. const groundMaterial = new THREE.MeshPhongMaterial({
  386. color: 0x1a1a1a,
  387. shininess: 10
  388. });
  389. const ground = new THREE.Mesh(groundGeometry, groundMaterial);
  390. ground.rotation.x = -Math.PI / 2;
  391. ground.position.y = -0.1;
  392. ground.receiveShadow = true;
  393. scene.add(ground);
  394. }
  395.  
  396. function generateCity() {
  397. const halfCity = CITY_SIZE / 2;
  398.  
  399. // Create grid of blocks
  400. for (let x = -halfCity; x < halfCity; x += BLOCK_SIZE + STREET_WIDTH) {
  401. for (let z = -halfCity; z < halfCity; z += BLOCK_SIZE + STREET_WIDTH) {
  402. createCityBlock(x, z);
  403. }
  404. }
  405.  
  406. // Create main highways
  407. createHighways();
  408. }
  409.  
  410. function createCityBlock(x, z) {
  411. const blockGroup = new THREE.Group();
  412.  
  413. // Determine if this is a special district (downtown, residential, etc)
  414. const distanceFromCenter = Math.sqrt(x*x + z*z);
  415. let districtType;
  416.  
  417. if (distanceFromCenter < 200) {
  418. districtType = 'downtown'; // Downtown - Tall skyscrapers
  419. } else if (distanceFromCenter < 400) {
  420. districtType = 'midtown'; // Midtown - Mix of heights
  421. } else {
  422. districtType = 'outskirts'; // Outskirts - Lower buildings
  423. }
  424.  
  425. // Number of buildings to place in this block
  426. const maxBuildings = Math.floor(BLOCK_SIZE / 15) ** 2;
  427. const numBuildings = Math.floor(maxBuildings * BUILDING_DENSITY);
  428.  
  429. // Create buildings within this block
  430. for (let i = 0; i < numBuildings; i++) {
  431. // Random position within block
  432. const bx = x + Math.random() * BLOCK_SIZE - BLOCK_SIZE/2;
  433. const bz = z + Math.random() * BLOCK_SIZE - BLOCK_SIZE/2;
  434.  
  435. // Determine building properties based on district
  436. let height, width, depth;
  437.  
  438. if (districtType === 'downtown') {
  439. // Downtown - Tall skyscrapers
  440. height = MIN_BUILDING_HEIGHT + Math.random() * (MAX_BUILDING_HEIGHT - MIN_BUILDING_HEIGHT);
  441. width = 10 + Math.random() * 20;
  442. depth = 10 + Math.random() * 20;
  443.  
  444. // Special case for super tall buildings in the very center
  445. if (distanceFromCenter < 100 && Math.random() < 0.3) {
  446. height *= 1.5;
  447. // Make a fancy top for the tallest buildings
  448. createSkyscraper(bx, bz, width, depth, height, blockGroup);
  449. continue;
  450. }
  451. } else if (districtType === 'midtown') {
  452. // Midtown - Medium height
  453. height = MIN_BUILDING_HEIGHT + Math.random() * (MAX_BUILDING_HEIGHT/2);
  454. width = 15 + Math.random() * 15;
  455. depth = 15 + Math.random() * 15;
  456. } else {
  457. // Outskirts - Lower buildings
  458. height = MIN_BUILDING_HEIGHT/2 + Math.random() * MIN_BUILDING_HEIGHT;
  459. width = 10 + Math.random() * 25;
  460. depth = 10 + Math.random() * 25;
  461. }
  462.  
  463. createBuilding(bx, bz, width, depth, height, blockGroup);
  464. }
  465.  
  466. // Add neon signs in downtown and midtown
  467. if ((districtType === 'downtown' || districtType === 'midtown') && Math.random() < 0.3) {
  468. createNeonSign(x, z, blockGroup);
  469. }
  470.  
  471. city.add(blockGroup);
  472. }
  473.  
  474. function createBuilding(x, z, width, depth, height, parent) {
  475. // Main building body
  476. const geometry = new THREE.BoxGeometry(width, height, depth);
  477.  
  478. // Randomly select material
  479. const materialIndex = Math.floor(Math.random() * buildingMaterials.length);
  480. const material = buildingMaterials[materialIndex];
  481.  
  482. const building = new THREE.Mesh(geometry, material);
  483. building.position.set(x, height/2, z);
  484. building.castShadow = true;
  485. building.receiveShadow = true;
  486. parent.add(building);
  487.  
  488. // Add windows (window texture would be better, but for simplicity we'll add some geometry)
  489. if (Math.random() < 0.7) {
  490. addBuildingWindows(building, width, depth, height);
  491. }
  492.  
  493. // Sometimes add antennas or water towers to tops of buildings
  494. if (Math.random() < 0.3) {
  495. addBuildingFeatures(x, z, width, depth, height, parent);
  496. }
  497.  
  498. return building;
  499. }
  500.  
  501. function addBuildingWindows(building, width, depth, height) {
  502. // Window material - this will be affected by time of day
  503. const windowMaterial = new THREE.MeshPhongMaterial({
  504. color: 0xffffee,
  505. emissive: 0x333311,
  506. transparent: true,
  507. opacity: 0.7
  508. });
  509.  
  510. // Track the window object for later updates
  511. building.windows = [];
  512.  
  513. // Add windows on each side of the building
  514. const sides = [
  515. { dir: 'front', x: 0, z: depth/2 + 0.1, w: width-2, h: height-2 },
  516. { dir: 'back', x: 0, z: -depth/2 - 0.1, w: width-2, h: height-2 },
  517. { dir: 'left', x: width/2 + 0.1, z: 0, w: depth-2, h: height-2 },
  518. { dir: 'right', x: -width/2 - 0.1, z: 0, w: depth-2, h: height-2 }
  519. ];
  520.  
  521. sides.forEach(side => {
  522. // Number of windows based on building size
  523. const rows = Math.floor(side.h / 6);
  524. const cols = Math.floor(side.w / 4);
  525.  
  526. const windowWidth = 2;
  527. const windowHeight = 3;
  528.  
  529. for (let row = 0; row < rows; row++) {
  530. for (let col = 0; col < cols; col++) {
  531. // Randomly skip some windows
  532. if (Math.random() < 0.2) continue;
  533.  
  534. const windowGeom = new THREE.PlaneGeometry(windowWidth, windowHeight);
  535. const windowObj = new THREE.Mesh(windowGeom, windowMaterial.clone());
  536.  
  537. // Position within the wall
  538. let wx, wy, wz, rx, ry, rz;
  539.  
  540. wy = (row + 0.5) * (side.h / rows) - side.h/2 + 2;
  541.  
  542. if (side.dir === 'front' || side.dir === 'back') {
  543. wx = (col + 0.5) * (side.w / cols) - side.w/2;
  544. wz = side.z;
  545. ry = Math.PI;
  546. if (side.dir === 'back') ry = 0;
  547. } else {
  548. wz = (col + 0.5) * (side.w / cols) - side.w/2;
  549. wx = side.x;
  550. ry = -Math.PI/2;
  551. if (side.dir === 'right') ry = Math.PI/2;
  552. }
  553.  
  554. windowObj.position.set(wx, wy, wz);
  555. windowObj.rotation.y = ry;
  556.  
  557. // Store window for day/night updates
  558. building.windows.push(windowObj);
  559.  
  560. building.add(windowObj);
  561. }
  562. }
  563. });
  564. }
  565.  
  566. function addBuildingFeatures(x, z, width, depth, height, parent) {
  567. const featureType = Math.random();
  568.  
  569. if (featureType < 0.4) {
  570. // Antenna
  571. const antennaHeight = 10 + Math.random() * 20;
  572. const antennaGeom = new THREE.CylinderGeometry(0.5, 0.5, antennaHeight);
  573. const antennaMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
  574. const antenna = new THREE.Mesh(antennaGeom, antennaMat);
  575.  
  576. antenna.position.set(x, height + antennaHeight/2, z);
  577. antenna.castShadow = true;
  578. parent.add(antenna);
  579.  
  580. // Add blinking light at top
  581. const blinkingLight = new THREE.PointLight(0xff0000, 0.5, 30);
  582. blinkingLight.position.set(x, height + antennaHeight + 1, z);
  583. parent.add(blinkingLight);
  584.  
  585. // Blinking animation
  586. const blinkRate = 0.5 + Math.random() * 0.5;
  587. blinkingLight.userData = { blinkRate };
  588. } else if (featureType < 0.7) {
  589. // Water tower
  590. const towerHeight = 5 + Math.random() * 5;
  591. const tankRadius = 3 + Math.random() * 2;
  592. const tankHeight = 4 + Math.random() * 3;
  593.  
  594. // Legs
  595. const legMat = new THREE.MeshPhongMaterial({ color: 0x444444 });
  596. for (let i = 0; i < 4; i++) {
  597. const legGeom = new THREE.CylinderGeometry(0.5, 0.5, towerHeight);
  598. const leg = new THREE.Mesh(legGeom, legMat);
  599.  
  600. const angle = (i / 4) * Math.PI * 2;
  601. const lx = x + Math.cos(angle) * tankRadius/1.5;
  602. const lz = z + Math.sin(angle) * tankRadius/1.5;
  603.  
  604. leg.position.set(lx, height + towerHeight/2, lz);
  605. leg.castShadow = true;
  606. parent.add(leg);
  607. }
  608.  
  609. // Tank
  610. const tankGeom = new THREE.CylinderGeometry(tankRadius, tankRadius, tankHeight, 16);
  611. const tankMat = new THREE.MeshPhongMaterial({ color: 0x777777 });
  612. const tank = new THREE.Mesh(tankGeom, tankMat);
  613.  
  614. tank.position.set(x, height + towerHeight + tankHeight/2, z);
  615. tank.castShadow = true;
  616. parent.add(tank);
  617. } else {
  618. // HVAC units
  619. const numUnits = 1 + Math.floor(Math.random() * 4);
  620. const hvacMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
  621.  
  622. for (let i = 0; i < numUnits; i++) {
  623. const unitSize = 2 + Math.random() * 3;
  624. const hvacGeom = new THREE.BoxGeometry(unitSize, unitSize, unitSize);
  625. const hvac = new THREE.Mesh(hvacGeom, hvacMat);
  626.  
  627. const hx = x + (Math.random() - 0.5) * (width - unitSize);
  628. const hz = z + (Math.random() - 0.5) * (depth - unitSize);
  629.  
  630. hvac.position.set(hx, height + unitSize/2, hz);
  631. hvac.castShadow = true;
  632. parent.add(hvac);
  633. }
  634. }
  635. }
  636.  
  637. function createSkyscraper(x, z, width, depth, height, parent) {
  638. // Base building
  639. createBuilding(x, z, width, depth, height * 0.9, parent);
  640.  
  641. // Add a tapered top
  642. const topHeight = height * 0.2;
  643. const topWidth = width * 0.6;
  644. const topDepth = depth * 0.6;
  645.  
  646. const topGeometry = new THREE.BoxGeometry(topWidth, topHeight, topDepth);
  647. const topMaterial = buildingMaterials[Math.floor(Math.random() * buildingMaterials.length)];
  648.  
  649. const top = new THREE.Mesh(topGeometry, topMaterial);
  650. top.position.set(x, height * 0.9 + topHeight/2, z);
  651. top.castShadow = true;
  652. parent.add(top);
  653.  
  654. // Add spire
  655. const spireHeight = height * 0.15;
  656. const spireGeometry = new THREE.CylinderGeometry(0, topWidth/8, spireHeight, 4);
  657. const spireMaterial = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
  658.  
  659. const spire = new THREE.Mesh(spireGeometry, spireMaterial);
  660. spire.position.set(x, height * 0.9 + topHeight + spireHeight/2, z);
  661. spire.castShadow = true;
  662. parent.add(spire);
  663.  
  664. // Add aircraft warning light
  665. const light = new THREE.PointLight(0xff0000, 1, 50);
  666. light.position.set(x, height * 0.9 + topHeight + spireHeight + 2, z);
  667. parent.add(light);
  668.  
  669. // Add light blinking animation
  670. light.userData = { blinkRate: 0.5 };
  671. }
  672.  
  673. function createNeonSign(x, z, parent) {
  674. const height = 30 + Math.random() * 50;
  675. const width = 10 + Math.random() * 30;
  676.  
  677. // Base structure
  678. const baseGeom = new THREE.BoxGeometry(width/10, height, width/10);
  679. const baseMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
  680. const base = new THREE.Mesh(baseGeom, baseMat);
  681.  
  682. base.position.set(x, height/2, z);
  683. base.castShadow = true;
  684. parent.add(base);
  685.  
  686. // Sign
  687. const signGeom = new THREE.BoxGeometry(width, height/4, width/15);
  688. const signMat = new THREE.MeshPhongMaterial({ color: 0x333333 });
  689. const sign = new THREE.Mesh(signGeom, signMat);
  690.  
  691. sign.position.set(x, height * 0.8, z + width/20);
  692. sign.castShadow = true;
  693. parent.add(sign);
  694.  
  695. // Neon text
  696. const neonMat = neonMaterials[Math.floor(Math.random() * neonMaterials.length)];
  697.  
  698. // Generate random neon pattern
  699. const pattern = Math.floor(Math.random() * 5);
  700. let neonGeom;
  701.  
  702. if (pattern === 0) {
  703. // Circle
  704. neonGeom = new THREE.TorusGeometry(width/3, width/30, 16, 32);
  705. const neon = new THREE.Mesh(neonGeom, neonMat);
  706. neon.position.set(x, height * 0.8, z + width/10);
  707. neon.userData = { type: 'neon' };
  708. neonLights.add(neon);
  709. } else if (pattern === 1) {
  710. // Straight lines
  711. const numLines = 3 + Math.floor(Math.random() * 3);
  712. for (let i = 0; i < numLines; i++) {
  713. neonGeom = new THREE.BoxGeometry(width * 0.8, width/30, width/30);
  714. const neon = new THREE.Mesh(neonGeom, neonMat);
  715. neon.position.set(x, height * 0.8 - i * width/8, z + width/10);
  716. neon.userData = { type: 'neon' };
  717. neonLights.add(neon);
  718. }
  719. } else if (pattern === 2) {
  720. // Cross
  721. neonGeom = new THREE.BoxGeometry(width * 0.6, width/30, width/30);
  722. const neonH = new THREE.Mesh(neonGeom, neonMat);
  723. neonH.position.set(x, height * 0.8, z + width/10);
  724. neonH.userData = { type: 'neon' };
  725. neonLights.add(neonH);
  726.  
  727. neonGeom = new THREE.BoxGeometry(width/30, width * 0.6, width/30);
  728. const neonV = new THREE.Mesh(neonGeom, neonMat);
  729. neonV.position.set(x, height * 0.8, z + width/10);
  730. neonV.userData = { type: 'neon' };
  731. neonLights.add(neonV);
  732. } else if (pattern === 3) {
  733. // Triangle
  734. const triangleShape = new THREE.Shape();
  735. const s = width/3;
  736. triangleShape.moveTo(0, s);
  737. triangleShape.lineTo(-s, -s);
  738. triangleShape.lineTo(s, -s);
  739. triangleShape.lineTo(0, s);
  740.  
  741. const triangleGeom = new THREE.ShapeGeometry(triangleShape);
  742. const triangleExtrudeSettings = {
  743. steps: 1,
  744. depth: width/30,
  745. bevelEnabled: false
  746. };
  747.  
  748. neonGeom = new THREE.ExtrudeGeometry(triangleShape, triangleExtrudeSettings);
  749. const neon = new THREE.Mesh(neonGeom, neonMat);
  750. neon.position.set(x, height * 0.8, z + width/10);
  751. neon.userData = { type: 'neon' };
  752. neonLights.add(neon);
  753. } else {
  754. // Star
  755. const starShape = new THREE.Shape();
  756. const s = width/4;
  757. for (let i = 0; i < 5; i++) {
  758. const angleStar = (i * 2 * Math.PI / 5) - Math.PI/2;
  759. const angleStarInner = ((i + 0.5) * 2 * Math.PI / 5) - Math.PI/2;
  760.  
  761. const x1 = Math.cos(angleStar) * s;
  762. const y1 = Math.sin(angleStar) * s;
  763.  
  764. const x2 = Math.cos(angleStarInner) * (s/2);
  765. const y2 = Math.sin(angleStarInner) * (s/2);
  766.  
  767. if (i === 0) {
  768. starShape.moveTo(x1, y1);
  769. } else {
  770. starShape.lineTo(x1, y1);
  771. }
  772.  
  773. starShape.lineTo(x2, y2);
  774. }
  775. starShape.lineTo(Math.cos(-Math.PI/2) * s, Math.sin(-Math.PI/2) * s);
  776.  
  777. const starExtrudeSettings = {
  778. steps: 1,
  779. depth: width/30,
  780. bevelEnabled: false
  781. };
  782.  
  783. neonGeom = new THREE.ExtrudeGeometry(starShape, starExtrudeSettings);
  784. const neon = new THREE.Mesh(neonGeom, neonMat);
  785. neon.position.set(x, height * .8, z + width/10);
  786. neon.userData = { type: 'neon' };
  787. neonLights.add(neon);
  788. }
  789. }
  790.  
  791. function createHighways() {
  792. const halfCity = CITY_SIZE / 2;
  793.  
  794. // Main highway crossings
  795. const highways = [
  796. { start: { x: -halfCity, z: 0 }, end: { x: halfCity, z: 0 } },
  797. { start: { x: 0, z: -halfCity }, end: { x: 0, z: halfCity } },
  798. { start: { x: -halfCity, z: -halfCity/2 }, end: { x: halfCity, z: -halfCity/2 } },
  799. { start: { x: -halfCity, z: halfCity/2 }, end: { x: halfCity, z: halfCity/2 } },
  800. { start: { x: -halfCity/2, z: -halfCity }, end: { x: -halfCity/2, z: halfCity } },
  801. { start: { x: halfCity/2, z: -halfCity }, end: { x: halfCity/2, z: halfCity } }
  802. ];
  803.  
  804. // Add circular highway
  805. const ringRadius = halfCity * 0.7;
  806. const ringSegments = 32;
  807. for (let i = 0; i < ringSegments; i++) {
  808. const angle1 = (i / ringSegments) * Math.PI * 2;
  809. const angle2 = ((i + 1) / ringSegments) * Math.PI * 2;
  810.  
  811. const x1 = Math.cos(angle1) * ringRadius;
  812. const z1 = Math.sin(angle1) * ringRadius;
  813.  
  814. const x2 = Math.cos(angle2) * ringRadius;
  815. const z2 = Math.sin(angle2) * ringRadius;
  816.  
  817. highways.push({
  818. start: { x: x1, z: z1 },
  819. end: { x: x2, z: z2 }
  820. });
  821. }
  822.  
  823. // Create each highway segment
  824. highways.forEach(highway => {
  825. createHighwaySegment(highway.start, highway.end);
  826. });
  827. }
  828.  
  829. function createHighwaySegment(start, end) {
  830. // Calculate dimensions
  831. const dx = end.x - start.x;
  832. const dz = end.z - start.z;
  833. const length = Math.sqrt(dx * dx + dz * dz);
  834. const width = STREET_WIDTH * 1.5;
  835.  
  836. // Create road surface
  837. const roadGeom = new THREE.PlaneGeometry(length, width);
  838. const road = new THREE.Mesh(roadGeom, roadMaterial);
  839.  
  840. // Position and rotate to align with endpoints
  841. road.position.set((start.x + end.x) / 2, 1, (start.z + end.z) / 2);
  842. road.rotation.x = -Math.PI / 2;
  843. road.rotation.z = Math.atan2(dz, dx);
  844.  
  845. road.receiveShadow = true;
  846. city.add(road);
  847.  
  848. // Add lane markings
  849. const laneMarkingGeom = new THREE.PlaneGeometry(length - 4, 0.5);
  850. const laneMarkingMat = new THREE.MeshBasicMaterial({
  851. color: 0xFFFFFF,
  852. transparent: true,
  853. opacity: 0.8
  854. });
  855.  
  856. const laneMarking = new THREE.Mesh(laneMarkingGeom, laneMarkingMat);
  857. laneMarking.position.set((start.x + end.x) / 2, 1.1, (start.z + end.z) / 2);
  858. laneMarking.rotation.x = -Math.PI / 2;
  859. laneMarking.rotation.z = Math.atan2(dz, dx);
  860. city.add(laneMarking);
  861.  
  862. // Add barriers on either side
  863. const barrierGeom = new THREE.BoxGeometry(length, 2, 0.5);
  864. const barrierMat = new THREE.MeshPhongMaterial({ color: 0x999999 });
  865.  
  866. const angle = Math.atan2(dz, dx);
  867. const offsetX = Math.sin(angle) * (width / 2);
  868. const offsetZ = -Math.cos(angle) * (width / 2);
  869.  
  870. // Left barrier
  871. const leftBarrier = new THREE.Mesh(barrierGeom, barrierMat);
  872. leftBarrier.position.set(
  873. (start.x + end.x) / 2 + offsetX,
  874. 2,
  875. (start.z + end.z) / 2 + offsetZ
  876. );
  877. leftBarrier.rotation.y = angle;
  878. leftBarrier.castShadow = true;
  879. leftBarrier.receiveShadow = true;
  880. city.add(leftBarrier);
  881.  
  882. // Right barrier
  883. const rightBarrier = new THREE.Mesh(barrierGeom, barrierMat);
  884. rightBarrier.position.set(
  885. (start.x + end.x) / 2 - offsetX,
  886. 2,
  887. (start.z + end.z) / 2 - offsetZ
  888. );
  889. rightBarrier.rotation.y = angle;
  890. rightBarrier.castShadow = true;
  891. rightBarrier.receiveShadow = true;
  892. city.add(rightBarrier);
  893.  
  894. // Add support columns for elevated highway
  895. const numColumns = Math.floor(length / 50);
  896. for (let i = 0; i <= numColumns; i++) {
  897. const t = i / numColumns;
  898. const columnX = start.x + dx * t;
  899. const columnZ = start.z + dz * t;
  900.  
  901. const columnHeight = 20 + Math.random() * 5;
  902. const columnGeom = new THREE.BoxGeometry(4, columnHeight, 4);
  903. const columnMat = new THREE.MeshPhongMaterial({ color: 0x888888 });
  904. const column = new THREE.Mesh(columnGeom, columnMat);
  905.  
  906. column.position.set(columnX, columnHeight / 2, columnZ);
  907. column.castShadow = true;
  908. column.receiveShadow = true;
  909. city.add(column);
  910. }
  911.  
  912. // For wider highways, add light posts
  913. if (Math.random() < 0.8) {
  914. const numLights = Math.floor(length / 40);
  915. for (let i = 1; i < numLights; i++) {
  916. const t = i / numLights;
  917. const lightX = start.x + dx * t;
  918. const lightZ = start.z + dz * t;
  919.  
  920. createLightPost(
  921. lightX + offsetX * 0.7,
  922. lightZ + offsetZ * 0.7,
  923. angle
  924. );
  925. }
  926. }
  927.  
  928. return { start, end, length, angle };
  929. }
  930.  
  931. function createLightPost(x, z, angle) {
  932. const postHeight = 15;
  933. const postGeom = new THREE.CylinderGeometry(0.3, 0.3, postHeight);
  934. const postMat = new THREE.MeshPhongMaterial({ color: 0x555555 });
  935. const post = new THREE.Mesh(postGeom, postMat);
  936.  
  937. post.position.set(x, postHeight / 2, z);
  938. post.castShadow = true;
  939. city.add(post);
  940.  
  941. // Arm extending over road
  942. const armLength = 6;
  943. const armGeom = new THREE.CylinderGeometry(0.2, 0.2, armLength);
  944. const arm = new THREE.Mesh(armGeom, postMat);
  945.  
  946. arm.position.set(0, postHeight / 2, armLength / 2);
  947. arm.rotation.x = Math.PI / 2;
  948. post.add(arm);
  949.  
  950. // Light
  951. const lightGeom = new THREE.SphereGeometry(0.8);
  952. const lightMat = new THREE.MeshBasicMaterial({
  953. color: 0xffffaa,
  954. transparent: true,
  955. opacity: 0.8
  956. });
  957. const lightBulb = new THREE.Mesh(lightGeom, lightMat);
  958.  
  959. lightBulb.position.set(0, 0, armLength - 1);
  960. arm.add(lightBulb);
  961.  
  962. // Point light
  963. const light = new THREE.PointLight(0xffffee, 0.8, 20);
  964. light.position.copy(lightBulb.position);
  965. light.userData = { type: 'streetLight' };
  966. arm.add(light);
  967.  
  968. // Store for day/night updates
  969. if (!city.userData.streetLights) {
  970. city.userData.streetLights = [];
  971. }
  972. city.userData.streetLights.push(light);
  973.  
  974. return post;
  975. }
  976.  
  977. function generateVehicles() {
  978. // Clear existing vehicles
  979. while (vehicles.children.length > 0) {
  980. vehicles.remove(vehicles.children[0]);
  981. }
  982.  
  983. const targetVehicleCount = Math.floor(200 * settings.trafficDensity);
  984.  
  985. // Create new vehicles
  986. for (let i = 0; i < targetVehicleCount; i++) {
  987. createVehicle();
  988. }
  989. }
  990.  
  991. function createVehicle() {
  992. // Random vehicle type
  993. const vehicleType = Math.random();
  994. let length, width, height, color;
  995.  
  996. if (vehicleType < 0.7) {
  997. // Car
  998. length = 4 + Math.random();
  999. width = 2;
  1000. height = 1.5;
  1001.  
  1002. // Random car color
  1003. const carColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff, 0xffffff, 0x000000];
  1004. color = carColors[Math.floor(Math.random() * carColors.length)];
  1005. } else if (vehicleType < 0.9) {
  1006. // Truck
  1007. length = 8 + Math.random() * 4;
  1008. width = 2.5;
  1009. height = 3;
  1010. color = 0x999999;
  1011. } else {
  1012. // Bus
  1013. length = 12;
  1014. width = 2.5;
  1015. height = 3;
  1016. color = 0x3388ff;
  1017. }
  1018.  
  1019. // Create vehicle body
  1020. const bodyGeom = new THREE.BoxGeometry(length, height, width);
  1021. const bodyMat = new THREE.MeshPhongMaterial({
  1022. color: color,
  1023. shininess: 80
  1024. });
  1025. const vehicle = new THREE.Mesh(bodyGeom, bodyMat);
  1026.  
  1027. // Add wheels (4 or 6 depending on length)
  1028. const wheelRadius = 0.7;
  1029. const wheelGeom = new THREE.CylinderGeometry(wheelRadius, wheelRadius, 0.5, 8);
  1030. const wheelMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
  1031.  
  1032. const wheelPositions = [
  1033. { x: -length/2 + wheelRadius, z: -width/2 - 0.2 },
  1034. { x: -length/2 + wheelRadius, z: width/2 + 0.2 },
  1035. { x: length/2 - wheelRadius, z: -width/2 - 0.2 },
  1036. { x: length/2 - wheelRadius, z: width/2 + 0.2 }
  1037. ];
  1038.  
  1039. // Add middle wheels for longer vehicles
  1040. if (length > 6) {
  1041. wheelPositions.push({ x: 0, z: -width/2 - 0.2 });
  1042. wheelPositions.push({ x: 0, z: width/2 + 0.2 });
  1043. }
  1044.  
  1045. wheelPositions.forEach(pos => {
  1046. const wheel = new THREE.Mesh(wheelGeom, wheelMat);
  1047. wheel.position.set(pos.x, -height/2, pos.z);
  1048. wheel.rotation.z = Math.PI / 2;
  1049. vehicle.add(wheel);
  1050. });
  1051.  
  1052. // Add windows
  1053. if (vehicleType < 0.9) {
  1054. // Windshield and rear window
  1055. const windowMat = new THREE.MeshPhongMaterial({
  1056. color: 0x88ccff,
  1057. transparent: true,
  1058. opacity: 0.7
  1059. });
  1060.  
  1061. const frontWindowGeom = new THREE.PlaneGeometry(2, 1.2);
  1062. const frontWindow = new THREE.Mesh(frontWindowGeom, windowMat);
  1063. frontWindow.position.set(length/2 - 0.6, 0.2, 0);
  1064. frontWindow.rotation.y = Math.PI;
  1065. vehicle.add(frontWindow);
  1066.  
  1067. const rearWindowGeom = new THREE.PlaneGeometry(1.8, 1);
  1068. const rearWindow = new THREE.Mesh(rearWindowGeom, windowMat);
  1069. rearWindow.position.set(-length/2 + 0.5, 0.2, 0);
  1070. vehicle.add(rearWindow);
  1071. }
  1072.  
  1073. // Add vehicle to scene
  1074. vehicle.castShadow = true;
  1075. vehicle.receiveShadow = true;
  1076. vehicles.add(vehicle);
  1077.  
  1078. // Set random position on a road
  1079. const halfCity = CITY_SIZE / 2;
  1080. let roadX, roadZ, angle;
  1081.  
  1082. // 70% chance to place on main roads, 30% on ring highway
  1083. if (Math.random() < 0.7) {
  1084. // Position on grid roads
  1085. if (Math.random() < 0.5) {
  1086. // East-west roads
  1087. roadX = (Math.random() - 0.5) * CITY_SIZE;
  1088. roadZ = Math.floor(Math.random() * 5 - 2) * (CITY_SIZE / 4);
  1089. angle = 0;
  1090. } else {
  1091. // North-south roads
  1092. roadX = Math.floor(Math.random() * 5 - 2) * (CITY_SIZE / 4);
  1093. roadZ = (Math.random() - 0.5) * CITY_SIZE;
  1094. angle = Math.PI / 2;
  1095. }
  1096. } else {
  1097. // Position on ring highway
  1098. const ringAngle = Math.random() * Math.PI * 2;
  1099. const ringRadius = halfCity * 0.7;
  1100. roadX = Math.cos(ringAngle) * ringRadius;
  1101. roadZ = Math.sin(ringAngle) * ringRadius;
  1102. angle = ringAngle + Math.PI / 2;
  1103. }
  1104.  
  1105. // Adjust height to be on top of road
  1106. vehicle.position.set(roadX, height/2 + 1.1, roadZ);
  1107. vehicle.rotation.y = angle;
  1108.  
  1109. // Add movement info
  1110. vehicle.userData = {
  1111. speed: 0.2 + Math.random() * 0.4,
  1112. angle: angle
  1113. };
  1114.  
  1115. // Add lights
  1116. const headlightGeom = new THREE.SphereGeometry(0.3);
  1117. const headlightMat = new THREE.MeshBasicMaterial({ color: 0xffffee });
  1118.  
  1119. // Front headlights
  1120. const leftHeadlight = new THREE.Mesh(headlightGeom, headlightMat);
  1121. leftHeadlight.position.set(length/2 + 0.1, -0.2, width/2 - 0.5);
  1122. vehicle.add(leftHeadlight);
  1123.  
  1124. const rightHeadlight = new THREE.Mesh(headlightGeom, headlightMat);
  1125. rightHeadlight.position.set(length/2 + 0.1, -0.2, -width/2 + 0.5);
  1126. vehicle.add(rightHeadlight);
  1127.  
  1128. // Rear lights
  1129. const rearlightGeom = new THREE.SphereGeometry(0.2);
  1130. const rearlightMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  1131.  
  1132. const leftRearlight = new THREE.Mesh(rearlightGeom, rearlightMat);
  1133. leftRearlight.position.set(-length/2 - 0.1, -0.2, width/2 - 0.5);
  1134. vehicle.add(leftRearlight);
  1135.  
  1136. const rightRearlight = new THREE.Mesh(rearlightGeom, rearlightMat);
  1137. rightRearlight.position.set(-length/2 - 0.1, -0.2, -width/2 + 0.5);
  1138. vehicle.add(rightRearlight);
  1139.  
  1140. // Add actual lights
  1141. const headlight1 = new THREE.PointLight(0xffffee, 1, 15);
  1142. headlight1.position.copy(leftHeadlight.position);
  1143. vehicle.add(headlight1);
  1144.  
  1145. const headlight2 = new THREE.PointLight(0xffffee, 1, 15);
  1146. headlight2.position.copy(rightHeadlight.position);
  1147. vehicle.add(headlight2);
  1148.  
  1149. // Keep track of lights for day/night
  1150. vehicle.userData.lights = [headlight1, headlight2];
  1151.  
  1152. return vehicle;
  1153. }
  1154.  
  1155. function setupEventListeners() {
  1156. // Handle window resize
  1157. window.addEventListener('resize', () => {
  1158. camera.aspect = window.innerWidth / window.innerHeight;
  1159. camera.updateProjectionMatrix();
  1160. renderer.setSize(window.innerWidth, window.innerHeight);
  1161. });
  1162.  
  1163. // Setup slider controls
  1164. document.getElementById('timeSlider').addEventListener('input', (e) => {
  1165. settings.timeOfDay = parseFloat(e.target.value);
  1166. document.getElementById('timeValue').textContent = formatTime(settings.timeOfDay);
  1167. updateLighting();
  1168. });
  1169.  
  1170. document.getElementById('trafficSlider').addEventListener('input', (e) => {
  1171. settings.trafficDensity = parseFloat(e.target.value);
  1172. document.getElementById('trafficValue').textContent = Math.round(settings.trafficDensity * 100) + '%';
  1173. generateVehicles();
  1174. });
  1175.  
  1176. document.getElementById('fogSlider').addEventListener('input', (e) => {
  1177. settings.fogIntensity = parseFloat(e.target.value);
  1178. document.getElementById('fogValue').textContent = Math.round(settings.fogIntensity * 10000) / 100 + '%';
  1179. scene.fog.density = settings.fogIntensity;
  1180. });
  1181.  
  1182. document.getElementById('neonSlider').addEventListener('input', (e) => {
  1183. settings.neonIntensity = parseFloat(e.target.value);
  1184. document.getElementById('neonValue').textContent = Math.round(settings.neonIntensity * 50) + '%';
  1185. updateNeonLights();
  1186. });
  1187.  
  1188. document.getElementById('cameraHeightSlider').addEventListener('input', (e) => {
  1189. settings.cameraHeight = parseFloat(e.target.value);
  1190. document.getElementById('cameraHeightValue').textContent = settings.cameraHeight;
  1191. });
  1192.  
  1193. document.getElementById('rotationSpeedSlider').addEventListener('input', (e) => {
  1194. settings.rotationSpeed = parseFloat(e.target.value);
  1195. document.getElementById('rotationSpeedValue').textContent = Math.round(settings.rotationSpeed * 100000) / 1000 + '%';
  1196. });
  1197. }
  1198.  
  1199. function formatTime(hours) {
  1200. const h = Math.floor(hours);
  1201. const m = Math.floor((hours * 60) % 60);
  1202. return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
  1203. }
  1204.  
  1205. function updateLighting() {
  1206. // Update sky colors based on time of day
  1207. if (skyDome && skyDome.material.uniforms) {
  1208. skyDome.material.uniforms.timeOfDay.value = settings.timeOfDay;
  1209. }
  1210.  
  1211. // Calculate sun position and intensity based on time
  1212. const sunAngle = ((settings.timeOfDay - 6) / 12) * Math.PI;
  1213. const sunHeight = Math.sin(sunAngle) * 500;
  1214. const sunDistance = Math.cos(sunAngle) * 500;
  1215.  
  1216. sunLight.position.set(sunDistance, sunHeight, 0);
  1217.  
  1218. // Set sun intensity based on height (day/night)
  1219. let sunIntensity = 0;
  1220.  
  1221. if (settings.timeOfDay > 6 && settings.timeOfDay < 18) {
  1222. // Day time
  1223. sunIntensity = 1;
  1224. if (settings.timeOfDay < 8) {
  1225. // Dawn transition
  1226. sunIntensity = (settings.timeOfDay - 6) / 2;
  1227. } else if (settings.timeOfDay > 16) {
  1228. // Dusk transition
  1229. sunIntensity = (18 - settings.timeOfDay) / 2;
  1230. }
  1231. }
  1232.  
  1233. sunLight.intensity = sunIntensity;
  1234.  
  1235. // Set moon at opposite position to sun
  1236. moonLight.position.set(-sunDistance, -sunHeight, 0);
  1237.  
  1238. // Moon intensity is inverse of sun
  1239. let moonIntensity = 0;
  1240.  
  1241. if (settings.timeOfDay < 6 || settings.timeOfDay > 18) {
  1242. moonIntensity = 0.3;
  1243. if (settings.timeOfDay > 18 && settings.timeOfDay < 20) {
  1244. // Dusk to night transition
  1245. moonIntensity = 0.3 * ((settings.timeOfDay - 18) / 2);
  1246. } else if (settings.timeOfDay > 4 && settings.timeOfDay < 6) {
  1247. // Night to dawn transition
  1248. moonIntensity = 0.3 * ((6 - settings.timeOfDay) / 2);
  1249. }
  1250. }
  1251.  
  1252. moonLight.intensity = moonIntensity;
  1253.  
  1254. // Update ambient light
  1255. const ambientIntensity = Math.max(0.1, Math.min(0.6, sunIntensity * 0.6));
  1256. ambientLight.intensity = ambientIntensity;
  1257.  
  1258. // Update fog color based on time of day
  1259. if (settings.timeOfDay > 6 && settings.timeOfDay < 18) {
  1260. // Day
  1261. scene.fog.color.setHex(0x93a5d6);
  1262. } else {
  1263. // Night
  1264. scene.fog.color.setHex(0x000030);
  1265. }
  1266.  
  1267. // Handle street lights
  1268. updateStreetLights();
  1269.  
  1270. // Handle building windows
  1271. updateBuildingWindows();
  1272.  
  1273. // Handle vehicle headlights
  1274. updateVehicleLights();
  1275.  
  1276. // Update neon signs
  1277. updateNeonLights();
  1278. }
  1279.  
  1280. function updateStreetLights() {
  1281. if (city.userData.streetLights) {
  1282. const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
  1283.  
  1284. city.userData.streetLights.forEach(light => {
  1285. light.intensity = isNight ? 1 : 0;
  1286. });
  1287. }
  1288. }
  1289.  
  1290. function updateBuildingWindows() {
  1291. // Go through all buildings with windows
  1292. city.traverse(obj => {
  1293. if (obj.windows) {
  1294. const timeOfDay = settings.timeOfDay;
  1295. let windowsLit = 0;
  1296.  
  1297. // Night time - most windows should be lit
  1298. if (timeOfDay >= 17 || timeOfDay <= 7) {
  1299. windowsLit = 0.7 + Math.random() * 0.2;
  1300. }
  1301. // Early morning - some windows lit
  1302. else if (timeOfDay > 7 && timeOfDay < 9) {
  1303. windowsLit = 0.3 + Math.random() * 0.2;
  1304. }
  1305. // Late afternoon - some windows lit
  1306. else if (timeOfDay > 15 && timeOfDay < 17) {
  1307. windowsLit = 0.5 + Math.random() * 0.2;
  1308. }
  1309. // Daytime - few windows lit
  1310. else {
  1311. windowsLit = 0.1 + Math.random() * 0.1;
  1312. }
  1313.  
  1314. // Update window materials
  1315. obj.windows.forEach(window => {
  1316. if (Math.random() < windowsLit) {
  1317. window.material.emissive.setHex(0x555511);
  1318. window.material.emissiveIntensity = 1;
  1319. } else {
  1320. window.material.emissive.setHex(0x000000);
  1321. window.material.emissiveIntensity = 0;
  1322. }
  1323. });
  1324. }
  1325. });
  1326. }
  1327.  
  1328. function updateVehicleLights() {
  1329. // Update all vehicle lights based on time of day
  1330. const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
  1331.  
  1332. vehicles.children.forEach(vehicle => {
  1333. if (vehicle.userData.lights) {
  1334. vehicle.userData.lights.forEach(light => {
  1335. light.intensity = isNight ? 1 : 0;
  1336. });
  1337. }
  1338. });
  1339. }
  1340.  
  1341. function updateNeonLights() {
  1342. // Only show neon lights at night, but power depends on neonIntensity slider
  1343. const isNight = settings.timeOfDay < 6 || settings.timeOfDay > 17;
  1344. const intensity = isNight ? settings.neonIntensity : 0;
  1345.  
  1346. neonLights.children.forEach(neon => {
  1347. if (neon.userData.type === 'neon') {
  1348. neon.material.opacity = 0.6 * intensity;
  1349. neon.material.emissiveIntensity = intensity;
  1350. }
  1351. });
  1352. }
  1353.  
  1354. function animate() {
  1355. requestAnimationFrame(animate);
  1356.  
  1357. // Update camera position
  1358. cameraAngle += settings.rotationSpeed;
  1359. const cameraDistance = 700;
  1360.  
  1361. camera.position.x = Math.cos(cameraAngle) * cameraDistance;
  1362. camera.position.z = Math.sin(cameraAngle) * cameraDistance;
  1363. camera.position.y = settings.cameraHeight;
  1364.  
  1365. camera.lookAt(0, 0, 0);
  1366.  
  1367. // Update vehicle positions
  1368. updateVehicles();
  1369.  
  1370. // Animate blinking lights (aircraft warnings)
  1371. animateBlinkingLights();
  1372.  
  1373. renderer.render(scene, camera);
  1374. }
  1375.  
  1376. function updateVehicles() {
  1377. vehicles.children.forEach(vehicle => {
  1378. const speed = vehicle.userData.speed;
  1379. const angle = vehicle.userData.angle;
  1380.  
  1381. // Move vehicle
  1382. vehicle.position.x += Math.cos(angle) * speed;
  1383. vehicle.position.z += Math.sin(angle) * speed;
  1384.  
  1385. // Check if vehicle has gone off the map and reset
  1386. const halfCity = CITY_SIZE / 2;
  1387. if (
  1388. vehicle.position.x > halfCity + 50 ||
  1389. vehicle.position.x < -halfCity - 50 ||
  1390. vehicle.position.z > halfCity + 50 ||
  1391. vehicle.position.z < -halfCity - 50
  1392. ) {
  1393. // Reset to opposite side
  1394. if (Math.abs(vehicle.position.x) > Math.abs(vehicle.position.z)) {
  1395. vehicle.position.x = -Math.sign(vehicle.position.x) * halfCity;
  1396. } else {
  1397. vehicle.position.z = -Math.sign(vehicle.position.z) * halfCity;
  1398. }
  1399. }
  1400. });
  1401. }
  1402.  
  1403. function animateBlinkingLights() {
  1404. // Animation for blinking lights (aircraft warnings, etc.)
  1405. const time = Date.now() * 0.001;
  1406.  
  1407. // Check all objects with blinking lights
  1408. scene.traverse(obj => {
  1409. if (obj.userData && obj.userData.blinkRate) {
  1410. const rate = obj.userData.blinkRate;
  1411. if (obj.type === 'PointLight') {
  1412. const blinkState = Math.sin(time * rate * Math.PI) > 0;
  1413. obj.intensity = blinkState ? 1 : 0;
  1414. }
  1415. }
  1416. });
  1417. }
  1418.  
  1419. // Initialize on load
  1420. window.addEventListener('load', init);
  1421. </script>
  1422. </body>
  1423. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement