The Art of the Drift
Engineering a Relaxing Hot Air Balloon System in Three.js
Date: September 18, 2026
Project: World Simulation
Focus: Vehicle Physics, Camera Immersion, and Collision Resolution
1. Abstract
The objective was to implement a Hot Air Balloon vehicle that felt “weightless” and “atmospheric.” Initial attempts resulted in “snappy” movement akin to a ground vehicle. Through iterative testing, we moved from Kinematic Movement (instant) to Momentum-Based Physics (drifting), while solving critical bugs regarding spawn-teleportation and collision locking.
2. The Evolution: What Went Wrong & What Went Right
❌ The “Bad” (Initial Pain Points)
- The “Car” Feel: Initially, the balloon stopped the millisecond the key was released. It felt like a floating car, not a balloon.
- The “Jump” Bug: Upon entering the balloon, the code forced a +3 height increase. This looked like a “glitchy jump” and ruined the smooth entry.
- The “Stuck” Paradox: When exiting in mid-air, the player was teleported instantly to the ground. If a building was beneath them, they spawned inside the mesh, triggering the isBlocked function and freezing the player in place.
- Speed Issues: Initial momentum was too high, making the balloon feel like a jet engine rather than a peaceful drift.
✅ The “Good” (The Wins)
- Momentum Flow: Implementing a velocity vector allowed the balloon to “glide.” Now, it takes time to speed up and time to slow down.
- The Aerial Perspective: Switching to a 45-degree diagonal camera shifted the experience from “playing a game” to “viewing a landscape.”
- Natural Exit: By removing the forced ground-snap, players now fall naturally from the balloon, adding a sense of gravity and danger.
3. Technical Implementation (The “Golden” Code)
To achieve the final feel, we modified two core systems: the Vehicle Logic (index.html) and the Object Classification (build.js).
A. The Physics Engine (index.html)
We replaced instant position updates with a Velocity-Friction Model.
The Constants:
code JavaScript
// Ultra-Slow Floating Physics
const BALLOON_ACCEL = 0.5; // Slow start, prevents "snapping"
const BALLOON_FRICTION = 0.998; // Near-zero friction = infinite glide
const BALLOON_MAX_SPEED = 4.0; // Capped for a relaxing pace
const BALLOON_LIFT_SPEED = 5.0; // Constant lift for the Q key
The Logic Loop:
code JavaScript
// Inside animate() -> if (isBalloon)
const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(activeControl.quaternion);
// Horizontal Momentum (W/S)
if (keys['w']) balloonVelocity.addScaledVector(forward, BALLOON_ACCEL * delta);
if (keys['s']) balloonVelocity.addScaledVector(forward, -BALLOON_ACCEL * delta);
// Direct Lift (Q) - No momentum, just constant rise
if (keys['q']) activeControl.position.y += BALLOON_LIFT_SPEED * delta;
// Downward Drift (E) - Uses momentum to "sink"
if (keys['e']) balloonVelocityY -= BALLOON_ACCEL * delta;
// Apply Glide Friction
balloonVelocity.multiplyScalar(BALLOON_FRICTION);
balloonVelocityY *= BALLOON_FRICTION;
// Final Position Update
activeControl.position.add(balloonVelocity);
activeControl.position.y += balloonVelocityY;
B. The Cinematic Camera (index.html)
To simulate “Sunlight at 9 AM,” we positioned the camera at a high diagonal offset.
code JavaScript
// Specialized Balloon Camera View
if (isDriving && currentVehicle && currentVehicle.userData && currentVehicle.userData.isBalloon) {
const camDist = 25;
// Offset camera 45 degrees top-down and behind
const offset = new THREE.Vector3(camDist, camDist, camDist).applyQuaternion(activeControl.quaternion);
_vLookTarget.copy(activeControl.position).add(offset);
camera.position.lerp(_vLookTarget, delta * 5);
camera.lookAt(activeControl.position);
}
C. The Build Classification (build.js)
For the physics to know it’s a balloon and not a car, we added metadata during the GLB load.
code JavaScript
// Inside createInScene(data)
const isBalloon = nameLower.includes('balloon') || nameLower.includes('hot-air');
if (isBalloon) {
obj.userData.isVehicle = true;
obj.userData.isBalloon = true; // This flag triggers the momentum physics
}
4. Conclusion & Final Tuning
The “Perfect Drift” was found by balancing Acceleration vs. Friction. By lowering acceleration to 0.5 and raising friction to 0.998, we achieved a state of “perpetual flow.”
The removal of the nearest.position.y += 3 line in toggleVehicle finally allowed the balloon to sit naturally at ground level 0, creating a seamless transition from walking to flying.
Current State: 🟢 Stable | Feeling: ☁️ Ethereal | Movement: 🌊 Fluid