Created
March 27, 2025 13:56
-
-
Save shricodev/b22a2d583f1a56beb2df2dcf636a1a88 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Simple Flight Simulator</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| } | |
| canvas { | |
| display: block; | |
| } | |
| #instructions { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| color: white; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| padding: 10px; | |
| border-radius: 5px; | |
| font-family: Arial, sans-serif; | |
| } | |
| #hud { | |
| position: absolute; | |
| bottom: 10px; | |
| left: 10px; | |
| color: white; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| padding: 10px; | |
| border-radius: 5px; | |
| font-family: Arial, sans-serif; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="instructions"> | |
| <h2>Flight Simulator</h2> | |
| <p>Controls:</p> | |
| <ul> | |
| <li>W / Up: Pitch Down (forward)</li> | |
| <li>S / Down: Pitch Up (backward)</li> | |
| <li>A / Left: Roll Left</li> | |
| <li>D / Right: Roll Right</li> | |
| <li>Q: Yaw Left</li> | |
| <li>E: Yaw Right</li> | |
| <li>Space: Increase Thrust</li> | |
| <li>Shift: Decrease Thrust</li> | |
| </ul> | |
| </div> | |
| <div id="hud"></div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script> | |
| // Set up the scene, camera, and renderer | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x87ceeb); // Sky blue background | |
| const camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 10000, | |
| ); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| // Add lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(100, 100, 50); | |
| scene.add(directionalLight); | |
| // Create airplane | |
| const createAirplane = () => { | |
| const airplane = new THREE.Group(); | |
| // Fuselage | |
| const fuselageGeometry = new THREE.BoxGeometry(5, 1, 1); | |
| const fuselageMaterial = new THREE.MeshPhongMaterial({ | |
| color: 0xff0000, | |
| }); | |
| const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial); | |
| airplane.add(fuselage); | |
| // Wings | |
| const wingGeometry = new THREE.BoxGeometry(2, 0.2, 6); | |
| const wingMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff }); | |
| const wing = new THREE.Mesh(wingGeometry, wingMaterial); | |
| wing.position.set(0, 0, 0); | |
| airplane.add(wing); | |
| // Tail | |
| const tailGeometry = new THREE.BoxGeometry(1, 0.5, 2); | |
| const tailMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff }); | |
| const tail = new THREE.Mesh(tailGeometry, tailMaterial); | |
| tail.position.set(-2.5, 0.25, 0); | |
| airplane.add(tail); | |
| // Vertical Stabilizer | |
| const vsGeometry = new THREE.BoxGeometry(1, 1, 0.2); | |
| const vsMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 }); | |
| const verticalStabilizer = new THREE.Mesh(vsGeometry, vsMaterial); | |
| verticalStabilizer.position.set(-2.5, 0.5, 0); | |
| airplane.add(verticalStabilizer); | |
| return airplane; | |
| }; | |
| // Create runway | |
| const createRunway = () => { | |
| const runwayGeometry = new THREE.BoxGeometry(20, 0.1, 100); | |
| const runwayMaterial = new THREE.MeshPhongMaterial({ color: 0x333333 }); | |
| const runway = new THREE.Mesh(runwayGeometry, runwayMaterial); | |
| runway.position.set(0, -0.55, 0); | |
| scene.add(runway); | |
| // Runway markings | |
| for (let i = -45; i <= 45; i += 10) { | |
| const markingGeometry = new THREE.BoxGeometry(1, 0.11, 2); | |
| const markingMaterial = new THREE.MeshPhongMaterial({ | |
| color: 0xffffff, | |
| }); | |
| const marking = new THREE.Mesh(markingGeometry, markingMaterial); | |
| marking.position.set(0, -0.49, i); | |
| scene.add(marking); | |
| } | |
| }; | |
| // Create ground with Minecraft-like texture | |
| const createGround = () => { | |
| const canvas = document.createElement("canvas"); | |
| canvas.width = 256; | |
| canvas.height = 256; | |
| const context = canvas.getContext("2d"); | |
| // Base ground color | |
| context.fillStyle = "#8B4513"; // Saddle brown | |
| context.fillRect(0, 0, 256, 256); | |
| // Add grid pattern for Minecraft-like effect | |
| context.strokeStyle = "#654321"; | |
| context.lineWidth = 1; | |
| for (let i = 0; i < 256; i += 16) { | |
| context.beginPath(); | |
| context.moveTo(0, i); | |
| context.lineTo(256, i); | |
| context.stroke(); | |
| context.beginPath(); | |
| context.moveTo(i, 0); | |
| context.lineTo(i, 256); | |
| context.stroke(); | |
| } | |
| const groundTexture = new THREE.CanvasTexture(canvas); | |
| groundTexture.wrapS = THREE.RepeatWrapping; | |
| groundTexture.wrapT = THREE.RepeatWrapping; | |
| groundTexture.repeat.set(100, 100); | |
| const groundGeometry = new THREE.PlaneGeometry(1000, 1000); | |
| const groundMaterial = new THREE.MeshLambertMaterial({ | |
| map: groundTexture, | |
| side: THREE.DoubleSide, | |
| }); | |
| const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
| ground.rotation.x = Math.PI / 2; | |
| ground.position.y = -0.6; | |
| scene.add(ground); | |
| }; | |
| // Generate random Minecraft-style cityscape | |
| const generateCityscape = () => { | |
| const citySize = 250; | |
| const buildingCount = 100; | |
| // Building colors - blocky, Minecraft-like | |
| const buildingColors = [ | |
| 0x888888, 0x666666, 0x8b4513, 0xa52a2a, 0x808080, 0xd3d3d3, 0x4682b4, | |
| 0x556b2f, | |
| ]; | |
| // Simple procedural texture function | |
| const createBuildingTexture = (color) => { | |
| const canvas = document.createElement("canvas"); | |
| canvas.width = 64; | |
| canvas.height = 64; | |
| const context = canvas.getContext("2d"); | |
| // Background color | |
| context.fillStyle = `#${color.toString(16).padStart(6, "0")}`; | |
| context.fillRect(0, 0, 64, 64); | |
| // Add grid lines for block effect | |
| context.strokeStyle = "#000000"; | |
| context.lineWidth = 1; | |
| // Horizontal and vertical lines for block pattern | |
| for (let i = 0; i < 64; i += 16) { | |
| context.beginPath(); | |
| context.moveTo(0, i); | |
| context.lineTo(64, i); | |
| context.stroke(); | |
| context.beginPath(); | |
| context.moveTo(i, 0); | |
| context.lineTo(i, 64); | |
| context.stroke(); | |
| } | |
| // Create windows | |
| if (Math.random() > 0.3) { | |
| context.fillStyle = "#FFFF99"; | |
| for (let y = 4; y < 64; y += 16) { | |
| for (let x = 4; x < 64; x += 16) { | |
| if (Math.random() > 0.3) { | |
| context.fillRect(x, y, 8, 8); | |
| } | |
| } | |
| } | |
| } | |
| return new THREE.CanvasTexture(canvas); | |
| }; | |
| for (let i = 0; i < buildingCount; i++) { | |
| // Random position within city limits (avoiding the runway) | |
| let x, z; | |
| do { | |
| x = Math.random() * citySize - citySize / 2; | |
| z = Math.random() * citySize - citySize / 2; | |
| } while (Math.abs(x) < 15 && Math.abs(z) < 55); // Avoid runway area | |
| // Random building size in block units (more blocky for Minecraft feel) | |
| const width = Math.ceil(Math.random() * 5) * 2; | |
| const height = Math.ceil(Math.random() * 20) * 2; | |
| const depth = Math.ceil(Math.random() * 5) * 2; | |
| // Select a random color | |
| const color = | |
| buildingColors[Math.floor(Math.random() * buildingColors.length)]; | |
| // Create a texture for the building | |
| const texture = createBuildingTexture(color); | |
| // Create building with texture | |
| const buildingGeometry = new THREE.BoxGeometry(width, height, depth); | |
| const buildingMaterial = new THREE.MeshLambertMaterial({ | |
| map: texture, | |
| color: 0xffffff, // Use white to let the texture define the color | |
| }); | |
| const building = new THREE.Mesh(buildingGeometry, buildingMaterial); | |
| // Position building | |
| building.position.set(x, height / 2 - 0.6, z); | |
| scene.add(building); | |
| } | |
| }; | |
| // Create airplane and add to the scene | |
| const airplane = createAirplane(); | |
| airplane.position.set(0, 0, -50); // Start at the beginning of the runway | |
| scene.add(airplane); | |
| // Create runway, ground, and cityscape | |
| createRunway(); | |
| createGround(); | |
| generateCityscape(); | |
| // Set up camera position | |
| camera.position.set(0, 5, -65); | |
| camera.lookAt(airplane.position); | |
| // Flight physics parameters | |
| const flightPhysics = { | |
| speed: 0, | |
| thrust: 0, | |
| maxThrust: 1, | |
| acceleration: 0.01, | |
| drag: 0.005, | |
| liftCoefficient: 0.01, | |
| gravity: 0.01, | |
| takeoffSpeed: 0.7, | |
| maxPitchAngle: Math.PI / 4, | |
| maxRollAngle: Math.PI / 3, | |
| rotationSpeed: 0.02, | |
| yawSpeed: 0.01, | |
| pitchSensitivity: 0.005, | |
| rollSensitivity: 0.01, | |
| flying: false, | |
| }; | |
| // Flight controls | |
| const controls = { | |
| moveForward: false, | |
| moveBackward: false, | |
| moveLeft: false, | |
| moveRight: false, | |
| yawLeft: false, | |
| yawRight: false, | |
| increaseThrust: false, | |
| decreaseThrust: false, | |
| }; | |
| // Set up control event listeners | |
| document.addEventListener("keydown", (event) => { | |
| switch (event.key.toLowerCase()) { | |
| case "w": | |
| case "arrowup": | |
| controls.moveForward = true; | |
| break; | |
| case "s": | |
| case "arrowdown": | |
| controls.moveBackward = true; | |
| break; | |
| case "a": | |
| case "arrowleft": | |
| controls.moveLeft = true; | |
| break; | |
| case "d": | |
| case "arrowright": | |
| controls.moveRight = true; | |
| break; | |
| case "q": | |
| controls.yawLeft = true; | |
| break; | |
| case "e": | |
| controls.yawRight = true; | |
| break; | |
| case " ": | |
| controls.increaseThrust = true; | |
| break; | |
| case "shift": | |
| controls.decreaseThrust = true; | |
| break; | |
| } | |
| }); | |
| document.addEventListener("keyup", (event) => { | |
| switch (event.key.toLowerCase()) { | |
| case "w": | |
| case "arrowup": | |
| controls.moveForward = false; | |
| break; | |
| case "s": | |
| case "arrowdown": | |
| controls.moveBackward = false; | |
| break; | |
| case "a": | |
| case "arrowleft": | |
| controls.moveLeft = false; | |
| break; | |
| case "d": | |
| case "arrowright": | |
| controls.moveRight = false; | |
| break; | |
| case "q": | |
| controls.yawLeft = false; | |
| break; | |
| case "e": | |
| controls.yawRight = false; | |
| break; | |
| case " ": | |
| controls.increaseThrust = false; | |
| break; | |
| case "shift": | |
| controls.decreaseThrust = false; | |
| break; | |
| } | |
| }); | |
| // Handle window resize | |
| window.addEventListener("resize", () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Update function for flight physics | |
| const updateFlightPhysics = () => { | |
| // Adjust thrust based on controls | |
| if (controls.increaseThrust) { | |
| flightPhysics.thrust += 0.01; | |
| if (flightPhysics.thrust > flightPhysics.maxThrust) { | |
| flightPhysics.thrust = flightPhysics.maxThrust; | |
| } | |
| } | |
| if (controls.decreaseThrust) { | |
| flightPhysics.thrust -= 0.01; | |
| if (flightPhysics.thrust < 0) { | |
| flightPhysics.thrust = 0; | |
| } | |
| } | |
| // Calculate acceleration and speed | |
| const acceleration = flightPhysics.thrust * flightPhysics.acceleration; | |
| flightPhysics.speed += acceleration; | |
| flightPhysics.speed -= flightPhysics.drag * flightPhysics.speed; | |
| // Update flight status based on speed | |
| if (flightPhysics.speed >= flightPhysics.takeoffSpeed) { | |
| flightPhysics.flying = true; | |
| } | |
| // Apply controls for pitch, roll, and yaw | |
| if (controls.moveForward) { | |
| airplane.rotation.x -= flightPhysics.pitchSensitivity; | |
| if (airplane.rotation.x < -flightPhysics.maxPitchAngle) { | |
| airplane.rotation.x = -flightPhysics.maxPitchAngle; | |
| } | |
| } | |
| if (controls.moveBackward) { | |
| airplane.rotation.x += flightPhysics.pitchSensitivity; | |
| if (airplane.rotation.x > flightPhysics.maxPitchAngle) { | |
| airplane.rotation.x = flightPhysics.maxPitchAngle; | |
| } | |
| } | |
| if (controls.moveLeft) { | |
| airplane.rotation.z += flightPhysics.rollSensitivity; | |
| if (airplane.rotation.z > flightPhysics.maxRollAngle) { | |
| airplane.rotation.z = flightPhysics.maxRollAngle; | |
| } | |
| } | |
| if (controls.moveRight) { | |
| airplane.rotation.z -= flightPhysics.rollSensitivity; | |
| if (airplane.rotation.z < -flightPhysics.maxRollAngle) { | |
| airplane.rotation.z = -flightPhysics.maxRollAngle; | |
| } | |
| } | |
| if (controls.yawLeft) { | |
| airplane.rotation.y += flightPhysics.yawSpeed; | |
| } | |
| if (controls.yawRight) { | |
| airplane.rotation.y -= flightPhysics.yawSpeed; | |
| } | |
| // Calculate lift | |
| const lift = flightPhysics.speed * flightPhysics.liftCoefficient; | |
| // Apply gravity when not flying or insufficient lift | |
| let verticalMovement = lift; | |
| if (!flightPhysics.flying || lift < flightPhysics.gravity) { | |
| verticalMovement -= flightPhysics.gravity; | |
| } | |
| // Calculate forward movement direction | |
| const direction = new THREE.Vector3(0, 0, 1); | |
| direction.applyQuaternion(airplane.quaternion); | |
| direction.normalize(); | |
| // Update airplane position | |
| airplane.position.x += direction.x * flightPhysics.speed; | |
| // Handle altitude | |
| if (flightPhysics.flying) { | |
| airplane.position.y += | |
| verticalMovement + direction.y * flightPhysics.speed; | |
| if (airplane.position.y < 0) { | |
| airplane.position.y = 0; | |
| flightPhysics.flying = false; | |
| } | |
| } else { | |
| // Keep the plane on the ground before takeoff | |
| airplane.position.y = 0; | |
| } | |
| airplane.position.z += direction.z * flightPhysics.speed; | |
| // Gradually level off when no pitch or roll input | |
| if (!controls.moveForward && !controls.moveBackward) { | |
| airplane.rotation.x *= 0.99; // Gradually return to level | |
| } | |
| if (!controls.moveLeft && !controls.moveRight) { | |
| airplane.rotation.z *= 0.99; // Gradually return to level | |
| } | |
| }; | |
| // Update HUD information | |
| const updateHUD = () => { | |
| const hud = document.getElementById("hud"); | |
| hud.innerHTML = ` | |
| <div>Speed: ${(flightPhysics.speed * 100).toFixed(1)} knots</div> | |
| <div>Thrust: ${(flightPhysics.thrust * 100).toFixed(0)}%</div> | |
| <div>Altitude: ${airplane.position.y.toFixed(1)} meters</div> | |
| <div>Status: ${flightPhysics.flying ? "Flying" : "On Ground"}</div> | |
| `; | |
| }; | |
| // Animation loop with smooth camera following | |
| let lastCameraPosition = new THREE.Vector3(); | |
| let lastCameraLookAt = new THREE.Vector3(); | |
| const animate = () => { | |
| requestAnimationFrame(animate); | |
| updateFlightPhysics(); | |
| // Calculate ideal camera position | |
| const cameraOffset = new THREE.Vector3(0, 5, -15); | |
| // Create a modified quaternion with reduced pitch and roll effects | |
| const smoothQuat = airplane.quaternion.clone(); | |
| const euler = new THREE.Euler().setFromQuaternion(smoothQuat); | |
| // Reduce the effect of pitch and roll for camera | |
| euler.x *= 0.3; | |
| euler.z *= 0.3; | |
| smoothQuat.setFromEuler(euler); | |
| // Apply the smoothed rotation to the camera offset | |
| cameraOffset.applyQuaternion(smoothQuat); | |
| // Ideal camera position | |
| const idealPosition = airplane.position.clone().add(cameraOffset); | |
| // Smoothly interpolate to ideal position | |
| if (!lastCameraPosition.equals(new THREE.Vector3())) { | |
| lastCameraPosition.lerp(idealPosition, 0.1); | |
| camera.position.copy(lastCameraPosition); | |
| } else { | |
| lastCameraPosition.copy(idealPosition); | |
| camera.position.copy(idealPosition); | |
| } | |
| // Smooth look at target | |
| const idealLookAt = airplane.position.clone(); | |
| if (!lastCameraLookAt.equals(new THREE.Vector3())) { | |
| lastCameraLookAt.lerp(idealLookAt, 0.1); | |
| camera.lookAt(lastCameraLookAt); | |
| } else { | |
| lastCameraLookAt.copy(idealLookAt); | |
| camera.lookAt(idealLookAt); | |
| } | |
| updateHUD(); | |
| renderer.render(scene, camera); | |
| }; | |
| animate(); | |
| </script> | |
| </body> | |
| </html> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: https://youtu.be/iYIKbBvYkow