浏览代码

Add NOLO driver.

Based on the work by Yann Vernier.
TheOnlyJoey 8 年之前
父节点
当前提交
44b472db93
共有 9 个文件被更改,包括 599 次插入9 次删除
  1. 13 0
      CMakeLists.txt
  2. 13 0
      configure.ac
  3. 11 0
      src/Makefile.am
  4. 11 7
      src/drv_deepoon/deepoon.c
  5. 327 0
      src/drv_nolo/nolo.c
  6. 47 0
      src/drv_nolo/nolo.h
  7. 169 0
      src/drv_nolo/packet.c
  8. 6 2
      src/openhmd.c
  9. 2 0
      src/openhmdi.h

+ 13 - 0
CMakeLists.txt

@@ -31,6 +31,7 @@ OPTION(OPENHMD_DRIVER_OCULUS_RIFT "Oculus Rift DK1 and DK2" ON)
 OPTION(OPENHMD_DRIVER_DEEPOON "Deepoon E2" ON)
 OPTION(OPENHMD_DRIVER_PSVR "Sony PSVR" ON)
 OPTION(OPENHMD_DRIVER_HTC_VIVE "HTC Vive" ON)
+OPTION(OPENHMD_DRIVER_NOLO "NOLO VR CV1" ON)
 OPTION(OPENHMD_DRIVER_EXTERNAL "External sensor driver" ON)
 OPTION(OPENHMD_DRIVER_ANDROID "General Android driver" OFF)
 
@@ -87,6 +88,18 @@ if(OPENHMD_DRIVER_HTC_VIVE)
 	set(LIBS ${LIBS} ${HIDAPI_LIBRARIES})
 endif(OPENHMD_DRIVER_HTC_VIVE)
 
+if(OPENHMD_DRIVER_NOLO)
+	set(openhmd_source_files ${openhmd_source_files}
+	${CMAKE_CURRENT_LIST_DIR}/src/drv_nolo/nolo.c
+	${CMAKE_CURRENT_LIST_DIR}/src/drv_nolo/packet.c
+	)
+	add_definitions(-DDRIVER_NOLO)
+
+	find_package(HIDAPI REQUIRED)
+	include_directories(${HIDAPI_INCLUDE_DIRS})
+	set(LIBS ${LIBS} ${HIDAPI_LIBRARIES})
+endif(OPENHMD_DRIVER_NOLO)
+
 if (OPENHMD_DRIVER_EXTERNAL)
 	set(openhmd_source_files ${openhmd_source_files}
 	${CMAKE_CURRENT_LIST_DIR}/src/drv_external/external.c

+ 13 - 0
configure.ac

@@ -71,6 +71,15 @@ AC_ARG_ENABLE([driver-psvr],
 
 AM_CONDITIONAL([BUILD_DRIVER_PSVR], [test "x$driver_psvr_enabled" != "xno"])
 
+# NOLO
+AC_ARG_ENABLE([driver-nolo],
+        [AS_HELP_STRING([--disable-driver-nolo],
+                [disable building of NOLO VR driver [default=yes]])],
+        [driver_nolo_enabled=$enableval],
+        [driver_nolo_enabled='yes'])
+
+AM_CONDITIONAL([BUILD_DRIVER_NOLO], [test "x$driver_nolo_enabled" != "xno"])
+
 # External Driver
 AC_ARG_ENABLE([driver-external],
         [AS_HELP_STRING([--disable-driver-external],
@@ -105,6 +114,10 @@ AS_IF([test "x$driver_deepoon_enabled" != "xno"],
 AS_IF([test "x$driver_psvr_enabled" != "xno"],
 	[PKG_CHECK_MODULES([hidapi], [$hidapi] >= 0.0.5)])
 
+# Libs required by NOLO VR Driver
+AS_IF([test "x$driver_nolo_enabled" != "xno"],
+	[PKG_CHECK_MODULES([hidapi], [$hidapi] >= 0.0.5)])
+
 # Do we build OpenGL example?
 AC_ARG_ENABLE([openglexample],
         [AS_HELP_STRING([--enable-openglexample],

+ 11 - 0
src/Makefile.am

@@ -61,6 +61,17 @@ libopenhmd_la_LDFLAGS += $(hidapi_LIBS)
 
 endif
 
+if BUILD_DRIVER_NOLO
+
+libopenhmd_la_SOURCES += \
+	drv_nolo/nolo.c \
+	drv_nolo/packet.c
+
+libopenhmd_la_CPPFLAGS += $(hidapi_CFLAGS) -DDRIVER_NOLO
+libopenhmd_la_LDFLAGS += $(hidapi_LIBS)
+
+endif
+
 if BUILD_DRIVER_EXTERNAL
 
 libopenhmd_la_SOURCES += \

+ 11 - 7
src/drv_deepoon/deepoon.c

@@ -290,18 +290,22 @@ static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
 	struct hid_device_info* cur_dev = devs;
 
 	while (cur_dev) {
-		ohmd_device_desc* desc = &list->devices[list->num_devices++];
+		// This is needed because DeePoon share USB IDs with the Nolo.
+		if (wcscmp(cur_dev->manufacturer_string, L"DeePoon VR, Inc.")==0 &&
+			wcscmp(cur_dev->product_string, L"DeePoon Tracker Device")==0) {
 
-		strcpy(desc->driver, "Deepoon Driver");
-		strcpy(desc->vendor, "Deepoon");
-		strcpy(desc->product, "Deepoon E2");
+			ohmd_device_desc* desc = &list->devices[list->num_devices++];
 
-		desc->revision = 0;
+			strcpy(desc->driver, "Deepoon Driver");
+			strcpy(desc->vendor, "Deepoon");
+			strcpy(desc->product, "Deepoon E2");
 
-		strcpy(desc->path, cur_dev->path);
+			desc->revision = 0;
 
-		desc->driver_ptr = driver;
+			strcpy(desc->path, cur_dev->path);
 
+			desc->driver_ptr = driver;
+		}
 		cur_dev = cur_dev->next;
 	}
 

+ 327 - 0
src/drv_nolo/nolo.c

@@ -0,0 +1,327 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2013 Fredrik Hultin.
+ * Copyright (C) 2013 Jakob Bornecrantz.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ *
+ * Original implementation by: Yann Vernier.
+ */
+
+/* NOLO VR- HID/USB Driver Implementation */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "nolo.h"
+
+#define NOLO_ID					0x0483 //ST microcontroller
+#define NOLO_HMD				0x5750
+
+static const int controllerLength = 3 + (3+4)*2 + 2 + 2 + 1;
+static devices_t* nolo_devices;
+
+static drv_priv* drv_priv_get(ohmd_device* device)
+{
+	return (drv_priv*)device;
+}
+
+static int send_feature_report(drv_priv* priv, const unsigned char *data, size_t length)
+{
+	return hid_send_feature_report(priv->handle, data, length);
+}
+
+static void update_device(ohmd_device* device)
+{
+	drv_priv* priv = drv_priv_get(device);
+	unsigned char buffer[FEATURE_BUFFER_SIZE];
+
+	// Only update when physical device
+	if (priv->id != 0)
+		return;
+
+	devices_t* current = nolo_devices;
+	drv_priv* controller0 = NULL;
+	drv_priv* controller1 = NULL;
+
+	//Check if controllers exist
+	while (current != NULL) {
+		if (current->drv->hmd_tracker == priv)
+		{
+			if (current->drv->controller0)
+				controller0 = current->drv->controller0;
+			if (current->drv->controller1)
+				controller1 = current->drv->controller1;
+			break;
+		}
+		current = current->next;
+	}
+
+	// Read all the messages from the device.
+	while(true){
+		int size = hid_read(priv->handle, buffer, FEATURE_BUFFER_SIZE);
+		if(size < 0){
+			LOGE("error reading from device");
+			return;
+		} else if(size == 0) {
+			return; // No more messages, return.
+		}
+
+		nolo_decrypt_data(buffer);
+
+		// currently the only message type the hardware supports
+		switch (buffer[0]) {
+			case 0xa5:  // Controllers packet
+			{
+				if (controller0)
+					nolo_decode_controller(controller0, buffer+1);
+				if (controller1)
+					nolo_decode_controller(controller1, buffer+64-controllerLength);
+			break;
+			}
+			case 0xa6: // HMD packet
+				nolo_decode_hmd_marker(priv, buffer+0x15);
+				nolo_decode_base_station(priv, buffer+0x36);
+			break;
+			default:
+				LOGE("unknown message type: %u", buffer[0]);
+		}
+	}
+
+
+	return;
+}
+
+static int getf(ohmd_device* device, ohmd_float_value type, float* out)
+{
+	drv_priv* priv = drv_priv_get(device);
+
+	switch(type){
+
+	case OHMD_ROTATION_QUAT: {
+			*(quatf*)out = priv->base.rotation;
+			break;
+		}
+
+	case OHMD_POSITION_VECTOR:
+		if(priv->id == 0) {
+			// HMD
+			*(vec3f*)out = priv->base.position;
+		}
+		else if(priv->id == 1) {
+			// Controller 0
+			*(vec3f*)out = priv->base.position;
+		}
+		else if(priv->id == 2) {
+			// Controller 1
+			*(vec3f*)out = priv->base.position;
+		}
+		break;
+
+	default:
+		ohmd_set_error(priv->base.ctx, "invalid type given to getf (%ud)", type);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+static void close_device(ohmd_device* device)
+{
+	LOGD("closing device");
+	drv_priv* priv = drv_priv_get(device);
+	hid_close(priv->handle);
+	free(priv);
+}
+
+static char* _hid_to_unix_path(char* path)
+{
+	const int len = 16;
+	char bus [16];
+	char dev [16];
+	char *result = malloc( sizeof(char) * ( 20 + 1 ) );
+
+	sprintf (bus, "%.*s\n", len, path);
+	sprintf (dev, "%.*s\n", len, path + 5);
+
+	sprintf (result, "/dev/bus/usb/%03d/%03d",
+		(int)strtol(bus, NULL, 16),
+		(int)strtol(dev, NULL, 16));
+	return result;
+}
+
+
+void push_device(devices_t * head, drv_nolo* val) {
+	devices_t* current = head;
+
+	if (!nolo_devices)
+	{
+		nolo_devices = calloc(1, sizeof(devices_t));
+		nolo_devices->drv = val;
+		nolo_devices->next = NULL;
+		return;
+	}
+
+	while (current->next != NULL) {
+		current = current->next;
+	}
+
+	/* now we can add a new variable */
+	current->next = calloc(1, sizeof(devices_t));
+	current->next->drv = val;
+	current->next->next = NULL;
+}
+
+static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
+{
+	drv_priv* priv = ohmd_alloc(driver->ctx, sizeof(drv_priv));
+	if(!priv)
+		goto cleanup;
+
+	priv->id = desc->id;
+	priv->base.ctx = driver->ctx;
+
+	// Open the HID device when physical device
+	if (priv->id == 0)
+	{
+		priv->handle = hid_open_path(desc->path);
+
+		if(!priv->handle) {
+			char* path = _hid_to_unix_path(desc->path);
+			ohmd_set_error(driver->ctx, "Could not open %s. "
+										"Check your rights.", path);
+			free(path);
+			goto cleanup;
+		}
+
+		if(hid_set_nonblocking(priv->handle, 1) == -1){
+			ohmd_set_error(driver->ctx, "failed to set non-blocking on device");
+			goto cleanup;
+		}
+
+	}
+
+	devices_t* current = nolo_devices;
+	drv_nolo* mNOLO = NULL;
+
+	//Check if the opened device is part of a group
+	while (current != NULL) {
+		if (strcmp(current->drv->path, desc->path)==0)
+			mNOLO = current->drv;
+		current = current->next;
+	}
+
+	if (!mNOLO)
+	{
+		//Create new group
+		mNOLO = calloc(1, sizeof(drv_nolo));
+		mNOLO->hmd_tracker = NULL;
+		mNOLO->controller0 = NULL;
+		mNOLO->controller1 = NULL;
+		strcpy(mNOLO->path, desc->path);
+		push_device(nolo_devices, mNOLO);
+	}
+
+	if (priv->id == 0) {
+		mNOLO->hmd_tracker = priv;
+	}
+	else if (priv->id == 1) {
+		mNOLO->controller0 = priv;
+		priv->base.properties.digital_button_count = 6;
+	}
+	else if (priv->id == 2) {
+		mNOLO->controller1 = priv;
+		priv->base.properties.digital_button_count = 6;
+	}
+
+	// Set default device properties
+	ohmd_set_default_device_properties(&priv->base.properties);
+
+	// set up device callbacks
+	priv->base.update = update_device;
+	priv->base.close = close_device;
+	priv->base.getf = getf;
+
+	return &priv->base;
+
+cleanup:
+	if(priv)
+		free(priv);
+
+	return NULL;
+}
+
+static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
+{
+	struct hid_device_info* devs = hid_enumerate(NOLO_ID, NOLO_HMD);
+	struct hid_device_info* cur_dev = devs;
+
+	int id = 0;
+	while (cur_dev) {
+		if (wcscmp(cur_dev->manufacturer_string, L"LYRobotix")==0 &&
+			wcscmp(cur_dev->product_string, L"NOLO")==0) {
+			ohmd_device_desc* desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1");
+
+			desc->revision = 0;
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+
+			//Controller 0
+			desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1: Controller 0");
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+
+			// Controller 1
+			desc = &list->devices[list->num_devices++];
+
+			strcpy(desc->driver, "OpenHMD NOLO VR CV1 driver");
+			strcpy(desc->vendor, "LYRobotix");
+			strcpy(desc->product, "NOLO CV1: Controller 1");
+
+			strcpy(desc->path, cur_dev->path);
+
+			desc->driver_ptr = driver;
+			desc->id = id++;
+		}
+		cur_dev = cur_dev->next;
+	}
+	hid_free_enumeration(devs);
+}
+
+static void destroy_driver(ohmd_driver* drv)
+{
+	LOGD("shutting down NOLO CV1 driver");
+	hid_exit();
+	free(drv);
+}
+
+ohmd_driver* ohmd_create_nolo_drv(ohmd_context* ctx)
+{
+	ohmd_driver* drv = ohmd_alloc(ctx, sizeof(ohmd_driver));
+	if(drv == NULL)
+		return NULL;
+
+	drv->get_device_list = get_device_list;
+	drv->open_device = open_device;
+	drv->destroy = destroy_driver;
+	drv->ctx = ctx;
+
+	return drv;
+}

+ 47 - 0
src/drv_nolo/nolo.h

@@ -0,0 +1,47 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ */
+
+/* NOLO VR - Internal Interface */
+
+#ifndef NOLODRIVER_H
+#define NOLODRIVER_H
+
+#include "../openhmdi.h"
+#include <hidapi.h>
+
+#define FEATURE_BUFFER_SIZE 64
+
+typedef struct {
+	ohmd_device base;
+
+	hid_device* handle;
+	int id;
+	uint8_t button_state;
+} drv_priv;
+
+typedef struct{
+	char path[OHMD_STR_SIZE];
+	drv_priv* hmd_tracker;
+	drv_priv* controller0;
+	drv_priv* controller1;
+} drv_nolo;
+
+typedef struct devices{
+	drv_nolo* drv;
+	struct devices * next;
+} devices_t;
+
+void btea_decrypt(uint32_t *v, int n, int base_rounds, uint32_t const key[4]);
+void nolo_decrypt_data(unsigned char* buf);
+
+void nolo_decode_base_station(drv_priv* priv, unsigned char* data);
+void nolo_decode_hmd_marker(drv_priv* priv, unsigned char* data);
+void nolo_decode_controller(drv_priv* priv, unsigned char* data);
+void nolo_decode_orientation(const unsigned char* data, quatf* quat);
+void nolo_decode_position(const unsigned char* data, vec3f* pos);
+
+#endif
+

+ 169 - 0
src/drv_nolo/packet.c

@@ -0,0 +1,169 @@
+/*
+ * OpenHMD - Free and Open Source API and drivers for immersive technology.
+ * Copyright (C) 2017 Joey Ferwerda.
+ * Distributed under the Boost 1.0 licence, see LICENSE for full text.
+ */
+
+/* NOLO VR - Packet Decoding and Utilities */
+
+#include <stdio.h>
+#include "nolo.h"
+
+#define DELTA 0x9e3779b9
+#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
+
+void btea_decrypt(uint32_t *v, int n, int base_rounds, uint32_t const key[4])
+{
+	uint32_t y, z, sum;
+	unsigned p, rounds, e;
+
+	/* Decoding Part */
+	rounds = base_rounds + 52/n;
+	sum = rounds*DELTA;
+	y = v[0];
+
+	do {
+		e = (sum >> 2) & 3;
+		for (p=n-1; p>0; p--) {
+			z = v[p-1];
+			y = v[p] -= MX;
+		}
+
+		z = v[n-1];
+		y = v[0] -= MX;
+		sum -= DELTA;
+	} while (--rounds);
+}
+
+void nolo_decrypt_data(unsigned char* buf)
+{
+	const int cryptwords = (64-4)/4, cryptoffset = 1;
+	static const uint32_t key[4] = {0x875bcc51, 0xa7637a66, 0x50960967, 0xf8536c51};
+	uint32_t cryptpart[cryptwords];
+
+	// Decrypt encrypted portion
+	for (int i = 0; i < cryptwords; i++) {
+	cryptpart[i] =
+		((uint32_t)buf[cryptoffset+4*i  ]) << 0  |
+		((uint32_t)buf[cryptoffset+4*i+1]) << 8  |
+		((uint32_t)buf[cryptoffset+4*i+2]) << 16 |
+		((uint32_t)buf[cryptoffset+4*i+3]) << 24;
+	}
+
+	btea_decrypt(cryptpart, cryptwords, 1, key);
+
+	for (int i = 0; i < cryptwords; i++) {
+		buf[cryptoffset+4*i  ] = cryptpart[i] >> 0;
+		buf[cryptoffset+4*i+1] = cryptpart[i] >> 8;
+		buf[cryptoffset+4*i+2] = cryptpart[i] >> 16;
+		buf[cryptoffset+4*i+3] = cryptpart[i] >> 24;
+	}
+}
+
+void nolo_decode_position(const unsigned char* data, vec3f* pos)
+{
+	const double scale = 0.0001f;
+
+	pos->x = (scale * (int16_t)(data[0]<<8 | data[1]));
+	pos->y = (scale * (int16_t)(data[2]<<8 | data[3]));
+	pos->z = (scale *          (data[4]<<8 | data[5]));
+}
+
+void nolo_decode_orientation(const unsigned char* data, quatf* quat)
+{
+	double w,i,j,k, scale;
+	// CV1 order
+	w = (int16_t)(data[0]<<8 | data[1]);
+	i = (int16_t)(data[2]<<8 | data[3]);
+	j = (int16_t)(data[4]<<8 | data[5]);
+	k = (int16_t)(data[6]<<8 | data[7]);
+	// Normalize (unknown if scale is constant)
+	//scale = 1.0/sqrt(i*i+j*j+k*k+w*w);
+	// Turns out it is fixed point. But the android driver author
+	// either didn't know, or didn't trust it.
+	// Unknown if normalizing it helps
+	scale = 1.0 / 16384;
+	//std::cout << "Scale: " << scale << std::endl;
+	w *= scale;
+	i *= scale;
+	j *= scale;
+	k *= scale;
+
+	// Reorder
+	quat->w = w;
+	quat->x = i;
+	quat->y = k;
+	quat->z = -j;
+}
+
+void nolo_decode_controller(drv_priv* priv, unsigned char* data)
+{
+	uint8_t bit, newbuttonstate;
+
+	if (data[0] != 2 || data[1] != 1) {
+	// Unknown version
+	/* Happens when controllers aren't on.
+	std::cout << "Nolo: Unknown controller "
+	  << " version " << (int)data[0] << " " << (int)data[1]
+	  << std::endl;
+	*/
+		return;
+	}
+
+	vec3f position;
+	quatf orientation;
+
+	nolo_decode_position(data+3, &position);
+	nolo_decode_orientation(data+3+3*2, &orientation);
+
+	//Change button state
+	newbuttonstate = data[3+3*2+4*2];
+	if (priv->button_state != newbuttonstate)
+	{
+		for (bit=0; bit<6; bit++)
+		{
+			if ((priv->button_state & 1<<bit) != (newbuttonstate & 1<<bit))
+			{
+				ohmd_digital_input_event event = { /* button index */ bit, /* button state */ (newbuttonstate & 1<<bit ? OHMD_BUTTON_DOWN : OHMD_BUTTON_UP) };
+				ohmdq_push(priv->base.digital_input_event_queue, &event);
+			}
+		}
+		priv->button_state = newbuttonstate;
+	}
+
+	priv->base.position = position;
+	priv->base.rotation = orientation;
+}
+
+void nolo_decode_hmd_marker(drv_priv* priv, unsigned char* data)
+{
+	if (data[0] != 2 || data[1] != 1) {
+		/* Happens with corrupt packages (mixed with controller data)
+		std::cout << "Nolo: Unknown headset marker"
+		  << " version " << (int)data[0] << " " << (int)data[1]
+		  << std::endl;
+		*/
+		// Unknown version
+		return;
+	}
+
+	vec3f homepos;
+	vec3f position;
+	quatf orientation;
+
+	nolo_decode_position(data+3, &position);
+	nolo_decode_position(data+3+3*2, &homepos);
+	nolo_decode_orientation(data+3+2*3*2+1, &orientation);
+
+	// Tracker viewer kept using the home for head.
+	// Something wrong with how they handle the descriptors.
+	priv->base.position = position;
+	priv->base.rotation = orientation;
+}
+
+void nolo_decode_base_station(drv_priv* priv, unsigned char* data)
+{
+	// Unknown version
+	if (data[0] != 2 || data[1] != 1)
+		return;
+}

+ 6 - 2
src/openhmd.c

@@ -42,6 +42,10 @@ ohmd_context* OHMD_APIENTRY ohmd_ctx_create(void)
 	ctx->drivers[ctx->num_drivers++] = ohmd_create_psvr_drv(ctx);
 #endif
 
+#if DRIVER_NOLO
+	ctx->drivers[ctx->num_drivers++] = ohmd_create_nolo_drv(ctx);
+#endif
+
 #if DRIVER_EXTERNAL
 	ctx->drivers[ctx->num_drivers++] = ohmd_create_external_drv(ctx);
 #endif
@@ -252,7 +256,7 @@ static int ohmd_device_getf_unp(ohmd_device* device, ohmd_float_value type, floa
 			rot = tmp;
 			mat4x4f orient, world_shift, result;
 			omat4x4f_init_look_at(&orient, &rot, &point);
-			omat4x4f_init_translate(&world_shift, +(device->properties.ipd / 2.0f), 0, 0);
+			omat4x4f_init_translate(&world_shift, -device->position.x +(device->properties.ipd / 2.0f), -device->position.y, -device->position.z);
 			omat4x4f_mult(&world_shift, &orient, &result);
 			omat4x4f_transpose(&result, (mat4x4f*)out);
 			return OHMD_S_OK;
@@ -263,7 +267,7 @@ static int ohmd_device_getf_unp(ohmd_device* device, ohmd_float_value type, floa
 			oquatf_mult_me(&rot, &device->rotation_correction);
 			mat4x4f orient, world_shift, result;
 			omat4x4f_init_look_at(&orient, &rot, &point);
-			omat4x4f_init_translate(&world_shift, -(device->properties.ipd / 2.0f), 0, 0);
+			omat4x4f_init_translate(&world_shift, -device->position.x + -(device->properties.ipd / 2.0f), -device->position.y, -device->position.z);
 			omat4x4f_mult(&world_shift, &orient, &result);
 			omat4x4f_transpose(&result, (mat4x4f*)out);
 			return OHMD_S_OK;

+ 2 - 0
src/openhmdi.h

@@ -35,6 +35,7 @@ typedef struct {
 	char product[OHMD_STR_SIZE];
 	char path[OHMD_STR_SIZE];
 	int revision;
+	int id;
 	ohmd_driver* driver_ptr;
 } ohmd_device_desc;
 
@@ -142,6 +143,7 @@ ohmd_driver* ohmd_create_oculus_rift_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_deepoon_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_htc_vive_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_psvr_drv(ohmd_context* ctx);
+ohmd_driver* ohmd_create_nolo_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_external_drv(ohmd_context* ctx);
 ohmd_driver* ohmd_create_android_drv(ohmd_context* ctx);