Understanding how to position, rotate, and scale objects in 3D space.
Every Object3D has three fundamental transform properties:
val mesh = Mesh(geometry, material)
mesh.position.set(1f, 2f, 3f)
mesh.rotation.y = PI / 2
mesh.scale.set(2f, 2f, 2f)
Position is a Vector3 representing the object's location relative to its parent.
// Set all components
mesh.position.set(x, y, z)
// Set individual components
mesh.position.x = 5f
mesh.position.y = 0f
mesh.position.z = -3f
// Copy from another vector
mesh.position.copy(otherPosition)
// Add to current position
mesh.position.add(offset)
// Local position (relative to parent)
val local = mesh.position // Vector3
// World position (absolute)
val world = mesh.getWorldPosition(Vector3()) // Allocates or reuses target
// Move along local axes
mesh.translateX(distance) // Local X axis
mesh.translateY(distance) // Local Y axis
mesh.translateZ(distance) // Local Z axis
// Move along arbitrary axis
mesh.translateOnAxis(axis, distance)
Rotation can be represented as Euler angles or Quaternions.
Euler angles are intuitive but can suffer from gimbal lock.
// Set rotation (radians)
mesh.rotation.x = PI / 4 // 45° around X
mesh.rotation.y = PI / 2 // 90° around Y
mesh.rotation.z = 0f
// Set all at once
mesh.rotation.set(x, y, z)
// Rotation order matters
mesh.rotation.order = EulerOrder.YXZ // Default is XYZ
Quaternions avoid gimbal lock and interpolate smoothly.
// Set from axis and angle
mesh.quaternion.setFromAxisAngle(Vector3.UP, angle)
// Set from Euler
mesh.quaternion.setFromEuler(euler)
// Set from rotation matrix
mesh.quaternion.setFromRotationMatrix(matrix)
// Combine rotations
mesh.quaternion.multiply(otherQuaternion)
// Interpolate (smooth rotation)
mesh.quaternion.slerp(targetQuaternion, t) // t = 0 to 1
Point an object at a target:
// Look at world position
mesh.lookAt(target)
mesh.lookAt(x, y, z)
// Cameras typically look down -Z
camera.lookAt(Vector3.ZERO)
// Rotate around local axes
mesh.rotateX(angle)
mesh.rotateY(angle)
mesh.rotateZ(angle)
// Rotate around arbitrary local axis
mesh.rotateOnAxis(axis, angle)
// Rotate around world axis
mesh.rotateOnWorldAxis(Vector3.UP, angle)
Scale is a Vector3 multiplier for the object's size.
// Uniform scale
mesh.scale.setScalar(2f) // 2x in all directions
// Non-uniform scale
mesh.scale.set(2f, 1f, 0.5f) // Wide and flat
// Individual axes
mesh.scale.x = 1.5f
Child scale combines with parent scale:
parent.scale.set(2f, 2f, 2f)
child.scale.set(0.5f, 0.5f, 0.5f)
// Child appears at 1x scale in world (2 * 0.5 = 1)
Internally, transforms are represented as 4x4 matrices.
// Auto-updated from position/rotation/scale
mesh.matrix
// Manual composition
mesh.matrixAutoUpdate = false
mesh.matrix.compose(position, quaternion, scale)
// Includes all parent transforms
mesh.matrixWorld
// Force update
mesh.updateMatrixWorld(force = true)
Extract position/rotation/scale from a matrix:
val position = Vector3()
val quaternion = Quaternion()
val scale = Vector3()
matrix.decompose(position, quaternion, scale)
Relative to the object's parent:
// Position in parent's coordinate system
mesh.position
// Direction in local space
val forward = Vector3(0f, 0f, 1f) // +Z is forward in local space
Absolute coordinates in the scene:
// Convert local to world
val worldPoint = localPoint.clone()
mesh.localToWorld(worldPoint)
// Convert world to local
val localPoint = worldPoint.clone()
mesh.worldToLocal(localPoint)
2D coordinates on the viewport:
// World to screen (normalized device coordinates)
val screenPos = worldPos.clone().project(camera)
// Screen to world ray
val raycaster = Raycaster()
raycaster.setFromCamera(mouseNDC, camera)
var angle = 0f
val radius = 5f
val center = Vector3()
fun animate(deltaTime: Float) {
angle += deltaTime
mesh.position.x = center.x + cos(angle) * radius
mesh.position.z = center.z + sin(angle) * radius
mesh.lookAt(center)
}
val path = CatmullRomCurve3(points)
var t = 0f
fun animate(deltaTime: Float) {
t = (t + deltaTime * speed) % 1f
// Position on curve
path.getPoint(t, mesh.position)
// Orientation along curve
val tangent = path.getTangent(t)
mesh.lookAt(mesh.position.clone().add(tangent))
}
val targetPosition = Vector3()
val lerpFactor = 0.1f
fun animate() {
mesh.position.lerp(targetPosition, lerpFactor)
}
val targetQuaternion = Quaternion()
val slerpFactor = 0.1f
fun animate() {
mesh.quaternion.slerp(targetQuaternion, slerpFactor)
}
fun animate() {
// Full billboard
mesh.quaternion.copy(camera.quaternion)
// Y-axis only (cylindrical)
mesh.rotation.y = atan2(
camera.position.x - mesh.position.x,
camera.position.z - mesh.position.z
)
}
By default, objects rotate around their origin. To change the pivot:
// Method 1: Offset geometry
geometry.translate(0f, -0.5f, 0f) // Move pivot up
// Method 2: Use a parent group
val pivot = Group()
pivot.position.copy(desiredPivot)
val mesh = Mesh(geometry, material)
mesh.position.sub(desiredPivot) // Offset mesh
pivot.add(mesh)
scene.add(pivot)
// Rotate around the pivot
pivot.rotation.y += 0.01f
Visualize local axes:
val axes = AxesHelper(5f) // 5 unit length
mesh.add(axes)
Visualize a direction:
val arrow = ArrowHelper(
direction = Vector3.UP,
origin = Vector3.ZERO,
length = 2f,
color = Color.RED
)
scene.add(arrow)
Visualize bounding box:
val boxHelper = BoxHelper(mesh, Color.YELLOW)
scene.add(boxHelper)
matrixAutoUpdate = false for static objectsupdateMatrixWorld// For many static objects
mesh.matrixAutoUpdate = false
mesh.updateMatrix()
// Only update when needed
if (needsUpdate) {
mesh.position.copy(newPosition)
mesh.updateMatrix()
}