Created
May 9, 2026 04:41
-
-
Save palaniraja/4d3542030925413436d0950b03a1f6e5 to your computer and use it in GitHub Desktop.
Threejs RC Trainer
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"> | |
| <title>Threejs RC Trainer</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background: #c2c9d1; | |
| } | |
| #hud { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| color: white; | |
| font-family: sans-serif; | |
| background: rgba(0, 0, 0, 0.7); | |
| padding: 15px; | |
| border-radius: 8px; | |
| pointer-events: none; | |
| z-index: 100; | |
| border: 1px solid #00bcd4; | |
| } | |
| .key { | |
| color: #00bcd4; | |
| font-weight: bold; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="hud"> | |
| <span class="key">W / S</span> : Pitch | <span class="key">A / D</span> : Roll<br> | |
| <span class="key">Arrows L / R</span> : Yaw | <span class="key">Up / Dn</span> : Throttle | |
| </div> | |
| <script type="importmap"> | |
| { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } } | |
| </script> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0xc2c9d1); | |
| const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| const light = new THREE.DirectionalLight(0xffffff, 1); | |
| light.position.set(5, 10, 7); | |
| scene.add(light, new THREE.AmbientLight(0xffffff, 0.6)); | |
| const matMain = new THREE.MeshStandardMaterial({ color: 0x00bcd4, flatShading: true }); | |
| const matNose = new THREE.MeshStandardMaterial({ color: 0xffffff, flatShading: true }); | |
| const matControl = new THREE.MeshStandardMaterial({ color: 0xff0000, flatShading: true }); | |
| const matTire = new THREE.MeshStandardMaterial({ color: 0x333333, flatShading: true }); | |
| const airplane = new THREE.Group(); | |
| scene.add(airplane); | |
| // 1. FUSELAGE (Cylinder) | |
| const fuse = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.5, 3.5, 8), matMain); | |
| fuse.rotation.x = -Math.PI / 2; | |
| airplane.add(fuse); | |
| // 2. NOSE & PROP | |
| const nose = new THREE.Mesh(new THREE.ConeGeometry(0.5, 0.6, 8), matNose); | |
| nose.rotation.x = Math.PI / 2; | |
| nose.position.z = 2.05; | |
| airplane.add(nose); | |
| const propGroup = new THREE.Group(); | |
| propGroup.position.z = 2.4; | |
| airplane.add(propGroup); | |
| const propBlade = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.8, 0.05), matTire); | |
| propGroup.add(propBlade); | |
| // 3. WINGS (Swept and Inset to remove gaps) | |
| function createSweptWing(isRight) { | |
| const group = new THREE.Group(); | |
| const shape = new THREE.Shape(); | |
| shape.moveTo(0, 0); | |
| shape.lineTo(2.5, -0.4); // Swept leading edge | |
| shape.lineTo(2.5, -1.2); // Tip trailing edge | |
| shape.lineTo(0, -1.2); // Root trailing edge | |
| shape.lineTo(0, 0); | |
| const wingGeom = new THREE.ExtrudeGeometry(shape, { depth: 0.05, bevelEnabled: false }); | |
| const wingMesh = new THREE.Mesh(wingGeom, matMain); | |
| wingMesh.rotation.x = Math.PI / 2; | |
| group.add(wingMesh); | |
| const aileron = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.1, 0.3), matControl); | |
| aileron.position.set(1.6, 0, -1.05); | |
| group.add(aileron); | |
| if (isRight) group.scale.x = -1; | |
| return { group, aileron }; | |
| } | |
| // MOVED INWARD (x=0.25) to intersect the cylinder and hide gaps | |
| const leftWing = createSweptWing(false); | |
| leftWing.group.position.set(0.25, 0.2, 0.8); | |
| airplane.add(leftWing.group); | |
| const rightWing = createSweptWing(true); | |
| rightWing.group.position.set(-0.25, 0.2, 0.8); | |
| airplane.add(rightWing.group); | |
| // 4. TAIL UNIT | |
| const fin = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.8, 0.6), matMain); | |
| fin.position.set(0, 0.6, -1.4); | |
| airplane.add(fin); | |
| const rudder = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.8, 0.3), matControl); | |
| rudder.position.set(0, 0, -0.45); | |
| fin.add(rudder); | |
| const stab = new THREE.Mesh(new THREE.BoxGeometry(2, 0.05, 0.6), matMain); | |
| stab.position.set(0, 0, -1.4); | |
| airplane.add(stab); | |
| const elevator = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.08, 0.3), matControl); | |
| elevator.position.set(0, 0, -0.45); | |
| stab.add(elevator); | |
| camera.position.set(6, 4, 6); | |
| camera.lookAt(0, 0, 0); | |
| // --- CONTROLS & ANIMATION --- | |
| const keys = {}; | |
| let thrust = 0.1; | |
| window.addEventListener('keydown', (e) => keys[e.code] = true); | |
| window.addEventListener('keyup', (e) => keys[e.code] = false); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| leftWing.aileron.rotation.x = 0; | |
| rightWing.aileron.rotation.x = 0; | |
| elevator.rotation.x = 0; | |
| rudder.rotation.y = 0; | |
| if (keys['KeyS']) { elevator.rotation.x = -0.5; airplane.rotation.x -= 0.02; } | |
| if (keys['KeyW']) { elevator.rotation.x = 0.5; airplane.rotation.x += 0.02; } | |
| if (keys['KeyA']) { | |
| leftWing.aileron.rotation.x = 0.5; | |
| rightWing.aileron.rotation.x = -0.5; | |
| airplane.rotation.z -= 0.03; | |
| } | |
| if (keys['KeyD']) { | |
| leftWing.aileron.rotation.x = -0.5; | |
| rightWing.aileron.rotation.x = 0.5; | |
| airplane.rotation.z += 0.03; | |
| } | |
| if (keys['ArrowLeft']) { rudder.rotation.y = -0.5; airplane.rotation.y += 0.02; } | |
| if (keys['ArrowRight']) { rudder.rotation.y = 0.5; airplane.rotation.y -= 0.02; } | |
| if (keys['ArrowUp']) thrust += 0.01; | |
| if (keys['ArrowDown']) thrust = Math.max(0.02, thrust - 0.01); | |
| propGroup.rotation.z += thrust; | |
| renderer.render(scene, camera); | |
| } | |
| animate(); | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment