Navigate

Search topics across all sections

GitHub
Scene Basics

Coordinate System

Every object in your 3D scene has an address — a position in 3D space. Understanding how this addressing system works is the key to placing objects exactly where you want them.

You place a cube at position [0, 0, 5] expecting it to float above the ground. Instead, it flies straight toward the camera and fills the entire screen. You try [0, 5, 0] and now it's up in the sky. The axes aren't what you expected.

terminal
Object appears at wrong position. Expected 'up' but got 'toward camera'.

Real-world

Imagine you're looking at a city map. Every building has an address based on three things:

X = East/West streets — Positive X is to the right (East), negative is left (West).

Y = Altitude — How high something is. Positive Y is up (a skyscraper), negative is underground (a subway).

Z = North/South avenues — Positive Z comes toward you (South), negative goes away from you (North).

The origin (0, 0, 0) is city center. Every object in your scene has an address in this city.

X Axis

Left / Right

Y Axis

Up / Down

Z Axis

Toward / Away

Origin

(0, 0, 0) = Center

Hands-On: Placing Objects in 3D Space

Let's place some objects and see how the coordinate system works in practice. Remember: X is left/right, Y is up/down, Z is toward/away.

Step 1: Place an object using its address

App.tsxTSX
<mesh position={[2, 1, 0]}>
  <boxGeometry />
  <meshStandardMaterial color="coral" />
</mesh>

This places a cube 2 units to the right and 1 unit up from the center. The position prop takes an array of [x, y, z]. Think of it as a street address: 2 blocks east, 1 floor up, ground level.

Step 2: Rotate and scale

App.tsxTSX
<mesh
  position={[0, 0, 0]}
  rotation={[0, Math.PI / 4, 0]}
  scale={[1, 2, 1]}
>
  <boxGeometry />
  <meshStandardMaterial color="skyblue" />
</mesh>

Rotation is in radians (Math.PI / 4 = 45 degrees). Scale stretches the cube — here it's twice as tall on Y. Notice the order: Three.js applies Scale first, then Rotation, then Position.

Step 3: Group objects together

App.tsxTSX
<group position={[3, 0, 0]}>
  <mesh position={[0, 1, 0]}>
    <sphereGeometry args={[0.5]} />
    <meshStandardMaterial color="gold" />
  </mesh>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="teal" />
  </mesh>
</group>

Groups are like neighborhoods. The group is at [3, 0, 0], and the sphere inside is at [0, 1, 0] relative to the group — so its real-world address is [3, 1, 0]. Move the group, and everything inside moves together.

What you just learned

Three.js uses a Y-up coordinate system: X = left/right, Y = up/down, Z = toward/away from the viewer.

The origin (0, 0, 0) is the center of the world. Every object has a position relative to this center.

Rotation is measured in radians, not degrees. Math.PI = 180 degrees, Math.PI / 2 = 90 degrees.

Groups let you create local coordinate systems — children are positioned relative to their parent.

Question

If a child mesh is at position [0, 2, 0] inside a group at [5, 0, 0], where is the child in world space? What happens to the child if you rotate the group 90 degrees around the Y axis?

Think about it...

You set a mesh's rotation to [0, 90, 0]. Instead of rotating 90 degrees, the object spins wildly. What went wrong?

Hint: Remember, Three.js doesn't use degrees...

Try These Challenges

Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.

Try This!

Beginner

Move the cube to [2, 0, -3] — where does it go?

Try This!

Beginner

Rotate by Math.PI/4 — what angle is that?

Try This!

Beginner

Scale to [2, 0.5, 1] — what shape do you get?

These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.

1
Confusing Y-up with Z-up
Objects appear rotated or in unexpected positions
Don't do this
App.tsxTSX
// Coming from Blender (Z-up) and assuming Z is up
<mesh position={[0, 0, 5]}>
  {/* Thinks this moves up — actually moves TOWARD the camera! */}
Three.js uses a Y-up right-handed coordinate system. If you're coming from Blender (Z-up) or Unreal (Z-up left-handed), the axes may be different from what you expect. glTF models exported from Blender auto-convert to Y-up.
2
Using degrees instead of radians
Rotations are wildly wrong — a 45-degree turn becomes 45 radians
Don't do this
App.tsxTSX
// WRONG: rotation uses radians, not degrees!
<mesh rotation={[0, 45, 0]}>
  {/* 45 radians = about 7 full spins! */}
All rotation values in Three.js are in radians. A full circle is 2 * Math.PI (~6.28) radians, not 360. Passing 45 means 45 radians, which is about 7 full rotations. Always use Math.PI fractions or THREE.MathUtils.degToRad().
3
Replacing position instead of mutating it
Object stops updating correctly
Don't do this
App.tsxTSX
// WRONG: Replacing the vector entirely in useFrame
useFrame(() => {
  ref.current.position = new Vector3(1, 2, 3);
  // Breaks the internal matrix update link!
});
Three.js Object3D's position, rotation, and scale are linked to the internal matrix system. Replacing them with a new object breaks this link. Always mutate with .set(), .copy(), or direct property assignment.

Best Practices

Use AxesHelper During Development

Add an axesHelper to your scene while building. The color coding (Red=X, Green=Y, Blue=Z) makes it immediately obvious which direction is which.

Use Groups for Hierarchies

Group related objects together. Moving or rotating the group transforms all children — perfect for things like robot arms, solar systems, or UI panels.

1 Unit = 1 Meter Convention

Stick to the convention that 1 unit equals 1 meter. This keeps physics, lighting, shadows, and VR/AR experiences working correctly without constant scaling headaches.

Prefer degToRad for Readability

When you think in degrees, use MathUtils.degToRad(45) instead of doing the math yourself. Your future self (and teammates) will thank you for the clarity.