The controls module provides camera and object interaction controls.
import io.materia.controls.*
Orbiting camera controls for rotating around a target.
class OrbitControls(
camera: Camera,
config: ControlsConfig = ControlsConfig()
)
Input adapters call onPointerDown, onPointerMove, onPointerUp, onWheel, and the keyboard handlers. Call update(deltaSeconds) exactly once before each render.
| Property | Type | Default | Description |
|---|---|---|---|
minDistance | Float | 1 | Minimum zoom distance |
maxDistance | Float | 1000 | Maximum zoom distance |
minPolarAngle | Float | 0 | Minimum vertical angle |
maxPolarAngle | Float | PI | Maximum vertical angle |
minAzimuthAngle | Float | -Infinity | Minimum horizontal angle |
maxAzimuthAngle | Float | Infinity | Maximum horizontal angle |
enableDamping | Boolean | true | Smoothly converge to requested poses |
dampingTime | Float | 0.04 | Frame-rate-independent convergence time in seconds |
settleEpsilon | Float | 0.0001 | Threshold for snapping to a stable pose |
maxDeltaTime | Float | 0.05 | Clamp used after stalls or hidden tabs |
enableZoom | Boolean | true | Enable zooming |
zoomSpeed | Float | 1.0 | Zoom sensitivity |
enableRotate | Boolean | true | Enable rotation |
rotateSpeed | Float | 1.0 | Rotation sensitivity |
enablePan | Boolean | true | Enable panning |
panSpeed | Float | 1.0 | Pan sensitivity |
autoRotate | Boolean | false | Auto-rotate |
autoRotateSpeed | Float | 2.0 | Auto-rotate speed |
enableKeys | Boolean | true | Enable keyboard |
fun update(deltaTime: Float)
fun reset()
fun moveTo(position: Vector3, target: Vector3, duration: Float = 1f)
fun setPose(position: Vector3, target: Vector3)
fun cancelAnimation()
fun cancelMomentum()
fun isSettled(): Boolean
fun saveState(): ControlsState
fun restoreState(savedState: ControlsState)
fun getPolarAngle(): Float
fun getAzimuthalAngle(): Float
fun getDistance(): Float
controls.addEventListener(object : ControlsEventListener {
override fun onControlsChange() = requestRender()
override fun onControlsStart() = capturePointer()
override fun onControlsEnd() = releasePointer()
})
val controls = OrbitControls(
camera,
ControlsConfig(
minDistance = 2f,
maxDistance = 50f,
maxPolarAngle = PI / 2,
enablePan = false,
dampingTime = 0.04f
)
)
fun frame(deltaSeconds: Float) {
controls.update(deltaSeconds)
renderer.render(scene, camera)
}
controls.moveTo(
position = Vector3(8f, 6f, 10f),
target = mesh.position,
duration = 0.6f
)
Extends OrbitControls for map-style navigation (pan with left mouse).
class MapControls(
camera: Camera,
domElement: HTMLElement
)
val controls = MapControls(camera, renderer.domElement)
controls.enableDamping = true
controls.screenSpacePanning = false // Pan parallel to ground
controls.maxPolarAngle = PI / 2
Unconstrained rotation controls (no up direction).
class TrackballControls(
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | true | Enable controls |
rotateSpeed | Float | 1.0 | Rotation speed |
zoomSpeed | Float | 1.2 | Zoom speed |
panSpeed | Float | 0.3 | Pan speed |
noRotate | Boolean | false | Disable rotation |
noZoom | Boolean | false | Disable zoom |
noPan | Boolean | false | Disable pan |
staticMoving | Boolean | false | No inertia |
dynamicDampingFactor | Float | 0.2 | Inertia damping |
minDistance | Float | 0 | Min distance |
maxDistance | Float | Infinity | Max distance |
fun update()
fun reset()
fun dispose()
Flight simulator-style controls.
class FlyControls(
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
movementSpeed | Float | 1.0 | Movement speed |
rollSpeed | Float | 0.005 | Roll speed |
dragToLook | Boolean | false | Require drag to look |
autoForward | Boolean | false | Always move forward |
fun update(delta: Float)
fun dispose()
val controls = FlyControls(camera, renderer.domElement)
controls.movementSpeed = 10f
controls.rollSpeed = PI / 24
fun animate(deltaTime: Float) {
controls.update(deltaTime)
renderer.render(scene, camera)
}
First-person shooter-style controls.
class FirstPersonControls(
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | true | Enable controls |
movementSpeed | Float | 1.0 | Movement speed |
lookSpeed | Float | 0.005 | Look sensitivity |
lookVertical | Boolean | true | Allow vertical look |
autoForward | Boolean | false | Auto move forward |
activeLook | Boolean | true | Mouse look |
heightSpeed | Boolean | false | Height affects speed |
heightCoef | Float | 1.0 | Height coefficient |
heightMin | Float | 0 | Minimum height |
heightMax | Float | 1.0 | Maximum height |
constrainVertical | Boolean | false | Limit vertical angle |
verticalMin | Float | 0 | Min vertical angle |
verticalMax | Float | PI | Max vertical angle |
fun update(delta: Float)
fun lookAt(target: Vector3)
fun dispose()
First-person controls with pointer lock (mouse capture).
class PointerLockControls(
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
isLocked | Boolean | false | Is pointer locked |
minPolarAngle | Float | 0 | Min vertical angle |
maxPolarAngle | Float | PI | Max vertical angle |
// Lock pointer
fun lock()
// Unlock pointer
fun unlock()
// Connect/disconnect events
fun connect()
fun disconnect()
// Get look direction
fun getDirection(target: Vector3): Vector3
// Movement
fun moveForward(distance: Float)
fun moveRight(distance: Float)
fun dispose()
controls.addEventListener("lock") {
instructions.style.display = "none"
}
controls.addEventListener("unlock") {
instructions.style.display = "block"
}
val controls = PointerLockControls(camera, document.body)
scene.add(controls.getObject()) // Add camera to scene
// Click to lock
document.body.addEventListener("click") {
controls.lock()
}
// Movement
val velocity = Vector3()
val direction = Vector3()
var moveForward = false
var moveBackward = false
var moveLeft = false
var moveRight = false
document.addEventListener("keydown") { event ->
when (event.code) {
"KeyW" -> moveForward = true
"KeyS" -> moveBackward = true
"KeyA" -> moveLeft = true
"KeyD" -> moveRight = true
}
}
document.addEventListener("keyup") { event ->
when (event.code) {
"KeyW" -> moveForward = false
"KeyS" -> moveBackward = false
"KeyA" -> moveLeft = false
"KeyD" -> moveRight = false
}
}
fun animate(delta: Float) {
if (controls.isLocked) {
direction.z = (if (moveForward) 1 else 0) - (if (moveBackward) 1 else 0)
direction.x = (if (moveRight) 1 else 0) - (if (moveLeft) 1 else 0)
direction.normalize()
velocity.x -= velocity.x * 10f * delta
velocity.z -= velocity.z * 10f * delta
if (moveForward || moveBackward) velocity.z -= direction.z * 400f * delta
if (moveLeft || moveRight) velocity.x -= direction.x * 400f * delta
controls.moveRight(-velocity.x * delta)
controls.moveForward(-velocity.z * delta)
}
renderer.render(scene, camera)
}
Gizmo for translating, rotating, and scaling objects.
class TransformControls(
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | true | Enable controls |
mode | String | "translate" | Transform mode |
space | String | "world" | Coordinate space |
size | Float | 1 | Gizmo size |
axis | String? | null | Active axis |
showX | Boolean | true | Show X axis |
showY | Boolean | true | Show Y axis |
showZ | Boolean | true | Show Z axis |
translationSnap | Float? | null | Translation snap |
rotationSnap | Float? | null | Rotation snap |
scaleSnap | Float? | null | Scale snap |
dragging | Boolean | false | Is dragging |
// Translation (move)
controls.setMode("translate")
// Rotation
controls.setMode("rotate")
// Scale
controls.setMode("scale")
// World space
controls.setSpace("world")
// Local space (object-relative)
controls.setSpace("local")
// Attach to object
fun attach(object3d: Object3D): TransformControls
// Detach from object
fun detach(): TransformControls
// Set mode
fun setMode(mode: String)
// Set space
fun setSpace(space: String)
// Set size
fun setSize(size: Float)
// Get raycaster
fun getRaycaster(): Raycaster
fun dispose()
controls.addEventListener("change") {
renderer.render(scene, camera)
}
controls.addEventListener("dragging-changed") { event ->
orbitControls.enabled = !event.value
}
controls.addEventListener("objectChange") {
// Object was transformed
}
val transformControls = TransformControls(camera, renderer.domElement)
scene.add(transformControls)
// Attach to selected object
transformControls.attach(selectedMesh)
// Keyboard shortcuts
document.addEventListener("keydown") { event ->
when (event.key) {
"g" -> transformControls.setMode("translate")
"r" -> transformControls.setMode("rotate")
"s" -> transformControls.setMode("scale")
"x" -> transformControls.showX = !transformControls.showX
"y" -> transformControls.showY = !transformControls.showY
"z" -> transformControls.showZ = !transformControls.showZ
" " -> transformControls.setSpace(
if (transformControls.space == "local") "world" else "local"
)
}
}
// Disable orbit controls while transforming
transformControls.addEventListener("dragging-changed") { event ->
orbitControls.enabled = !event.value
}
Drag objects in the scene.
class DragControls(
objects: List<Object3D>,
camera: Camera,
domElement: HTMLElement
)
| Property | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | true | Enable controls |
transformGroup | Boolean | false | Transform group |
controls.addEventListener("dragstart") { event ->
event.`object`.material.emissive.set(0xaaaaaa)
}
controls.addEventListener("drag") { event ->
// Object being dragged
}
controls.addEventListener("dragend") { event ->
event.`object`.material.emissive.set(0x000000)
}
controls.addEventListener("hoveron") { event ->
// Hovering over object
}
controls.addEventListener("hoveroff") { event ->
// No longer hovering
}
val draggableObjects = listOf(cube1, cube2, cube3)
val dragControls = DragControls(draggableObjects, camera, renderer.domElement)
// Disable orbit while dragging
dragControls.addEventListener("dragstart") {
orbitControls.enabled = false
}
dragControls.addEventListener("dragend") {
orbitControls.enabled = true
}
Arcball-style rotation controls.
class ArcballControls(
camera: Camera,
domElement: HTMLElement,
scene: Scene? = null
)
Similar to OrbitControls with additional:
| Property | Type | Description |
|---|---|---|
cursorZoom | Boolean | Zoom at cursor position |
scaleFactor | Float | Zoom scale factor |
focusAnimationTime | Float | Focus animation duration |