cargames / index.html
akhaliq's picture
akhaliq HF Staff
Update index.html
c4c386f verified
<!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"></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>
// Scene
let scene = new THREE.Scene();
// Skybox with sun (gradient sky)
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);
// Terrain (Perlin noise hills)
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; // Simple noise
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);
// Airplane (detailed 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();
// ... (same wing vertices as before)
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);
// ... (add tail, wheels)
// 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);
// 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 mass = 1500; // kg
let wingArea = 10; // m²
let wingLiftCoeff = 5.0;
let dragCoeff = 0.5;
let thrustMax = 5000; // Newtons (engine force)
let thrust = 0;
let rpm = 0;
let fuel = 100; // %
let flapsDeploy = 0; // 0-100%
let velocity = new THREE.Vector3();
let wind = new THREE.Vector3((Math.random() * 2 - 1) * 5, 0, (Math.random() * 2 - 1) * 5); // m/s
// Controls
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;
}
});
// Instruments update
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);
// Engine
if (keys.w && fuel > 0) {
thrust = Math.min(thrustMax, thrust + 100);
rpm = Math.min(2500, rpm + 50);
fuel -= 0.05; // Consume fuel
} else {
thrust = Math.max(0, thrust - 100);
rpm = Math.max(0, rpm - 50);
}
// Aerodynamics
let airspeedVec = velocity.clone().sub(wind); // Relative airspeed
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; // Stall
// Forces
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));
// Update velocity & position
velocity.add(totalForce.divideScalar(mass).multiplyScalar(1/60));
planeGeom.position.add(velocity.clone().multiplyScalar(1/60));
// Collision (raycast down)
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; // Don't go through ground
velocity.y = Math.max(0, velocity.y * 0.8);
}
// Controls
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;
// Apply rotations
planeGeom.rotation.order = 'ZXY';
planeGeom.rotation.z = roll;
planeGeom.rotation.x = pitch;
planeGeom.rotation.y = yaw;
// Update sun position (day/night cycle)
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; // Rise/set
skyMat.uniforms.sunPosition.value.copy(sun.position);
ambientLight.intensity = 0.2 + sun.position.y / 200;
updateInstruments();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>