cargames / index.html
akhaliq's picture
akhaliq HF Staff
Update index.html
011ac1b verified
raw
history blame
9.66 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Three.js Flight Simulator</title>
<style>
body { margin: 0; background-color: #000; overflow: hidden; }
canvas { display: block; }
#controls { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.7); padding: 10px; font-family: monospace; font-size: 14px; }
#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="controls">
<b>Flight Simulator Controls:</b><br>
W / ↑ : Pitch Up (Climb)<br>
S / ↓ : Pitch Down (Dive)<br>
A / ← : Yaw Left (Turn Left, but use roll for real turns)<br>
D / β†’ : Yaw Right (Turn Right, but use roll for real turns)<br>
Q / E : Roll Left/Right (Bank for turns)<br>
↑↑ Speed up (gradually)<br>
↓↓ Slow down (gradually)<br>
Mouse Look (drag to change view direction)
</div>
<div id="instruments">
<b>Flight 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>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Scene
let scene = new THREE.Scene();
// Skybox (gradient sky)
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: `
varying vec3 vWorldPosition;
void main() {
float heightFactor = (vWorldPosition.y + 250.0) / 500.0;
heightFactor = clamp(heightFactor, 0.0, 1.0);
vec3 topColor = vec3(0.1, 0.4, 0.8); // Light blue
vec3 bottomColor = vec3(0.8, 0.6, 0.2); // Light brown
gl_FragColor = vec4(mix(bottomColor, topColor, heightFactor), 1.0);
}
`,
side: THREE.BackSide
});
let sky = new THREE.Mesh(skyGeom, skyMat);
scene.add(sky);
// Ground (simple hills)
let groundGeom = new THREE.PlaneGeometry(200, 200, 64, 64);
for (let i = 0; i < groundGeom.attributes.position.count; i++) {
let x = groundGeom.attributes.position.getX(i);
let z = groundGeom.attributes.position.getZ(i);
let y = Math.sin(x * 0.1) * Math.cos(z * 0.1) * 5.0; // Simple hills
groundGeom.attributes.position.setY(i, y);
}
let groundMat = new THREE.MeshLambertMaterial({ color: 0x228B22 }); // Forest green
let ground = new THREE.Mesh(groundGeom, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Airplane (slightly nicer mesh)
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();
const wingVertices = new Float32Array([
-2, 0, 1, 2, 0, 1, 2, 0, -2,
-2, 0, 1, 2, 0, -2, -2, 0, -2,
]);
let posAttr = new THREE.BufferAttribute(wingVertices, 3);
wingGeom.setAttribute('position', posAttr);
let wingMat = new THREE.MeshLambertMaterial({ color: 0xFFFFFF });
let wingLeft = new THREE.Mesh(wingGeom, wingMat);
wingLeft.position.x = -0.5;
wingLeft.castShadow = true;
let wingRight = wingLeft.clone();
wingRight.position.x = 0.5;
let tailGeom = new THREE.BufferGeometry();
const tailVertices = new Float32Array([
0, 0.5, -3, -0.5, 0, -3, 0.5, 0, -3
]);
let tailAttr = new THREE.BufferAttribute(tailVertices, 3);
tailGeom.setAttribute('position', tailAttr);
let tailMat = new THREE.MeshLambertMaterial({ color: 0xFFFFFF });
let tail = new THREE.Mesh(tailGeom, tailMat);
tail.castShadow = true;
planeGeom.add(fuselage);
planeGeom.add(wingLeft);
planeGeom.add(wingRight);
planeGeom.add(tail);
scene.add(planeGeom);
// Camera
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;
// Lighting
let ambientLight = new THREE.AmbientLight(0x333333);
scene.add(ambientLight);
let dirLight = new THREE.DirectionalLight(0xFFFFFF, 0.8);
dirLight.position.set(10, 20, 10);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 100;
scene.add(dirLight);
// Renderer
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Flight dynamics
let pitch = 0; // radians
let yaw = 0;
let roll = 0;
let velocity = new THREE.Vector3(0, 0, 0); // m/s
let airspeed = 0; // knots
let altitude = 0; // meters
let heading = 0; // degrees
let liftForce = 0;
let dragForce = 0;
let gravity = new THREE.Vector3(0, -9.81, 0); // m/sΒ²
let mass = 1000; // kg (just a number)
let wingLiftCoefficient = 10.0; // Made-up lift coeff
let dragCoefficient = 0.5; // Parasitic drag
let maxStallAngle = Math.PI / 6; // 30 degrees
// Controls state
let keys = {
w: false, s: false, a: false, d: false, q: false, e: false
};
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'w': case 'ArrowUp': keys.w = true; break;
case 's': case 'ArrowDown': keys.s = true; break;
case 'a': case 'ArrowLeft': keys.a = true; break;
case 'd': case 'ArrowRight': keys.d = true; break;
case 'q': keys.q = true; break;
case 'e': keys.e = true; break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.key) {
case 'w': case 'ArrowUp': keys.w = false; break;
case 's': case 'ArrowDown': keys.s = false; break;
case 'a': case 'ArrowLeft': keys.a = false; break;
case 'd': case 'ArrowRight': keys.d = false; break;
case 'q': keys.q = false; break;
case 'e': keys.e = false; break;
}
});
// Mouse look
let mouseDown = false;
let lastMouseX, lastMouseY;
let sensitivity = 0.005;
document.addEventListener('mousedown', (e) => {
mouseDown = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
document.addEventListener('mouseup', () => mouseDown = false);
document.addEventListener('mousemove', (e) => {
if (mouseDown) {
let dx = e.clientX - lastMouseX;
let dy = e.clientY - lastMouseY;
yaw -= dx * sensitivity;
pitch -= dy * sensitivity;
pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch));
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
});
// Update instruments display
function updateInstruments() {
document.getElementById('altimeter').innerText = Math.round(altitude * 3.28084) + ' ft'; // meters to feet
document.getElementById('airspeed').innerText = Math.round(airspeed) + ' kts';
document.getElementById('heading').innerText = Math.round(THREE.Math.radToDeg(yaw)) % 360 + 'Β°';
}
// Main loop
function animate() {
requestAnimationFrame(animate);
// Aerodynamics
let velocityDir = new THREE.Vector3(0, 0, -1); // Plane's forward in local space
velocityDir.applyQuaternion(planeGeom.quaternion).normalize();
airspeed = velocity.length() * 1.94384; // m/s to knots (approx)
let angleOfAttack = Math.acos(velocityDir.dot(new THREE.Vector3(0, 1, 0).applyQuaternion(planeGeom.quaternion)));
liftForce = wingLiftCoefficient * airspeed * airspeed * Math.sin(angleOfAttack);
if (angleOfAttack > maxStallAngle) liftForce *= (1 - (angleOfAttack - maxStallAngle) / (Math.PI / 2 - maxStallAngle)); // Stall
dragForce = dragCoefficient * airspeed * airspeed;
// Forces
let lift = new THREE.Vector3(0, liftForce, 0).applyQuaternion(planeGeom.quaternion); // Lift always perpendicular to wings
let drag = velocityDir.clone().multiplyScalar(-dragForce);
let totalForce = new THREE.Vector3().add(gravity).add(lift).add(drag).divideScalar(mass);
// Update velocity & position (Verlet-ish integration)
velocity.add(totalForce.multiplyScalar(1/60)); // dt ~= 1/60s
planeGeom.position.add(velocity.clone().multiplyScalar(1/60));
// Collision with ground (very crude)
altitude = planeGeom.position.y - groundGeom.attributes.position.getY(0); // approx
if (altitude < 0) {
planeGeom.position.y += -altitude; // push back up
velocity.y = Math.max(0, velocity.y * 0.8); // dampen bounce
}
// Controls
if (keys.w) pitch -= 0.005;
if (keys.s) pitch += 0.005;
if (keys.a) yaw -= 0.01;
if (keys.d) yaw += 0.01;
if (keys.q) roll -= 0.02;
if (keys.e) roll += 0.02;
// Limit angles
pitch = Math.max(-Math.PI/3, Math.min(Math.PI/4, pitch)); // less extreme pitch
roll = Math.max(-Math.PI/4, Math.min(Math.PI/4, roll));
// Apply rotations (order matters)
planeGeom.rotation.order = 'ZXY'; // Roll, Pitch, Yaw (aviation standard)
planeGeom.rotation.z = roll;
planeGeom.rotation.x = pitch;
planeGeom.rotation.y = yaw;
// Update camera direction (following plane's rotation)
heading = THREE.Math.radToDeg(yaw) % 360;
updateInstruments();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>