Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/wiki/Basics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
## Contents

1. [How library is organized](#how-library-is-organized)
2. [Entry point](#entry-point)
3. [Lifecycle](#lifecycle)

## How library is organized

AtumVR is split into three Gradle modules:

- **api** — Public-facing interfaces and structure. Also includes utility/helper classes used across the project.
- **core** — The actual VR implementation and runtime logic.
- **example** — A reference project to help you get started. You **should not** depend on this module.

## Entry point

**VRProvider** is an entry point for your application to manage VR

Use one of these abstract classes from **core** module:

- `XRProvider` - OpenXR
- `OpenVRProvider` - OpenVR (planned in future releases)

you will require to implement methods that create handlers for rendering, input and state.

For OpenXR use these abstract classes for handlers:

- `XRRenderer`
- `XRInputHandler`
- `XRState`

Feel free to override any of their logic if needed

More details and integration examples are covered in the next pages of the **Start** section.

## Lifecycle

### VRState

VR Provider has an associated VRState object:

- `XRState` - OpenXR
- `OpenVRState` - OpenVR (planned in future releases)

`VRState` represents the current VR session status (_initialized_, _active_, _focused_).
You'll use it often, especially in applications that support both VR and non-VR modes.

### Setting up lifecycle

1. **Initialize the provider**
Your `VRProvider` must be initialized before doing anything VR-related.

2. **Sync state at the start of the app loop**
When VRState indicates "_initialized_" is true, call `syncState()` at the **very beginning** of your main loop.

3. **When VRState indicates "_active_" is true, call these methods in the following order:**

1. `syncState()` - at the beginning of main loop
2. `startFrame()` - at the beginning of main loop
3. `render()` - somewhere during rendering stage in your main loop (where its convenient for your case)
4. `postRender()` - up to you, its optional

### Notes about `startFrame()`

`startFrame()` will **pause your thread** until the next VR frame is available from the VR runtime.
In other words, it effectively controls the refresh rate. If your application already has its own frame timing / limiter, you should **disable it while in VR mode** to avoid fighting the VR runtime.

### Focus handling

In addition to `active`, there is a separate flag: `focused`.

When true, it means the user is interacting with your app in VR

If false, it may mean:

- VR is not active
- The user is interacting with the VR runtime menu or something else not related to your app in VR.

It is **highly recommended** to limit rendering when `focused` is `false` (for example, don't render VR hands/controllers while unfocused).

### Destroying the VR session

It is pretty simple, just call the method `destroy()` in VRProvider **after postRender() or before startFrame()**.

You are free to initialize the VRProvider again using the same instance.
213 changes: 213 additions & 0 deletions docs/wiki/Input-Advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
## Contents

1. [Custom Action Sets](#custom-action-sets)
2. [Profile Sets](#profile-sets)
3. [Creating Custom Actions](#creating-custom-actions)
4. [Action Listeners](#action-listeners)

## Custom Action Sets

If the built-in `ProfileSetHolder` doesn't fit your needs, you can create custom action sets.

### Creating an action set

Extend `XRActionSet`:

```java
public class CustomActionSet extends XRActionSet {
private FloatButtonMultiAction triggerAction;
private Vec2MultiAction joystickAction;

public CustomActionSet(XRProvider provider) {
super(provider,
"custom_actions", // internal name
"Custom Actions", // display name
0 // priority
);
}

@Override
protected List<XRAction> loadActions(XRProvider provider) {
triggerAction = new FloatButtonMultiAction(
provider, this,
new ActionIdentifier("custom", "trigger"),
"Trigger"
);

joystickAction = new Vec2MultiAction(
provider, this,
new ActionIdentifier("custom", "joystick"),
"Joystick"
);

return List.of(triggerAction, joystickAction);
}

public FloatButtonMultiAction getTriggerAction() {
return triggerAction;
}

public Vec2MultiAction getJoystickAction() {
return joystickAction;
}
}
```

### Action types

AtumVR provides these action types:

- **FloatButtonMultiAction** - analog triggers (0.0 to 1.0 with button threshold)
- **BoolButtonMultiAction** - simple boolean buttons
- **Vec2MultiAction** - 2D inputs like joysticks
- **PoseMultiAction** - position/rotation data
- **HapticPulseAction** - output action for vibration

The "Multi" variants support both left and right hand subactions.

## Profile Sets

Different VR controllers have different button layouts. Profile sets handle these differences.

### Using ProfileSetHolder

`ProfileSetHolder` manages action sets for different controller profiles:

```java
ProfileSetHolder profileSetHolder = new ProfileSetHolder(vrProvider);

// Get the currently active profile set (based on connected controllers)
XRProfileSet activeProfile = profileSetHolder.getActiveProfileSet();

// Get specific profile set
XRProfileSet oculusProfile = profileSetHolder.getProfileSet(
XRInteractionProfile.OCULUS_TOUCH
);
```

### Supported profiles

AtumVR includes built-in support for:

- Oculus Touch controllers
- Valve Index controllers
- HTC Vive controllers
- HP Mixed Reality controllers
- And more...

The `ProfileSetHolder` automatically detects which controllers are connected and uses the appropriate profile.

### Shared actions

Some actions are common across all controllers (like hand poses). These are in the `SharedActionSet`:

```java
SharedActionSet shared = profileSetHolder.getSharedSet();
PoseMultiAction aimPose = shared.getHandPoseAim();
PoseMultiAction gripPose = shared.getHandPoseGrip();
HapticPulseAction haptic = shared.getHapticPulse();
```

## Creating Custom Actions

### Single-hand action

For actions that only apply to one hand:

```java
public class CustomSingleAction extends XRSingleAction<VRActionDataButton> {
public CustomSingleAction(XRProvider provider, XRActionSet actionSet) {
super(provider, actionSet,
new ActionIdentifier("custom", "action"),
"Custom Action",
XRInputActionType.BOOLEAN
);
}

@Override
public String getDefaultBindings(XRInteractionProfile profile) {
return switch (profile) {
case OCULUS_TOUCH -> "/user/hand/left/input/x/click";
case INDEX_CONTROLLER -> "/user/hand/left/input/a/click";
default -> null;
};
}
}
```

### Multi-hand action

For actions that apply to both hands:

```java
public class CustomMultiAction extends XRMultiAction<VRActionDataButton> {
public CustomMultiAction(XRProvider provider, XRActionSet actionSet) {
super(provider, actionSet,
new ActionIdentifier("custom", "grab"),
"Grab",
XRInputActionType.BOOLEAN
);
}

@Override
protected SubAction<VRActionDataButton> createSubAction(ControllerType type) {
return new BoolButtonSubAction(type) {
@Override
public String getDefaultBindings(XRInteractionProfile profile) {
String hand = type == ControllerType.LEFT ? "left" : "right";
return switch (profile) {
case OCULUS_TOUCH -> "/user/hand/" + hand + "/input/squeeze/value";
case INDEX_CONTROLLER -> "/user/hand/" + hand + "/input/squeeze/force";
default -> null;
};
}
};
}
}
```

## Action Listeners

You can register a listener to be notified when any action changes:

```java
inputHandler.setActionListener(actionName -> {
System.out.println("Action triggered: " + actionName);
});
```

### Custom update logic

Override the `update()` method in your input handler for custom logic:

```java
@Override
public void update() {
super.update(); // Important: call super first

var profileSet = profileSetHolder.getActiveProfileSet();
if (profileSet == null) return;

// Check for specific input combinations
if (profileSet.getTriggerValue().getHandSubaction(ControllerType.LEFT).isPressed() &&
profileSet.getTriggerValue().getHandSubaction(ControllerType.RIGHT).isPressed()) {
// Both triggers pressed
onBothTriggersPressed();
}
}
```

### Action change detection

To detect when a button state changes (not just when it's pressed):

```java
VRActionDataButton button = profileSet.getButton("a_button");
if (button.isButtonChanged()) {
if (button.isPressed()) {
// Button was just pressed
} else {
// Button was just released
}
}
```
Loading
Loading