May 17, 2025 · essay
Crafting a 3D Undersea World with Three.js and Anime.js
Diving deep into Three.js and Anime.js to build an animated 3D aquatic world, complete with interactive creatures and ambient effects.
The short version
- LLM
- My LLM assistant (Google Gemini 2.5 Pro Preview 05-06) was invaluable in this project and 100% of the code was written with its help.
- Why
- To transform a child's 2D story concept into a dynamic, interactive 3D underwater environment, leveraging the power of Three.js for rendering and Anime.js for expressive animations.
- Challenge
- Programmatically creating distinct 3D characters (squid, fish, jellyfish, shark) using Three.js geometries, orchestrating complex ambient and idle animations, and debugging 3D orientation and interaction logic.
- Outcome
- A vibrant single-page web application featuring multiple animated sea creatures with unique behaviors, ambient environmental effects like rising bubbles, and interactive hover/autonomous speech bubbles.
- AI approach
- My LLM assistant served as a 3D modeling consultant, animation choreographer, and tireless debugger, helping translate 2D ideas into 3D realities and untangling many a quaternion.
- Learnings
- Three.js's versatility for procedural modeling (LatheGeometry, TubeGeometry with custom curves), the power of Anime.js for tweening 3D object properties, and the critical importance of modular JavaScript for managing growing project complexity.
Why this project? The initial spark was to bring a child's imaginative 2D undersea story to life. However, the ambition quickly evolved: why not create a fully immersive 3D world? This project became an exploration into programmatic 3D modeling with Three.js and crafting expressive character animations using Anime.js, aiming for a lively, interactive single-page web application.
The Challenge(s): Creating a cast of distinct sea creatures – a squid ("Ing"), multiple fish, jellyfish, and a shark – entirely from code using Three.js primitives was a significant undertaking. Each character required careful geometric construction (CapsuleGeometry, LatheGeometry for fish/shark bodies, TubeGeometry with custom curves for tentacles/arms, ExtrudeGeometry for fins) to capture a unique look.
Animating these creatures to feel alive was another layer. We implemented:
- Idle Animations: Continuous subtle movements for tentacles, fins, and jellyfish bell pulsations.
- Ambient Movement: Random drifting patterns for most creatures, making them explore the 3D space.
- Specialized Shark Movement: Evolved from a simple patrol to a more natural "turn-and-swim" behavior, where the shark smoothly orients towards a new random target before swimming.
- Bubble Effects: Programmatically spawning and animating rising air bubbles to enhance the underwater atmosphere.
Debugging 3D orientation (getting the shark's fins right took a few tries!), managing animation states, and ensuring smooth performance with multiple animated entities were ongoing challenges. The modularization of JavaScript into separate files for each character and manager (scene, animation, interaction) became crucial as the project grew.
The Outcome: We now have a bustling little 3D undersea world! Key features include:
- A central squid ("Ing") with flowing arms and fins.
- Multiple colorful fish, each with a more defined body thanks to LatheGeometry.
- Graceful jellyfish pulsing and drifting with procedurally generated tentacles.
- A shark ("Sharky") that cruises the scene with more natural turns.
- Interactive hover-triggered speech bubbles for most characters.
- Autonomous, randomly timed speech bubbles for the shark, adding personality.
- A continuous stream of rising air bubbles.
- Basic camera controls via Three.js's OrbitControls.
The entire scene is rendered in real-time, with all animations and behaviors managed by JavaScript.
Key Learnings:
- Three.js for Procedural Models: LatheGeometry and TubeGeometry (with custom THREE.Curve paths) are incredibly powerful for creating organic shapes without external model files. Understanding local vs. world coordinates and object hierarchies is paramount for correct part assembly and orientation.
- Anime.js for 3D: Anime.js seamlessly animates properties of Three.js objects (position, rotation, scale), making complex movements and idle behaviors relatively straightforward to implement. Its timeline features are invaluable.
- Modular JavaScript is King: As features and characters were added, breaking down the monolithic JavaScript into modules for scene setup, character creation (one per type), animation management (further broken down by concern), and interaction handling was essential for sanity and scalability. ES6 modules with importmap provided a clean way to manage dependencies without a bundler for this project.
- 3D to 2D Projection for UI: Using vector.project(camera) to map 3D world coordinates to 2D screen coordinates is the standard technique for placing HTML/CSS UI elements (like speech bubbles) accurately over a Three.js scene.
- Debugging 3D is Visual: Often, the best way to debug orientation or animation issues is to temporarily add THREE.AxesHelper to objects, simplify animations, and iteratively test. Screenshots and videos were also very helpful in our process.
- Performance with Many Objects: While not yet an issue, the strategy of cloning materials for individual bubble opacity (then reverting to scaling for disappearance with shared material) highlighted the trade-offs between visual detail and performance when dealing with many small, animated entities.
The "AI" Approach (Still True!): My LLM collaborator was instrumental. From suggesting Three.js geometries and Anime.js techniques to helping debug complex rotation logic and untangling module import errors, it felt like true pair programming. It even helped draft this blog post!
This project has been a fantastic journey into the capabilities of modern web graphics libraries. The ability to programmatically define and animate an entire 3D world opens up so many creative possibilities. Next on the horizon: perhaps integrating Matter.js for some simple physics-based interactions or tackling more complex narrative animations from Eliska's original story!