Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danielitoCode/Spatial/llms.txt

Use this file to discover all available pages before exploring further.

MotionSpec is an @Immutable sealed class that describes how camera animations in Spatial compute their duration and interpolation curve when CameraState.animateTo() is called. It acts as a Compose-layer adapter over the shared spatial-motion planner, keeping the UI API clean while delegating duration math to resolveCameraMotionPlan. Three variants cover the common cases: Adaptive for natural-feeling camera moves that scale with the distance to travel, Instant for zero-duration jumps, and custom(...) for fine-tuned control over velocity targets and duration bounds.

Import

import com.elitec.spatial_compose.MotionSpec

MotionSpec.Adaptive

data object MotionSpec.Adaptive : MotionSpec(
    minDurationMillis = 120L,
    maxDurationMillis = 1200L,
    targetAngularVelocityDegreesPerSecond = 180f,
    targetZoomVelocityPerSecond = 1.75f,
    easing = MotionEasing.SmoothStep,
    instant = false,
)
The recommended default. Duration is derived adaptively from the angular distance (yaw + pitch combined as a Euclidean vector) and the relative zoom distance to travel, so a small nudge takes ~120ms while a full 180° sweep takes closer to 1200ms. SmoothStep easing gives a natural ease-in / ease-out feel. Use this in the vast majority of camera-animation scenarios.

MotionSpec.Instant

data object MotionSpec.Instant : MotionSpec(
    minDurationMillis = 0L,
    maxDurationMillis = 0L,
    targetAngularVelocityDegreesPerSecond = Float.POSITIVE_INFINITY,
    targetZoomVelocityPerSecond = Float.POSITIVE_INFINITY,
    easing = MotionEasing.Linear,
    instant = true,
)
Zero-duration animation — the camera jumps to the target state in a single frame. The instant = true flag short-circuits the motion planner entirely, bypassing both duration calculation and easing. Semantically equivalent to CameraState.jumpTo(), but expressed through the animateTo coroutine API for consistency when you want to switch between animated and instant transitions based on a flag.

MotionSpec.custom(…)

companion object {
    fun custom(
        minDurationMillis: Long = 120L,
        maxDurationMillis: Long = 1200L,
        targetAngularVelocityDegreesPerSecond: Float = 180f,
        targetZoomVelocityPerSecond: Float = 1.75f,
        easing: MotionEasing = MotionEasing.SmoothStep,
        instant: Boolean = false,
    ): MotionSpec
}
Factory for a fully custom animation profile. Returns a MotionSpec.Custom instance. All parameters default to the same values used by MotionSpec.Adaptive, so you only need to override the properties you want to change.
minDurationMillis
Long
default:"120L"
The minimum animation duration in milliseconds. The adaptive planner will never produce a duration shorter than this value, even for very small angular deltas. Acts as a floor to prevent jarring sub-frame snaps.
maxDurationMillis
Long
default:"1200L"
The maximum animation duration in milliseconds. The adaptive planner will never produce a duration longer than this value, even for very large angular sweeps. Acts as a ceiling to prevent sluggish animations.
targetAngularVelocityDegreesPerSecond
Float
default:"180f"
Target angular velocity used to derive animation duration from orbit distance. The planner divides the angular distance (degrees) by this velocity to produce a raw duration in seconds. A higher value produces shorter animations; a lower value produces longer, slower sweeps.
targetZoomVelocityPerSecond
Float
default:"1.75f"
Target zoom velocity (in relative log-distance units per second) used to derive animation duration from zoom distance. Works analogously to targetAngularVelocityDegreesPerSecond but for the zoom axis.
easing
MotionEasing
default:"MotionEasing.SmoothStep"
Interpolation curve applied to the animation progress fraction. MotionEasing.SmoothStep gives a smooth ease-in / ease-out feel. MotionEasing.Linear produces a constant-speed interpolation. You can also supply a custom MotionEasing lambda.
instant
Boolean
default:"false"
When true, bypasses the motion planner entirely and applies the target state in one frame, identical to MotionSpec.Instant. Useful when you want to toggle between animated and instant transitions conditionally without changing the call site.

Shared Properties

All MotionSpec subclasses expose the following read-only properties, inherited from the sealed base class.
PropertyTypeDescription
minDurationMillisLongFloor for adaptive animation duration.
maxDurationMillisLongCeiling for adaptive animation duration.
targetAngularVelocityDegreesPerSecondFloatAngular velocity target used by the duration planner.
targetZoomVelocityPerSecondFloatZoom velocity target used by the duration planner.
easingMotionEasingInterpolation curve applied to the animation progress fraction.
instantBooleantrue if the motion planner is bypassed entirely.

Example

// Fast cinematic sweep with a tighter duration window
val fastMotion = MotionSpec.custom(
    minDurationMillis = 100L,
    maxDurationMillis = 600L,
    targetAngularVelocityDegreesPerSecond = 240f,
)
scope.launch {
    cameraState.animateTo(yaw = 90f.deg, motion = fastMotion)
}
// Instant jump — no animation
scope.launch {
    cameraState.animateTo(
        yaw = 0f.deg,
        pitch = 0f.deg,
        zoom = 1f,
        motion = MotionSpec.Instant,
    )
}
MotionSpec.Adaptive is the right default for almost all use cases. Reach for MotionSpec.custom(...) when you have specific UX requirements — for example, a product viewer where you want faster snapping between preset viewing angles.
The MotionSpec.custom(...) factory returns a MotionSpec.Custom instance (an internal subclass of MotionSpec). All public API surfaces accept the MotionSpec sealed type, so you can store and pass the result as MotionSpec without needing to reference Custom directly.
Duration is computed from the larger of the angular and zoom travel times. If you animate both yaw and zoom simultaneously, the total duration is set by whichever axis requires more time at the configured velocity — the other axis completes early and holds its final value.

Build docs developers (and LLMs) love