Table of Contents

Crear una extension de entrada de imagen y datos de movimiento del dispositivo

Al crear una extension de entrada de imagen y datos de movimiento del dispositivo, los desarrolladores pueden ampliar EasyAR Sense con una implementacion de camara personalizada para admitir headsets u otros dispositivos de entrada especificos. El contenido siguiente presenta los pasos y las consideraciones para crear una extension de entrada de imagen y datos de movimiento del dispositivo.

Antes de empezar

Crear la clase de fuente de datos de fotogramas externa

Ambas son subclases de MonoBehaviour, y el nombre del archivo debe ser igual al nombre de la clase.

Por ejemplo, para crear una extension de entrada de dispositivo 6DoF:

public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}

Al crear una extension de headset, puede usar la plantilla com.easyar.sense.ext.hmdtemplate y modificarla como base. Esta plantilla se encuentra en el paquete comprimido del plugin de Unity descargado del sitio web de EasyAR.

Definicion del dispositivo

Sobrescriba IsHMD para definir si el dispositivo es un headset.

Por ejemplo, establezcalo en true en un headset.

public override bool IsHMD { get => true; }

Sobrescriba Display para definir la pantalla del dispositivo.

Por ejemplo, en un headset, la informacion de pantalla predeterminada Display.DefaultHMDDisplay define la rotacion de pantalla como 0.

protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;

Disponibilidad

Sobrescriba IsAvailable para definir si el dispositivo esta disponible.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;

Si IsAvailable no se puede determinar durante el ensamblaje de la session, puede sobrescribir la corrutina CheckAvailability() para bloquear el proceso de ensamblaje hasta que se confirme si esta disponible.

Origen de la session

Sobrescriba OriginType para definir el tipo de origen definido por el SDK del dispositivo.

Si OriginType es Custom, tambien debe sobrescribir Origin.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
    hasUXRComponents ? DeviceOriginType.None :
#endif
    DeviceOriginType.XROrigin;

Camara virtual

Si OriginType es Custom o None, debe sobrescribir Camera para proporcionar la camara virtual.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;

Camara fisica

Sobrescriba DeviceCameras con el tipo DeviceFrameSourceCamera para proporcionar informacion de la camara fisica del dispositivo. Estos datos se usaran al introducir datos de fotogramas de camara. La creacion debe estar completa cuando CameraFrameStarted sea true.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var imageDimensions = new int[2];
    RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
    size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
    started = true;
}

Sobrescriba CameraFrameStarted para proporcionar la marca que indica que ha empezado la entrada de fotogramas de camara.

Por ejemplo:

protected override bool CameraFrameStarted => started;

Inicio y detencion de la session

Sobrescriba OnSessionStart(ARSession) y realice la inicializacion especifica de AR. Asegurese de llamar primero a base.OnSessionStart.

Por ejemplo:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    StartCoroutine(InitializeCamera());
}

Este es un lugar adecuado para abrir la camara del dispositivo, como una camara RGB o VST, especialmente si esas camaras no estan disenadas para permanecer siempre abiertas. Tambien es un lugar adecuado para obtener datos de calibracion que no cambiaran durante todo el ciclo de vida. A veces puede ser necesario esperar a que el dispositivo este listo o a que se actualicen los datos antes de poder obtenerlos.

Tambien es un lugar adecuado para iniciar el bucle de entrada de datos. Tambien puede escribir este bucle en Update() u otros metodos, especialmente cuando los datos deben obtenerse en un punto concreto del orden de ejecucion de Unity. No introduzca datos antes de que la session este lista (ready).

Si es necesario, tambien puede omitir el proceso de inicio y comprobar los datos en cada actualizacion; esto depende por completo de los requisitos concretos.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

private IEnumerator InitializeCamera()
{
    yield return new WaitUntil(() => (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Detecting && (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Tracking_Paused);

    var focalLength = new float[2];
    RokidExtensionAPI.RokidOpenXR_API_GetFocalLength(focalLength);
    var principalPoint = new float[2];
    RokidExtensionAPI.RokidOpenXR_API_GetPrincipalPoint(principalPoint);
    var distortion = new float[5];
    RokidExtensionAPI.RokidOpenXR_API_GetDistortion(distortion);
    var imageDimensions = new int[2];
    RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);

    size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
    var cameraParamList = new List<float> { focalLength[0], focalLength[1], principalPoint[0], principalPoint[1] }.Concat(distortion.ToList().GetRange(1, 4)).ToList();
    cameraParameters = CameraParameters.tryCreateWithCustomIntrinsics(size.ToEasyARVector(), cameraParamList, CameraModelType.OpenCV_Fisheye, CameraDeviceType.Back, 0).Value;
    deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);

    RokidExtensionAPI.RokidOpenXR_API_OpenCameraPreview(OnCameraDataUpdate);
    started = true;
}

Sobrescriba OnSessionStop() y libere los recursos. Asegurese de llamar a base.OnSessionStop.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

protected override void OnSessionStop()
{
    base.OnSessionStop();
    RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
    started = false;
    StopAllCoroutines();
    cameraParameters?.Dispose();
    cameraParameters = null;
    deviceCamera?.Dispose();
    deviceCamera = null;
}

Introducir datos de fotogramas de camara

Despues de obtener una actualizacion de datos de fotogramas de camara, llame a HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) para introducir los datos de fotogramas de camara.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

private static void OnCameraDataUpdate(IntPtr ptr, int dataSize, ushort width, ushort height, long timestamp)
{
    if (!instance) { return; }
    if (ptr == IntPtr.Zero || dataSize == 0 || timestamp == 0) { return; }
    if (timestamp == instance.curTimestamp) { return; }

    instance.curTimestamp = timestamp;

    RokidExtensionAPI.RokidOpenXR_API_GetHistoryCameraPhysicsPose(timestamp, positionCache, rotationCache);
    var pose = new Pose
    {
        position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
        rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
    };
    // NOTE: Use real tracking status when camera exposure if possible when writing your own device frame source.
    var trackingStatus = ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus();

    var size = instance.size;
    var pixelSize = instance.size;
    var pixelFormat = PixelFormat.Gray;
    var yLen = pixelSize.x * pixelSize.y;
    var bufferBlockSize = yLen;

    var bufferO = instance.TryAcquireBuffer(bufferBlockSize);
    if (bufferO.OnNone) { return; }

    var buffer = bufferO.Value;
    buffer.tryCopyFrom(ptr, 0, 0, bufferBlockSize);

    using (buffer)
    using (var image = Image.create(buffer, pixelFormat, size.x, size.y, pixelSize.x, pixelSize.y))
    {
        instance.HandleCameraFrameData(instance.deviceCamera, timestamp * 1e-9, image, instance.cameraParameters, pose, trackingStatus);
    }
}
Precaución

No olvide ejecutar Dispose() despues del uso, o liberar Image, Buffer y otros datos relacionados mediante mecanismos como using. De lo contrario, se produciran fugas de memoria graves, y la obtencion de buffers desde el buffer pool tambien puede fallar.

Introducir datos de fotogramas de renderizado

Despues de que los datos del dispositivo esten listos, llame a HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) en cada fotograma de renderizado para introducir los datos de fotogramas de renderizado.

Por ejemplo, la implementacion en RokidFrameSource es la siguiente:

protected void LateUpdate()
{
    if (!started) { return; }
    if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Detecting) { return; }
    if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Tracking_Paused) { return; }

    InputRenderFrameMotionData();
}

private void InputRenderFrameMotionData()
{
    var timestamp = RokidExtensionAPI.RokidOpenXR_API_GetCameraPhysicsPose(positionCache, rotationCache);
    var pose = new Pose
    {
        position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
        rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
    };
    if (timestamp == 0) { return; }
    HandleRenderFrameData(timestamp * 1e-9, pose, ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus());
}

Pasos siguientes

Temas relacionados