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,
domElement: HTMLElement // or Window on JVM
)
| Property | Type | Default | Description | |----------|------|---------|-------------| | enabled | Boolean | true | Enable controls | | target | Vector3 | (0,0,0) | Orbit target point | | minDistance | Float | 0 | Minimum zoom distance | | maxDistance | Float | Infinity | Maximum zoom distance | | minZoom | Float | 0 | Minimum zoom (ortho) | | maxZoom | Float | Infinity | Maximum zoom (ortho) | | 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 | false | Enable smooth motion | | dampingFactor | Float | 0.05 | Damping amount | | 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 | | screenSpacePanning | Boolean | true | Pan in screen space | | keyPanSpeed | Float | 7.0 | Keyboard pan speed | | autoRotate | Boolean | false | Auto-rotate | | autoRotateSpeed | Float | 2.0 | Auto-rotate speed | | enableKeys | Boolean | true | Enable keyboard | | keys | Keys | default | Key bindings | | mouseButtons | MouseButtons | default | Mouse bindings | | touches | Touches | default | Touch bindings |
data class Keys(
val left: String = "ArrowLeft",
val up: String = "ArrowUp",
val right: String = "ArrowRight",
val bottom: String = "ArrowDown"
)
data class MouseButtons(
val left: MouseAction = MouseAction.ROTATE,
val middle: MouseAction = MouseAction.DOLLY,
val right: MouseAction = MouseAction.PAN
)
enum class MouseAction {
ROTATE, DOLLY, PAN, NONE
}
// Update controls (call each frame)
fun update(): Boolean
// Reset to initial state
fun reset()
// Save current state
fun saveState()
// Get polar angle
fun getPolarAngle(): Float
// Get azimuthal angle
fun getAzimuthalAngle(): Float
// Get distance to target
fun getDistance(): Float
// Listen to target changes
fun listenToKeyEvents(element: HTMLElement)
// Dispose event listeners
fun dispose()
controls.addEventListener("change") {
renderer.render(scene, camera)
}
controls.addEventListener("start") {
// Interaction started
}
controls.addEventListener("end") {
// Interaction ended
}
val controls = OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05f
controls.minDistance = 2f
controls.maxDistance = 50f
controls.maxPolarAngle = PI / 2 // Don't go below ground
// Animate
fun animate() {
controls.update() // Required for damping
renderer.render(scene, camera)
}
// Focus on object
controls.target.copy(mesh.position)
controls.update()
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 |