Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save shricodev/b22a2d583f1a56beb2df2dcf636a1a88 to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/b22a2d583f1a56beb2df2dcf636a1a88 to your computer and use it in GitHub Desktop.
<!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>
@shricodev
Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment