| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Ultimate Three.js Flight Simulator</title> |
| | <style> |
| | body { margin: 0; background-color: #000; overflow: hidden; } |
| | canvas { display: block; } |
| | #instruments { position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: #0F0; padding: 10px; font-family: monospace; font-size: 14px; } |
| | </style> |
| | </head> |
| | <body> |
| |
|
| | <div id="instruments"> |
| | <b>Cockpit Instruments:</b><br> |
| | Alt: <span id="altimeter">0 ft</span><br> |
| | Airspeed: <span id="airspeed">0 kts</span><br> |
| | Heading: <span id="heading">0°</span><br> |
| | RPM: <span id="rpm">0</span><br> |
| | Fuel: <span id="fuel">100%</span><br> |
| | Flaps: <span id="flaps">0%</span> |
| | </div> |
| |
|
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| | <script> |
| | |
| | let scene = new THREE.Scene(); |
| | |
| | |
| | let sun = new THREE.DirectionalLight(0xFFFFFF, 1); |
| | sun.position.set(100, 100, 100); |
| | scene.add(sun); |
| | let skyGeom = new THREE.SphereGeometry(500, 32, 32); |
| | let skyMat = new THREE.ShaderMaterial({ |
| | 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 sunPosition; |
| | varying vec3 vWorldPosition; |
| | void main() { |
| | vec3 viewDirection = normalize(vWorldPosition); |
| | vec3 sunDir = normalize(sunPosition); |
| | float sunDot = max(dot(viewDirection, sunDir), 0.0); |
| | vec3 dayColor = vec3(0.2, 0.5, 1.0); // Blue sky |
| | vec3 sunsetColor = vec3(1.0, 0.5, 0.2); // Orange |
| | vec3 nightColor = vec3(0.05, 0.05, 0.1); // Dark blue |
| | float sunHeight = sunPosition.y / 100.0; |
| | vec3 skyColor = mix(nightColor, mix(sunsetColor, dayColor, sunHeight), smoothstep(-0.1, 0.1, sunHeight)); |
| | gl_FragColor = vec4(skyColor + sunDot * 0.5, 1.0); |
| | } |
| | `, |
| | side: THREE.BackSide, |
| | uniforms: { sunPosition: { value: sun.position } } |
| | }); |
| | let sky = new THREE.Mesh(skyGeom, skyMat); |
| | scene.add(sky); |
| | |
| | |
| | let terrainSize = 256; |
| | let terrainGeom = new THREE.PlaneGeometry(200, 200, terrainSize, terrainSize); |
| | for (let i = 0; i < terrainGeom.attributes.position.count; i++) { |
| | let x = terrainGeom.attributes.position.getX(i); |
| | let z = terrainGeom.attributes.position.getZ(i); |
| | let noise = (Math.sin(x * 0.1) + Math.sin(z * 0.1)) * 5 + (Math.random() * 2 - 1) * 2; |
| | terrainGeom.attributes.position.setY(i, noise); |
| | } |
| | let terrainMat = new THREE.MeshLambertMaterial({ color: 0x228B22 }); |
| | let terrain = new THREE.Mesh(terrainGeom, terrainMat); |
| | terrain.rotation.x = -Math.PI / 2; |
| | terrain.receiveShadow = true; |
| | scene.add(terrain); |
| | |
| | |
| | let planeGeom = new THREE.Group(); |
| | let fuselageGeom = new THREE.BoxGeometry(2, 0.4, 6); |
| | let fuselageMat = new THREE.MeshLambertMaterial({ color: 0xFFFFFF }); |
| | let fuselage = new THREE.Mesh(fuselageGeom, fuselageMat); |
| | fuselage.castShadow = true; |
| | let wingGeom = new THREE.BufferGeometry(); |
| | |
| | let wingMat = new THREE.MeshLambertMaterial({ color: 0xFFFFFF }); |
| | let wingLeft = new THREE.Mesh(wingGeom, wingMat); |
| | wingLeft.position.x = -0.5; |
| | let wingRight = wingLeft.clone(); |
| | wingRight.position.x = 0.5; |
| | planeGeom.add(fuselage); |
| | planeGeom.add(wingLeft); |
| | planeGeom.add(wingRight); |
| | |
| | |
| | |
| | let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| | planeGeom.add(camera); |
| | camera.position.y = 1.5; |
| | camera.position.z = 2; |
| | |
| | |
| | let ambientLight = new THREE.AmbientLight(0x333333); |
| | scene.add(ambientLight); |
| | |
| | |
| | let renderer = new THREE.WebGLRenderer({ antialias: true }); |
| | renderer.setSize(window.innerWidth, window.innerHeight); |
| | renderer.shadowMap.enabled = true; |
| | document.body.appendChild(renderer.domElement); |
| | |
| | |
| | let mass = 1500; |
| | let wingArea = 10; |
| | let wingLiftCoeff = 5.0; |
| | let dragCoeff = 0.5; |
| | let thrustMax = 5000; |
| | let thrust = 0; |
| | let rpm = 0; |
| | let fuel = 100; |
| | let flapsDeploy = 0; |
| | let velocity = new THREE.Vector3(); |
| | let wind = new THREE.Vector3((Math.random() * 2 - 1) * 5, 0, (Math.random() * 2 - 1) * 5); |
| | |
| | |
| | let keys = { w: false, s: false, a: false, d: false, q: false, e: false, f: false }; |
| | document.addEventListener('keydown', (e) => { |
| | switch (e.key) { |
| | case 'w': keys.w = true; break; |
| | case 's': keys.s = true; break; |
| | case 'a': keys.a = true; break; |
| | case 'd': keys.d = true; break; |
| | case 'q': keys.q = true; break; |
| | case 'e': keys.e = true; break; |
| | case 'f': if (flapsDeploy < 100) flapsDeploy += 10; break; |
| | } |
| | }); |
| | document.addEventListener('keyup', (e) => { |
| | switch (e.key) { |
| | case 'w': keys.w = false; break; |
| | case 's': keys.s = false; break; |
| | case 'a': keys.a = false; break; |
| | case 'd': keys.d = false; break; |
| | case 'q': keys.q = false; break; |
| | case 'e': keys.e = false; break; |
| | } |
| | }); |
| | |
| | |
| | function updateInstruments() { |
| | document.getElementById('altimeter').innerText = Math.round(planeGeom.position.y * 3.28084) + ' ft'; |
| | document.getElementById('airspeed').innerText = Math.round(velocity.length() * 1.94384) + ' kts'; |
| | document.getElementById('heading').innerText = Math.round(THREE.Math.radToDeg(planeGeom.rotation.y)) % 360 + '°'; |
| | document.getElementById('rpm').innerText = Math.round(rpm); |
| | document.getElementById('fuel').innerText = Math.round(fuel) + '%'; |
| | document.getElementById('flaps').innerText = flapsDeploy + '%'; |
| | } |
| | |
| | let pitch = 0, yaw = 0, roll = 0; |
| | function animate() { |
| | requestAnimationFrame(animate); |
| | |
| | |
| | if (keys.w && fuel > 0) { |
| | thrust = Math.min(thrustMax, thrust + 100); |
| | rpm = Math.min(2500, rpm + 50); |
| | fuel -= 0.05; |
| | } else { |
| | thrust = Math.max(0, thrust - 100); |
| | rpm = Math.max(0, rpm - 50); |
| | } |
| | |
| | |
| | let airspeedVec = velocity.clone().sub(wind); |
| | let airspeed = airspeedVec.length(); |
| | let angleOfAttack = Math.acos(airspeedVec.dot(new THREE.Vector3(0, 1, 0).applyQuaternion(planeGeom.quaternion))); |
| | let lift = 0.5 * 1.225 * wingArea * wingLiftCoeff * airspeed * airspeed * Math.sin(angleOfAttack) * (1 + flapsDeploy / 100); |
| | let drag = 0.5 * 1.225 * dragCoeff * wingArea * airspeed * airspeed; |
| | if (angleOfAttack > Math.PI / 4) lift *= 0.5; |
| | |
| | |
| | let liftVec = new THREE.Vector3(0, lift, 0).applyQuaternion(planeGeom.quaternion); |
| | let dragVec = airspeedVec.clone().multiplyScalar(-drag / airspeed); |
| | let thrustVec = new THREE.Vector3(0, 0, thrust).applyQuaternion(planeGeom.quaternion); |
| | let totalForce = new THREE.Vector3().add(liftVec).add(dragVec).add(thrustVec).add(new THREE.Vector3(0, -mass * 9.81, 0)); |
| | |
| | |
| | velocity.add(totalForce.divideScalar(mass).multiplyScalar(1/60)); |
| | planeGeom.position.add(velocity.clone().multiplyScalar(1/60)); |
| | |
| | |
| | let raycaster = new THREE.Raycaster(planeGeom.position, new THREE.Vector3(0, -1, 0)); |
| | let intersects = raycaster.intersectObject(terrain); |
| | if (intersects.length > 0 && intersects[0].distance < 5) { |
| | planeGeom.position.y = intersects[0].point.y + 2; |
| | velocity.y = Math.max(0, velocity.y * 0.8); |
| | } |
| | |
| | |
| | if (keys.a) yaw -= 0.01; |
| | if (keys.d) yaw += 0.01; |
| | if (keys.q) roll -= 0.02; |
| | if (keys.e) roll += 0.02; |
| | if (keys.s) pitch += 0.005; |
| | if (keys.w) pitch -= 0.005; |
| | |
| | |
| | planeGeom.rotation.order = 'ZXY'; |
| | planeGeom.rotation.z = roll; |
| | planeGeom.rotation.x = pitch; |
| | planeGeom.rotation.y = yaw; |
| | |
| | |
| | let time = Date.now() * 0.00005; |
| | sun.position.x = Math.sin(time) * 100; |
| | sun.position.z = Math.cos(time) * 100; |
| | sun.position.y = Math.sin(time * 0.5) * 50 + 50; |
| | skyMat.uniforms.sunPosition.value.copy(sun.position); |
| | ambientLight.intensity = 0.2 + sun.position.y / 200; |
| | |
| | updateInstruments(); |
| | renderer.render(scene, camera); |
| | } |
| | animate(); |
| | </script> |
| | </body> |
| | </html> |