reckfellENGINE

Phone VR with MobileVRInterface

Executed lifecycle helper, lens parameters, units and phone-stereo limitations.

On this page

Phone VR with MobileVRInterface

MobileVRInterface is a built-in stereo-output interface for a phone display in a holder. Its XRServer name is Native mobile. This path is separate from OpenXR and ARCore. It models head rotation from available sensor input, while position uses a fixed EyeHeight: it does not track walking around a room.

For a device using OpenXR, follow OpenXR startup. This section covers the simple phone-stereo path and lifecycle diagnosis.

What successful startup means

Initialize creates a head tracker, makes the interface primary and returns true. Current implementation does this without a physical phone too. Reporting “headset connected” after Initialize alone is therefore incorrect. Two views, a head pose and fixed height do not establish sensor availability or tracking accuracy either.

Output needs a Viewport with UseXR=true and an XR rig. Initializing the interface does not enable this flag automatically. Do not run two components managing primary interface and the head tracker concurrently.

A single-owner example

The helper uses the registered interface, refuses to overwrite an existing head/primary, retains UseXR and releases only its own initialization:

C#
using System;
using Reckfell;

public sealed class MobileVrPreview
{
    private MobileVRInterface? current;
    private XRTracker? head;
    private Viewport? viewport;
    private bool previousUseXr;

    // One owner, main thread. Call TryStop before leaving the preview.
    public bool TryStart(Viewport target)
    {
        ArgumentNullException.ThrowIfNull(target);
        if (current != null) return false;
        var candidate = XRServer.FindInterface("Native mobile") as MobileVRInterface;
        if (candidate == null || candidate.IsInitialized()
            || XRServer.PrimaryInterface != null || XRServer.GetTracker("head") != null)
            return false;
        bool oldUseXr = target.UseXR;
        if (!candidate.Initialize()) return false;
        var tracker = XRServer.GetTracker("head");
        if (tracker == null)
        {
            candidate.Uninitialize();
            return false;
        }
        current = candidate;
        head = tracker;
        viewport = target;
        previousUseXr = oldUseXr;
        target.UseXR = true;
        return true;
    }

    public bool TryStop()
    {
        var active = current;
        if (active == null) return true;
        // Do not unregister another provider's head tracker after a conflict.
        if (XRServer.PrimaryInterface != active || XRServer.GetTracker("head") != head)
            return false;
        if (viewport != null && ReckfellObject.IsInstanceValid(viewport))
            viewport.UseXR = previousUseXr;
        active.Uninitialize();
        current = null;
        head = null;
        viewport = null;
        return true;
    }
}

Retain one MobileVrPreview instance in the controlling Entity. In _Ready, pass its live GetViewport() to TryStart and check the bool. Call TryStop when leaving preview, before deleting the owner. Use ReckfellApi.Print for messages, not the old GD.Print.

TryStop=false signals an ownership conflict: another component changed primary or head. The helper does not unregister another provider's tracker in that case. Resolve the conflict in controller logic; do not start a second XR provider over the first one. Repeating TryStop after a successful stop is safe.

The interface and tracker are borrowed from XRServer. Do not Dispose/Free them. The helper does not free the Viewport either. It changes no lens parameters, global WorldScale or reference frame: configure those separately before startup.

Parameters and units

C# parameterDefaultMeaning
EyeHeight1.85Assumed height in meters before WorldScale, not measured phone position.
Iod6.0Interocular distance in centimeters. At WorldScale=1, eye offsets are −0.03 and +0.03.
DisplayWidth14.5Physical display width in centimeters, not pixel resolution.
DisplayToLens4.0Display-to-lens distance in centimeters.
Oversample1.5Render-target size multiplier that also affects frustum calculation; not MSAA.
K1, K20.215, 0.215Dimensionless lens-distortion coefficients, not universal holder calibration.
OffsetRect(0, 0, 1, 1)Output region relative to the full screen rectangle.
VrsMinRadius20Full-density central region as a percentage of size when using VRS. Setter range: 1–100.
VrsStrength1VRS-map strength, range 0.1–10. Guarantees no GPU saving.

Measure actual display and holder parameters. Do not pass meters to DisplayToLens/Iod or centimeters to EyeHeight. The optical model needs meaningful positive dimensions. Many setters store numbers without checking physical correctness.

This path implements no automatic holder QR-profile loading: Initialize does not discover distortion coefficients or dimensions. Higher Oversample does not fix incorrect lens geometry. Code computes the per-eye target as half the window width × Oversample and window height × Oversample; larger values increase rendering volume.

Scale and eye caches

Set WorldScale before startup. Current GetCameraOffsets caches eye offsets. Execution found that changing WorldScale from 1 to 2 left previously computed ±0.03 unchanged. A controlled TryStop/TryStart rebuilt them as ±0.06. This is a reproduced current-engine limitation, not a fixed defect.

Changing Iod to another value clears offsets and projections; DisplayWidth, DisplayToLens and Oversample clear projections. Assigning the same value again is not a universal cache reset. Array getters can return a shared cache: do not edit its elements to change configuration.

Sensors and output

For Android, source documents input_devices/sensors/enable_accelerometer, enable_gravity, enable_gyroscope and enable_magnetometer. These enable input sources, not evidence that hardware exists. Check actual values and behavior on the target device. GetTrackingStatus and sensor-fusion quality were not accepted by this run.

The main output path splits the screen into left/right halves with lens distortion. A SubViewport without a screen rectangle produces no on-screen output through this interface and emits a warning: a separate render target does not automatically become a holder image. VRS needs an appropriate RD renderer and Viewport.VrsMode=Xr; VRS properties alone do not establish actual map use.

The C# example was tested as lifecycle code on Windows/SDK 1.0.0. This does not establish C# export to a phone. Confirm platform, language-runtime and module support in the selected build and on-device testing.

Diagnosis

SymptomCheck
Native mobile is missingThe build's mobile_vr module; disable_xr disables it.
TryStart returns falseAn initialized interface, occupied primary/head, repeated start or missing module.
Initialize succeeds but the head is staticSensors and their settings; initialization success checks no phone.
Wrong heightEyeHeight in meters and WorldScale; this is a fixed-height model.
Stereo does not alignIod/DisplayWidth/DisplayToLens in centimeters, holder parameters and distortion.
Eye offsets ignore scale changesStop your own preview and start after configuring scale; do not overwrite another provider's head.
TryStop returns falseA competing primary/head owner; the helper deliberately retains others' data.

Verification scope

Installed Reckfell 1.0.0, Windows headless with XR off: 22 observations cover parameters, helper startup/shutdown, ownership conflicts, UseXR restoration, fixed height and two-view calculations. Sensors and imagery received no hardware acceptance. Original live-WorldScale cache failure remains a separate unsuccessful acceptance result in the report.

Phone hardware/C# export, GetTrackingStatus initialization, sensor accuracy, real lenses, stereo pixels, VRS output, comfort and FPS were not tested. Engine source, installed Runtime and SDK were unchanged.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.