Una nueva forma de interactuar con objetos 3D por medio de un iPhone
— Luis Guillermo Ballesteros 2012/05
La siguiente es una propuesta de un estilo de interacción sobre este tipo de objetos basado en gestos sobre la pantalla táctil de un dispositivo móvil (iPhone) y las rotaciones el usuario imponga con el movimiento de su mano y muñeca al mismo. Se construyó un prototipo para probar la viabilidad del nuevo estilo de interacción, la transmisión de datos estuvo administrada por la infraestructura VRPN y la visualización de los objetos se realizó en una pantalla convencional de un computador por medio de un motor de videojuegos (Unity).
El siguiente video ilustra el nuevo estilo de interacción sobre objetos 3D implementantado en un iPhone apoyado con la plataforma VRPN y la visualización en el motor de videojuegos Unity.
Para reproducir el sistema iMove3D se requieren los siguientes dispositivos y software:
Para utilizar la funcionalidad completa del sistema iMove3D es necesario tener un dispositivo iOS (iPhone, iPod Touch o iPad) que contenga giróscopo, no basta con que posea los acelerómetros ya que la implementación del software de iMove3D usa el Core Motion Framework y en particular la librería CMDeviceMotion, esta librería utiliza algoritmos de fusion entre las lecturas de los acelerómetros y el giróscopo (Unidad Inercial) para dar la orientación del dispositivo mas precisamente.
Por lo anterior los modelos que se sugieren son:
El sistema operativo para implementar iMove3D en cualquiera de los anteriores aparatos debería ser iOS 4.0 o superior.
* Nota: La implementación en iPad es potestativa al usuario ya que desde el punto de vista de programación el funcionamiento es exactamente igual, sin embargo si puede haber diferencias con un iPhone o un iPod: Si bien la pantalla táctil de un iPad es mucho mayor e incluso es más cómoda para trasladar objetos o rotarlos, el tamaño mismo del aparato puede resultar incómodo para realizar las rotaciones. En efecto, en las primeras dos iteraciones de desarrollo se usó un iPad y los usuarios alegaron su incomodidad para realizar los gestos de rotación.
El desarrollo del sistema se hizo sobre un MacBook (Modelo Abril 2010) con las siguientes características:
La parte de VRPN y Unity se desarrolló en una máquina Virtual Windows XP SP3 soportada por VMWare Fusion (la razón: ya tenía una primera versión de VRPN unido con Unity funcionando en este entorno y realmente no tenía mucho tiempo para realizar la implementación en MAC; aunque sería deseable…), ésta tiene las siguientes características:
Se requiere el siguiente software:
Luego de tener instalado el IDE xCode se inicia el desarrollo en iOS*
*Nota 2: Empezar a desarrollar sobre iOS (con dispositivos reales) para alguien neófito en esta plataforma es una experiencia “sui generis”. Yo estaba acostumbrado a iniciar un desarrollo bajando el IDE, adicionar una que otra API y eventualmente usar un simulador; luego de un tiempo relativamente corto ya se tiene un “Hello World” (tengo entendido que con Android es mas o menos así). Esto NO funciona así para desarrollar en iOS cuando se requiere usar los sensores embebidos dentro del aparato (eg, unidad inercial - giróscopos y/o acelerómetros, brújula, sensore de proximidad, etc). Dado que el simulador incluido dentro del xCode no “simula” (perdón por la redundancia) estos sensores, se hace necesario usar un dispositivo físico (iPhone, iPod o iPad). Ahora bien, para desarrollar en un dispositivo de estos se requiere una licencia “iOS Developer”, para obtenerla se tienen dos opciones:
Sin embargo, la opción dos implica un proceso administrativo que en mi caso demoró un poco mas de dos semanas. Dos semanas antes de obtener la primera lectura de un giróscopo o acelerómetro es algo que afectó severamente mi cronograma. Entonces, para que alguien, por lo menos de esta universidad, que quiera desarrollar en esta plataforma no le ocurra lo mismo, cito mi experiencia y adjunto el documento donde se detallan los pasos para obtener la licencia “iOS developer”
proceso_appledevelopment.pdf Este documento fue escrito por Mario Franco en Marzo de 2012. Es muy completo y se destaca el hecho de que resalta en colores las acciones a desempeñar por el profesor, el alumno y el administrador del convenio Apple-Uniandes (Julian). Adicional a lo escrito por Mario yo agregaría al paso 1: “Se debe inscribir en el Apple Developer Portal con la cuenta de correo de Uniandes”. Yo ya tenía una cuenta con otro dominio y empecé el proceso con esa cuenta, esto desencadenó que el proceso que tuve que seguir se demorara mas de lo normal.
Con esto finaliza la parte de xCode
VRPN (Virtual Reality Peripheral Network) es una plataforma de software para realizar la gestión de diversos dispositivos de entrada. Esta plataforma provee una interfaz independiente de los dispositivos periféricos y transparente a la red para ser usados en ambientes de realidad virtual. Aunque VRPN fue diseñada para ambientes de realidad virtual, no está necesariamente ligada a estos. La arquitectura de esta plataforma es cliente servidor lo cual quiere decir que uno o varios dispositivos periféricos se conectan a un servidor y este procesa los eventos por medio de callbacks los cuales los clientes reciben y tratan a su conveniencia.
VRPN provee acceso a una gran variedad de dispositivos a traves de interfaces extensibles. En el caso particular de este demo se usaron estas interfaces para crear un servidor para que se conecta y procesa los eventos del dispositivo iOS. Para realizar pruebas de concepto sobre este servidor se uso el programa Print_Devices (incluido en el paquete de VRPN) para verificar que efectivamente se estaban adquiriendo las lecturas adecuadamente.
Finalmente se creó una librería dinámica de enlace (DLL) para que tomara los callback del dispositivo y los transfiriera adecuadamente a Unity.
Con el propósito de mejorar el entendimiento y la eficiencia de la plataforma VRPN a usar en esta demo se organizó el código, se retiró el código fuente que no se utilizaría -Sólo se dejó el código realmente necesario- y se adicionó el código del servidor wiring. Con esto se logró tener una versión de VRPN que sólo pesaba 1.59 MB (sin compilar). Esta versión se modificó para que compilara en Visual Studio 2010.
Contiene el código fuente de las librerías a usar por el servidor y el cliente Print_Devices. Esta a su vez contiene los siguientes subproyectos:
Contiene las directrices de compilación
Contiene las librerias para el manejo de quaterniones
Contiene las librerías básicas para la generación de los eventos de los distintos tipos de dispositivos (Análogos, Botones, Dial, Tracker, etc), funcionalidad de conexión, log y transmisión serial. Adicionalmente en este subproyecto se ubican los archivos fuente donde se implementa la conexión al dispositivo wiring y el procesamiento de las lecturas de los encoders y los botones. En este subproyecto se ubican los dos archivos que se adicionan para implementar el manejo del dispositivo iOS: vrpn_iOSDevice.h y vrpn_iOSDevice.C.
* vrpndll
Contiene el código fuente y las directivas de compilación para generar una DLL que encapsula la funcionalidad básica del servidor VRPN. Esta DLL NO es la interfaz cliente que mas adelante usaremos para la conexión con Unity (Esta la veremos mas adelante).
Contiene la implementación de loop principal sobre el cual se ejecutan los callback que reflejan los eventos que indican una actualización de las lecturas del dispositivo wiring. En este subproyecto se editó el archivo vrpn_generic_server.c
Contiene el código fuente de un programa que imprime en pantalla los mensajes de los callback vrpn. Sirve para probar que el servidor esté ejecutando correctamente.
Para la implementación del servidor se creó una librería especializada en obtener las lecturas del dispositivo iOS. Esta librería consta de dos archivos:
Los anteriores archivos se ubican en el subproyecto vrpn dentro de la carpeta Library.
Adicionalmente, se modificó la implementación del Loop principal en el archivo:
Contiene las definiciones de las variables y las estructuras de datos a usar por la implementación:
// vrpn_iOSDevice.h // This is a driver for wirings being used through the // Serial basic interface. // The driver portions of this code are based on the empiric // knowledge of colivri students. // Last update Luis Ballesteros March 2012 #ifndef VRPN_WIRINGDEVICE_H #define VRPN_WIRINGDEVICE_H //#if defined(_WIN32) #include "vrpn_Analog.h" #include "vrpn_Button.h" #include <basetsd.h> #include <windows.h> #include <stdio.h> // Additional libraries for serial reading of wiring board #include <time.h> #include <string.h> #include <conio.h> #include <stddef.h> #include <sstream> #include <iostream> #include <vector> #define BUFFER_SZ 16 using namespace std; class VRPN_API vrpn_iOSDevice: public vrpn_Analog, public vrpn_Button { public: vrpn_iOSDevice (const char * name, const char * port, vrpn_Connection * c); //~vrpn_iOSDevice (); // Called once through each main loop iteration to handle // updates. virtual void mainloop (); protected: //virtual vrpn_int32 get_report(void); // Try to read a report from the device void clear_values(void); // Clear the Analog and Button values*/ struct timeval _timestamp; // Time of the last report from the device // send report iff changed virtual void report_changes (vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY); // send report whether or not changed virtual void report (vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY); //Delimiter static const char delim; //Input buffer static char* szBuffer; //Structures for reading the serial port static DCB dcb; //Variables for getting how much data was read or wirtten if it is the case static DWORD dwRead, dwWritten; //Handle for hComm serial port static HANDLE hComm; //Overlapped struture for asynchonous serial reading static OVERLAPPED ovlr, ovlw; //Timeout used for serial port inicialization static COMMTIMEOUTS cto; //method for printing bits void printbitssimple(__int16); }; #endif
Este código implementa la inicialización del puerto, conexión, validación de la conexión y ejecucuón del loop principal donde se realiza la lectura del buffer con la información del estado del dispositivo para generar los callback:
Inicialización
Se inicializan las variables y estructuras de datos para la lecturas asincrónica del puerto serial:
// Serial port initialization // Create events for overlapped operation ovlr.Internal = 0; ovlr.InternalHigh = 0; ovlr.Offset = 0; ovlr.OffsetHigh = 0; ovlr.Pointer = NULL; ovlr.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); ovlw.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
Conexión
char portname[15] = "\\\\.\\"; strcat(portname, port); printf("[1/4] Opening port %s................... ", port); // Open the port hComm = CreateFile(portname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
Validación de la conexión
if(hComm==INVALID_HANDLE_VALUE) { //If not success full display an Error if(GetLastError()==ERROR_FILE_NOT_FOUND){ //Print Error if neccessary printf("ERROR: Handle was not attached. \nReason: %s not available.\n", port); exit(-1); } else { printf("ERROR!!!"); exit(-1); } } else { printf("Done!\n", port); printf("[2/4] Getting current control settings ... "); //Try to get the current if (!GetCommState(hComm, &dcb)) { //If impossible, show an error printf("failed to get current serial parameters!"); exit(-1); } else { printf("Done!\n", port); printf("[3/4] Setting up port parameters.......... "); // Get the state of the device and modify it dcb.DCBlength = sizeof(dcb); GetCommState(hComm, &dcb); dcb.BaudRate = CBR_9600; SetCommState(hComm, &dcb); dcb.ByteSize=8; SetCommState(hComm, &dcb); dcb.StopBits=ONESTOPBIT; SetCommState(hComm, &dcb); dcb.Parity=NOPARITY; SetCommState(hComm, &dcb); // Set the timeout parameters nonsensically cto.ReadIntervalTimeout = 1000; cto.ReadTotalTimeoutConstant = 1000; cto.ReadTotalTimeoutMultiplier = 1000; cto.WriteTotalTimeoutConstant = 1000; cto.WriteTotalTimeoutMultiplier = 1000; SetCommTimeouts(hComm, &cto); //Set the parameters and check for their proper application if(!SetCommState(hComm, &dcb)) { printf("ALERT: Could not set Serial Port parameters"); exit (-1); } else { printf("Done!\n"); //If everything went fine we're connected printf("[4/4] Finishing iOS connection ........ "); Sleep(2000); //We wait 2s as the wiring board will be reseting printf("Done!\n\n"); } } }
Loop principal y Lectura del buffer
void vrpn_iOSDevice::mainloop() { // Call the generic server mainloop, since we are a server struct timeval reporttime; vrpn_gettimeofday(&reporttime, NULL); server_mainloop(); //iOS reading code ReadFile(hComm, szBuffer, BUFFER_SZ, &dwRead, &ovlr); // Wait for the receive to complete and display the response if (GetOverlappedResult(hComm, &ovlr, &dwRead, TRUE)) { int i=0; //We will iterate over the size of the buffer minus the number of bytes we prefetch (in this case are 8) while(i<BUFFER_SZ-8) { //Does the index point the first delimiter? if( szBuffer[i]=='~' && szBuffer[i+1]=='U' && szBuffer[i+2]=='~') { //printf("OK i=%d [%d]\n",i,szBuffer[i]); //Since the index points the first '\n', it has to move forward 3 bytes i=i+3; //By doing the logic AND between the buttons byte and the corresponding value //(eg. button 1 = 1000 0000, button 2 = 0100 0000, etc...), we extract the stautus //of each one of them. vrpn_Button::buttons[0] = (szBuffer[i] & 0x80)==0x80?true:false; vrpn_Button::buttons[1] = (szBuffer[i] & 0x40)==0x40?true:false; vrpn_Button::buttons[2] = (szBuffer[i] & 0x20)==0x20?true:false; vrpn_Button::buttons[3] = (szBuffer[i] & 0x10)==0x10?true:false; vrpn_Button::buttons[4] = (szBuffer[i] & 0x08)==0x08?true:false; vrpn_Button::buttons[5] = (szBuffer[i] & 0x04)==0x04?true:false; vrpn_Button::buttons[6] = (szBuffer[i] & 0x02)==0x02?true:false; vrpn_Button::buttons[7] = (szBuffer[i] & 0x01)==0x01?true:false; //Move the pointer to the first byte of the first encoder i ++; //For each one of the encoders we will get the values from the fetch data //Get the Yaw value vrpn_Analog::channel[0] = ((__int16)(szBuffer[i]<<8) | (__int16)(szBuffer[i+1] & 0x00FF)); //Move the pointer to the first byte of the second encoder i = i + 2; //Get the Pitch value vrpn_Analog::channel[1] = ((__int16)(szBuffer[i]<<8) | (__int16)(szBuffer[i+1] & 0x00FF)); //Move the pointer to the first delimiter i=i+2; } else { //printf("NO i=%d [%d]\n",i,szBuffer[i]); //i is not pointing at the first of the three delimiters. keep looking... i++; } } } // Send any changes out over the connection. _timestamp = reporttime; report_changes(); }
Genera los callback a partir de la lecturas de los análogos (encoder) y el boton pulsador.
int vrpn_Generic_Server_Object::setup_iOSDevice (char * & pch, char * line, FILE * config_file) { char name[LINESIZE]; char port[LINESIZE]; // Get the arguments next(); if (sscanf(pch, "%511s%511s", name, port) != 2) { fprintf(stderr, "Bad vrpn_iOS device line: %s\n", line); return -1; } // Make sure there's room for a new analog if (num_analogs >= VRPN_GSO_MAX_ANALOG) { fprintf(stderr,"Too many Analogs in config file"); return -1; } if ( (analogs[num_analogs] = new vrpn_iOSDevice(name, port, connection)) == NULL ) { fprintf(stderr, "Can't create new vrpn_iOSDevice\n"); return -1; } else { num_analogs++; } return 0; }
Estos son los pasos para compilar y generar los ejecutables del servidor y el print devices:
Se desarrolló un cliente de VRPN implementado en una librería Dinamica de Enlace - Dinamic Link Library (DLL)-. Este cliente tiene como propósito servir como interfaz en entre el servidor y el motor de videojuegos (Unity). Este no realiza procesamiento de información sólo pasa información de un lado para otro.
Este se implemnetó en C++ por medio de Visual Studio y consta principalmente de los siguientes archivos:
Este es el código principal de la DLL donde se realiza la captura de los callback provenientes del servidor VRPN.
#include "stdafx.h" #include "stdio.h" #include "iOSClientDevice.h" #include <signal.h> #include <iostream> iOSClientDevice *iOSClientDevice::smIntance = NULL; iOSClientDevice * iOSClientDevice::GetInstance() { if( smIntance == NULL ) smIntance = new iOSClientDevice; return smIntance; } iOSClientDevice::iOSClientDevice(void) { mAnalog = NULL; mButton = NULL; button0 = false; button1 = false; button2 = false; button3 = false; button4 = false; button5 = false; button6 = false; button7 = false; } iOSClientDevice::~iOSClientDevice(void) { Stop(); } void iOSClientDevice::Start() { stopDll = false; // Create vrpn connections mAnalog = new vrpn_Analog_Remote(deviceConnection.c_str()); if( mAnalog != NULL ) { printf("Good! VRPN Analog connection Created\n"); mAnalog->register_change_handler(this, handle_analog); } else { printf("Error: cannot connect to analog device\n"); } mButton = new vrpn_Button_Remote(deviceConnection.c_str()); if( mButton != NULL ) { printf("Good! VRPN Button connection Created\n"); mButton->register_change_handler(this, handle_button); } else { printf("Error: cannot connect to button device\n"); } deviceThreadHandle = CreateThread(NULL, 0, &iOSClientDevice::ThreadDevice, (LPVOID) this, 0, NULL); } void iOSClientDevice::Stop() { stopDll = true; if( deviceThreadHandle != NULL ) WaitForSingleObject(deviceThreadHandle, INFINITE); deviceThreadHandle = NULL; // Stop vrpn connections if( mAnalog != NULL ){ mAnalog->unregister_change_handler( this, handle_analog ); delete mAnalog; mAnalog = NULL; } if( mButton != NULL ){ mButton->unregister_change_handler( this, handle_button ); delete mButton; mButton = NULL; } } // -------------------------------------------------------------------------------- // Device methods DWORD WINAPI iOSClientDevice::ThreadDevice( LPVOID pThis ) { //printf("We are into the ThreadDevice...\n"); iOSClientDevice * device = (iOSClientDevice*)pThis; device->RunDevice(); return 0; } void iOSClientDevice::SetupDevice( const char* deviceName, int yawChannel, int pitchChannel, int rollChannel, int deviceButton_0, int deviceButton_1, int deviceButton_2, int deviceButton_3, int deviceButton_4, int deviceButton_5, int deviceButton_6, int deviceButton_7) { this->deviceConnection = ""; this->deviceConnection.append(deviceName); this->yawChannel = yawChannel; this->pitchChannel = pitchChannel; this->rollChannel = rollChannel; this->deviceButton_0 = button0; this->deviceButton_1 = button1; this->deviceButton_2 = button2; this->deviceButton_3 = button3; this->deviceButton_4 = button4; this->deviceButton_5 = button5; this->deviceButton_6 = button6; this->deviceButton_7 = button7; } void iOSClientDevice::RunDevice() { while( !stopDll ) { mButton->mainloop(); mAnalog->mainloop(); Sleep((DWORD)1); } ExitThread(0); } void VRPN_CALLBACK iOSClientDevice::handle_button( void* userData, const vrpn_BUTTONCB b ) { iOSClientDevice * device = (iOSClientDevice *)userData; device->ProcessButton( b ); } void iOSClientDevice::ProcessButton( const vrpn_BUTTONCB b ) { if ( b.button == deviceButton_0 ) button0 = b.state == 1 ? true : false; if ( b.button == deviceButton_1 ) button1 = b.state == 1 ? true : false; if ( b.button == deviceButton_2 ) button2 = b.state == 1 ? true : false; if ( b.button == deviceButton_3 ) button3 = b.state == 1 ? true : false; if ( b.button == deviceButton_4 ) button4 = b.state == 1 ? true : false; if ( b.button == deviceButton_5 ) button5 = b.state == 1 ? true : false; if ( b.button == deviceButton_6 ) button6 = b.state == 1 ? true : false; if ( b.button == deviceButton_7 ) button7 = b.state == 1 ? true : false; } void VRPN_CALLBACK iOSClientDevice::handle_analog( void* userData, const vrpn_ANALOGCB a ) { iOSClientDevice * device = (iOSClientDevice *)userData; device->ProcessAnalog( a ); } void iOSClientDevice::ProcessAnalog( const vrpn_ANALOGCB a ) { if( a.num_channel < pitchChannel || a.num_channel < yawChannel || a.num_channel < rollChannel ) return ; yaw = (float)a.channel[yawChannel]; pitch = (float)a.channel[pitchChannel]; roll = (float)a.channel[rollChannel]; }
Estos son los pasos para compilar y generar la DLL y un programa de prueba de esta:
Unity es un motor de videojuegos sobre el cual se pueden crear y editar escenarios y objetos 3D.
Sobre este motor se creó un cubo cuyas caras tienen varios colores de modo que cuando éste gire se pueda notar mejor la orientación.
La DLL generada en la sección Cliente DLL se debe dejar en la carpeta: \4. Unity\Assets\plugins.
De otra lado la el archivo vrpn.dll el cual se ubica en \2. VRPN Server\pc_win32\DLL\Release se debe copiar y pegar en la ruta: C:\Program Files\Unity\Editor
Para que el cubo se mueva se creó un script en C# (Editado en Mono - editor de código fuente por defecto de Unity) que lee los métodos publicados por una interfaz local y realiza las transformaciones de rotación respectivas. Este script se llama cubeBehavior.cs y se encuenyta en la ruta: \4. Unity\Assets\scripts dentro del zip del codigo fuente adjunto.
using UnityEngine; using System.Collections; public class CubeBehavior : MonoBehaviour, IiOSDeviceListener { // ------------------------------------------------------------------------------------------------------ // Public attributes // ------------------------------------------------------------------------------------------------------ /// <summary> /// Factor used to smooth rotation data /// </summary> public float PlayerSmoothRotationFactor = 0.1f; // ------------------------------------------------------------------------------------------------------ // Private attributes // ------------------------------------------------------------------------------------------------------ /// <summary> /// Used to read or not form the dll /// </summary> private bool bCanReadDll; /// <summary> /// Device values /// </summary> private DeviceValues mDeviceValues; /// <summary> /// Get or set actor's parent transform /// </summary> public virtual Transform Parent { get { return mTransform.parent; } set { mTransform.parent = value; } } /// <summary> /// Speed up access for transform data /// </summary> protected Transform mTransform; /// <summary> /// Get or set object's transform /// </summary> public virtual Transform Transfrom { get { return mTransform; } set { mTransform = value; } } /// <summary> /// Get or set actor's rotation /// </summary> public virtual Quaternion Rotation { get { return mTransform.rotation; } set { mTransform.rotation = value; } } // Use this for initialization void Start () { mTransform = gameObject.transform; mDeviceValues = new DeviceValues(); bCanReadDll = false; Invoke("StartiOS", 1); } private void StartiOS() { Debug.Log("Starting iOS Device...."); iOSDevice.iOS_SetupDevice("wiring0@localhost", 0, 1, 2, 0, 1, 2, 3, 4, 5, 6, 7); Debug.Log("Starting iOS device...."); iOSDevice.iOS_StartDevice(); Debug.Log("Reading iOS device...."); StartReadFromDevice(); } // Update is called once per frame void Update () { if (bCanReadDll) { iOSDevice.iOS_ReadDevice(out mDeviceValues); mTransform.localRotation = Quaternion.Lerp(mTransform.localRotation, Quaternion.Euler(mDeviceValues.pitch * 0.36f, mDeviceValues.yaw * 0.36f, mDeviceValues.roll * 0.36f), PlayerSmoothRotationFactor); if (mDeviceValues.isButtonPress0) { print("Button 0 pressed"); mTransform.localPosition = new Vector3(0,0.5f,0); } else { print("Button 0 released"); mTransform.localPosition = new Vector3(0, 0, 0); } } } /// <summary> /// Allow the object to read from the dll /// </summary> public void StartReadFromDevice() { bCanReadDll = true; } /// <summary> /// Stop the object to read from the dll /// </summary> public void StopReadFromDevice() { bCanReadDll = false; } }
Ahora, para leer los eventos del cliente DLL y se puedan interpretar en Unity existe este script:
using UnityEngine; using System.Collections; /// <summary> /// Interface implemented by all object who need /// to read data from iOSDeviceDll /// </summary> public interface IiOSDeviceListener { /// <summary> /// Allow the object to read from the dll /// </summary> void StartReadFromDevice(); /// <summary> /// Stop the object to read from the dll /// </summary> void StopReadFromDevice(); }
Para ejecutar el escenario y empezar a recibir los eventos de movimiento sobre el cubo se hace clic sobre el triángulo en la parte superior de la interfaz de Unity.
El servidor VRPN se debe ejecutar previamente. Si todo marcho OK y luego de ejecutar el escenario de Unity, se debe visualizar el cubo rotando en los tres ejes deacuerdo al movimiento que se le imprima a los encoders. Si el botón pulsador se presiona, el cubo debe saltar:
El siguiente es un archivo comprimido con el código fuente de código desarrollado o modificado para esta demo.
Está dividido en cuatro carpetas: