Course outline
The Project You Will Build
In one line
Your lumen-3d folder grows into the whole showroom. Here is its final layout, one line per folder and file, which phase adds what, and how the build topics work.
Think of it like this
A builder's plan shows every room before the first brick is laid, even though the house is built one room at a time. You always know where the kitchen will go, so you never block it with a wall. The folder tree below is the plan of your showroom.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- module
- One JavaScript file that
exports some functions or values for other files toimport. Splitting an app into modules keeps each file small and focused. - src/
- The source folder: the code you write. Vite reads it, follows the imports and builds the app from it.
- public/
- Files that are served exactly as they are, at the site's root (
public/data/3d/models/lumen-bike.glbis fetched as/data/3d/models/lumen-bike.glb; this course keeps its assets inpublic/data/3d/, the same paths Hectal uses, so every example runs unchanged on your computer). - glTF / .glb
- The standard file format for 3D models on the web;
.glbis the single binary file version. Phase 10 explains it.
Step by step
01The plan
This is the lumen-3d folder as it will look at the end of the course. You don't make these files now; each phase adds its own.
index.html the page that holds the canvas and the panel · package.json the dependencies, pinned · vite.config.js dev server and build settings · public/data/3d/ the assets, at the same paths as on this site: showroom-poster.png (Phase 1), models/ the bike as glTF (Phase 10), textures/ and hdr/ images and environment maps (Phase 6), draco/ decoder files for compressed models (Phase 10).
src/main.js starts the app: checks support, creates the world · src/world/renderer.js, scene.js, camera.js, lights.js, loop.js the parts every 3D app has · src/objects/bike.js the bike (shapes first, a loaded model later) · src/objects/showroom.js the floor, walls and stand.
src/systems/controls.js camera and keyboard controls · picking.js what did the user click · loader.js loading with progress and errors · physics.js the test ride · budget.js the draw-call and memory checks · src/materials/paint.glsl.js and paint.tsl.js the custom paint · src/ui/panel.js the colour and part picker · src/ui/a11y.js the text alternative and announcements · src/react/App.jsx, Bike.jsx the React version · tests/scene.test.js checks on the scene.
02Why world, objects and systems
The split follows a question: would another 3D app have this too? Every app has a renderer and a loop, so those go in world/. Only Lumen has this bike, so it goes in objects/. Picking and loading are behaviour that many objects use, so they go in systems/.
This kind of split (shared engine parts, the things in the world, and the systems that act on them) is common in real 3D apps and games, and it makes the interview question "how would you structure a three.js app?" easy to answer.
03One file now, modules soon
For the first few topics everything lives in src/main.js, because one file is easier to understand. The live example below is that file already sorted into functions, and each function's comment names the module it will move to. In Phase 3 you move them, and nothing about the scene changes.
04The build topics carry you
Each phase's Build topic shows the files it touches in full, so you can always compare yours with the course's, or copy the course's and continue. Mistakes in your own project are normal and useful: the black-screen checklist and the console will find most of them.
Try it yourself
- 1
Find the module for each job
Without looking back, say which file each of these will live in: the code that turns the stand (
world/loop.js), the colour picker (ui/panel.js), the code that loads the bike model (systems/loader.js), the bike's custom paint (materials/paint.glsl.js, laterpaint.tsl.js). - 2
Change the order
In the example, move
startLoop(...)aboveconst stand = createStand();(and passstandafter it is made). You can't:startLoopneeds the stand to exist. This is why apps are set up in a fixed order: scene, camera, renderer, lights, objects, then the loop.
Code & diagrams
Each function says where it moves to. The output lists the order things are made in, which is also the order every three.js app starts in.
Console
Run the scene to see its console.Expected console output
made, in order: scene -> camera -> renderer -> lights -> stand -> loopBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Add the stand twice
In the example, add a second scene.add(stand); just after the first one.
Myth vs fact
Myth
Small projects don't need folders.
Fact
Splitting early costs a few minutes. Splitting a 2,000-line file later costs days. The plan exists so that you never have to.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The world/objects/systems split is a light version of the entity-component-system idea games use: objects are the entities, and systems run over them every frame. Phase 16's physics system is the first one that really runs per frame over many objects.
- ▸
Keep three.js imports inside modules that need them. Phase 18 lazy-loads the whole 3D part of the page, which only works if the page's other code doesn't import three.js at the top.
Remember this
- 1
The problem this solves: a 3D app written as one long file works for a week and then becomes impossible to change: the camera code is tangled with the lights, the loader and the buttons. Knowing the final layout from the start, you put each new piece where it belongs.
- 2
The idea behind the layout.
src/world/holds what every 3D app needs (renderer, scene, camera, lights, the loop).src/objects/holds the things in the scene (the bike, the showroom).src/systems/holds behaviour that works on objects (controls, picking, loading, physics, the performance budget).src/materials/holds the custom shaders,src/ui/the HTML around the canvas, andsrc/react/the React version from Phase 11.public/holds files served as they are: models, textures, decoders. - 3
How it grows. Phase 0 makes
index.htmlandsrc/main.js. Phase 1 adds a support check tomain.js. Phase 2 startsobjects/bike.jswith the first frame tubes. Phase 3 splitsmain.jsinto theworld/modules andobjects/showroom.js. Phase 4 builds the rest of the bike from shapes. Phases 5–9 bring better lights and shadows, textures, camera views, picking and the turntable. Phase 10 exports the bike as a.glbfile intopublic/data/3d/models/and loads it withsystems/loader.js. Phase 11 addssrc/react/. Phases 12–13 add the paint shaders, 16 the physics, 17 the budget, 18ui/a11y.jsand the fallbacks. - 4
How a build topic works. The last topic of every phase is called *Build*. It says which files it adds or changes, shows each one in full (so if you fell behind, you can copy it and catch up), and has a live example that runs the same code on this page. The panel "Your project files" shows the whole tree as it stands after that topic.
Explain it without notes
Explain why the bike goes in src/objects/ but the camera controls go in src/systems/.
What does a Build topic give you that helps when you fall behind?
Practice
Where would you put a function that shows a "Loading 40%..." bar while the bike model downloads, and why?
Sketch (on paper) your lumen-3d folder as it is right now, after Topic 0.4.
Done when you can
Draw the final lumen-3d tree and say what each folder is for
Say which phase adds the bike, the loader, the React version and the physics
Explain what a Build topic shows and how to catch up with it