Course outline
Build the Bike from Shapes
In one line
A bicycle is tubes, rings and a few blobs. You build the whole Lumen frame from cylinders, tori and a capsule, aim each tube between two points, group paired parts, and count what each part costs.
Think of it like this
A child builds a toy bike from a construction kit: straws for the frame, two rings for wheels, a bead for the seat. Nothing is carved. Every part is a simple shape, put in the right place at the right angle. Our bike is the same, with numbers in place of glue.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- unit vector
- An arrow of length exactly 1.
vector.normalize()makes one. Many rotation functions need it. - quaternion
- A way to store a rotation as four numbers.
setFromUnitVectors(a, b)makes the rotation that turns arrowaonto arrowb. - lookAt
- Turns an object so its +z axis points at a point you give. Cameras are the exception: they look down −z.
- Group
- An object with no shape of its own that holds other objects, so they move, turn and are named together.
Step by step
01Name the key points
The frame is described by six points in metres, the numbers from the bike's drawing: the bottom bracket (where the pedals turn), the two axles, the seat cluster and the top and bottom of the head tube. Naming them in one object P means a number is typed once and read everywhere.
02Write tubeBetween once
tubeBetween(from, to, radius, name) does the three jobs: length from the distance, position halfway, turn from setFromUnitVectors. The direction is measured first (to − from), its length used, and only then normalised. Every tube on the bike is one call.
03Pairs in a loop
for (const z of [-0.055, 0.055]) runs twice. P.bottomBracket.clone().setZ(z) copies the point (never change P itself) and moves the copy to one side. The stay is then a tube between two copied points.
04Read the bill
traverse visits every object in the tree. The script adds up each mesh's triangles by name and prints them: the tubes are 64 each, the wheels 768 each. The renderer's own line then confirms 14 draw calls: 13 bike parts and the stand.
Try it yourself
- 1
Make the wheels cheap
In the bike example change
TorusGeometry(0.33, 0.022, 8, 48)toTorusGeometry(0.33, 0.022, 4, 24). The status bar now says 1480 triangles in the same 14 draw calls: each wheel drops from 768 to 192 triangles, and the tyres look slightly angular close up. - 2
Forget the pair
Change all four
setZ(z)calls in the stays loop tosetZ(0). The frame looks the same, because each pair now sits exactly on top of itself. The console still says 14 draw calls and 2632 triangles: the counters cannot see an overlap.
Code & diagrams
Open the scene tree: the bike is a Group holding named meshes. The status bar shows 14 draw calls, one per mesh. This is the geometry only; paint and the finished parts arrive in later phases.
Console
Run the scene to see its console.Expected console output
down tube 64 triangles
top tube 64 triangles
seat tube 64 triangles
fork 64 triangles
chain stay 128 triangles
seat stay 128 triangles
rear wheel 768 triangles
front wheel 768 triangles
seat post 64 triangles
saddle 216 triangles
handlebar 48 triangles
drawn: 14 draw calls, 2632 trianglesOrange: quaternion. Blue (see-through, fatter): lookAt on a geometry laid along z. They end up in the same place, so the orange tube sits inside the blue one.
Console
Run the scene to see its console.Expected console output
bottom bracket to head tube
quaternion: (0.00, 0.30, 0.00) -> (0.47, 0.70, 0.00)
lookAt: (0.00, 0.30, 0.00) -> (0.47, 0.70, 0.00)
straight up
quaternion: (0.00, 0.00, 0.00) -> (0.00, 1.00, 0.00)
lookAt: (0.00, 0.00, 0.00) -> (0.00, 1.00, 0.00)
straight down
quaternion: (0.00, 1.00, 0.00) -> (0.00, 0.00, 0.00)
lookAt: (0.00, 1.00, 0.00) -> (0.00, 0.00, 0.00)
sideways, along z
quaternion: (0.00, 0.50, -0.50) -> (0.00, 0.50, 0.50)
lookAt: (0.00, 0.50, -0.50) -> (0.00, 0.50, 0.50)
same point: length 0 - quaternion 0, 0, 0, 1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Skip normalize()
In the aiming example change to.clone().sub(from).normalize() to to.clone().sub(from).
Myth vs fact
Myth
A tube needs a special tube geometry.
Fact
A cylinder is a tube. TubeGeometry follows a curve, which this bike does not need yet; straight parts are cylinders, placed with two points.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
A zero-length tube (the same point twice) produces a cylinder of height 0 and a rotation that is the identity (
0, 0, 0, 1): nothing throws, nothing shows. If a part vanishes, printfrom.distanceTo(to)for it. - ▸
Paired parts could share one geometry: both stays of a pair have the same length and radius, so
new CylinderGeometryonce and twoMeshobjects would upload it once. Here each has its own, for clarity.
Remember this
- 1
The problem this solves: the shapes from Topic 4.1 all sit at the origin, pointing up or along an axis. A bike frame needs a tube that runs from one exact point to another at a slant. The skill of this topic is placing a simple shape so its two ends land exactly on two points.
- 2
Tubes from cylinders. A
CylinderGeometry(r, r, length, 16)is centred on its own origin and runs along its y axis. To join pointato pointb: make the lengtha.distanceTo(b), put the cylinder halfway ((a + b) / 2), and turn it so its y axis points alongb − a.quaternion.setFromUnitVectors(from, to)builds exactly that turn. It needs both vectors to be unit vectors (length 1), so callnormalize()on the direction first. (Recall: Phase 2'stubeBetween.) - 3
**
lookAtis the other way.**object.lookAt(point)turns an object so its +z axis points at the point (a camera points the other way, down −z). A cylinder lies along y, so you first turn the geometry (geometry.rotateX(Math.PI / 2)) to lay it along z. The second example proves both ways land the ends on the same points. - 4
Wheels from tori. A
TorusGeometry(0.33, 0.022, 8, 48)is a ring of radius 0.33 (the wheel) whose tube is 0.022 thick (the tyre), 8 segments round the tube and 48 round the ring. It stands upright in the x-y plane, so its axle runs along z: the wheel needs no turning. Placed at an axle point, its lowest point is0.35 − 0.33 − 0.022 ≈ 0, on the stand's top. - 5
Count the cost. Every tube costs 64 triangles at 16 sides. A tyre costs 768. The saddle capsule costs 216. The two wheels are more than half of all the triangles in the scene. When it needs to be cheaper, look at the wheels first, and the status bar shows what each change saves.
Explain it without notes
How do you place a cylinder so its two ends are exactly on two points?
Why are P.bottomBracket.clone().setZ(z) and not P.bottomBracket.setZ(z) used for the stays?
Practice
Add a kickstand tube from the bottom bracket to (−0.2, 0, 0) with radius 0.012 and print how many triangles it adds.
Make tubeBetween refuse a zero-length tube: it should throw a helpful error naming the tube.
Done when you can
Place a cylinder between two points with
setFromUnitVectorsand check its endsSay why
normalize()is needed and what goes wrong without itBuild a pair of parts in a named Group