Core Engine

aether-input

VR input abstraction with OpenXR and desktop fallback

The aether-input crate provides input handling for VR controllers, hand tracking, desktop keyboard/mouse, and haptic feedback. It abstracts over OpenXR for VR headsets and provides a desktop adapter for development without a headset.

Overview

The input system is built around a pipeline that converts raw device data into high-level interaction events. It supports:

  • OpenXR session management with reference spaces and swapchain handling.
  • Controller and hand tracking with joint-level skeletal data.
  • Action mapping that binds physical inputs to logical actions.
  • Locomotion modes including smooth movement, snap/smooth turn, and teleport.
  • Haptic feedback dispatched to specific controller channels.
  • Desktop fallback with configurable keyboard and mouse bindings.
  • Dead zone and sensitivity processing for analog inputs.

Key Types

InputRuntime

The top-level runtime that processes raw input each tick and produces a PlayerInputFrame.

use aether_input::{InputRuntime, InputRuntimeConfig};

let config = InputRuntimeConfig {
    backend: InputBackend::OpenXR,
    comfort_profile: ComfortProfile::default(),
};
let runtime = InputRuntime::new(config);

OpenXrAdapter

Manages the OpenXR session lifecycle, tracking, and haptic output.

use aether_input::{OpenXrAdapter, SessionConfig, SessionState};

let adapter = OpenXrAdapter::new(SessionConfig::default());

SessionManager

Handles OpenXR session state transitions (idle, ready, synchronized, visible, focused).

use aether_input::{SessionManager, SessionState, ReferenceSpaceType};

let mut session = SessionManager::new();
// Transitions are driven by the OpenXR runtime

TrackingPipeline

Processes raw tracking data from controllers and hand joints into a TrackingSnapshot.

use aether_input::{TrackingPipeline, TrackingSnapshot, Hand, ControllerState};

let pipeline = TrackingPipeline::new();
let snapshot: TrackingSnapshot = pipeline.sample();

ActionMap

Maps physical inputs to named logical actions. Supports rebinding at runtime.

use aether_input::{ActionMap, ActionBinding, InputSource};

let mut actions = ActionMap::new();
actions.bind(ActionBinding {
    name: "grab".into(),
    source: InputSource::TriggerLeft,
    threshold: 0.8,
});

LocomotionProfile

Configures movement style for VR comfort. Supports teleport, smooth movement, snap turn, and smooth turn.

use aether_input::{LocomotionProfile, LocomotionMode, ComfortStyle};

let profile = LocomotionProfile {
    mode: LocomotionMode::SmoothMove,
    comfort: ComfortStyle::VignetteOnMove,
    speed: 3.0,
    snap_angle_degrees: 45.0,
};

DesktopAdapter

Provides keyboard and mouse input for desktop development without a VR headset.

use aether_input::{DesktopAdapter, DesktopAdapterConfig, KeyCode};

let adapter = DesktopAdapter::new(DesktopAdapterConfig::default());

HapticDispatcher

Sends haptic pulses to VR controllers with configurable waveforms.

use aether_input::{HapticDispatcher, HapticRequest, HapticEffect, HapticChannel};

let mut haptics = HapticDispatcher::new();
haptics.submit(HapticRequest {
    channel: HapticChannel::Left,
    effect: HapticEffect::Pulse {
        amplitude: 0.7,
        duration_ms: 50,
    },
});

Usage Examples

Processing a Frame

use aether_input::{
    InputRuntime, InputRuntimeConfig, InputRuntimeInput,
    InputRuntimeOutput, PlayerInputFrame,
};

let mut runtime = InputRuntime::new(InputRuntimeConfig::default());

// Each tick, feed raw state and receive processed input
let output: InputRuntimeOutput = runtime.step(&InputRuntimeInput {
    dt: 0.011,
    tracking: raw_tracking,
});

let frame: &PlayerInputFrame = &output.player_input;

Teleport Computation

use aether_input::{compute_teleport, TeleportAnchor, TeleportResult};

let result: TeleportResult = compute_teleport(
    current_position,
    aim_direction,
    max_distance,
);