在HoloLens或Daydream之外使用Daydream控制器?

9
这个白日梦控制器很棒,我们希望能在我的AR应用中使用它。 它可以通过蓝牙与HoloLens配对,但不确定是否可以在Unity中查看它。
HoloLens和daydream都需要自己的Unity技术预览。 gvr Controller code 在网上,但似乎直接与GVR C api通信。
您有没有想过在daydream技术预览之外的Unity中访问daydream控制器是否可能?

3
顺便提一句,Mr. Doob已经构建了@Dotphracker惊人代码答案的JS版本,网址如下。https://github.com/mrdoob/daydream-controller.js/ - Anand Agarawala
1个回答

20

没有使用GVR服务也可以轻松访问Daydream控制器。实际上,我正在研究这方面的内容,并可以分享我的一些知识。

获取数据

使用蓝牙Gatt协议,您可以查看所有可用数据并订阅所需的ID。我不确定在Hololens/Unity中如何执行此操作。基本上,您需要执行以下操作:

  1. 连接设备
  2. 选择服务 (0000fe55-0000-1000-8000-00805f9b34fb)
  3. 选择特征值 (00000001-1000-1000-8000-00805f9b34fb)
  4. 请求其通知 (00002902-0000-1000-8000-00805f9b34fb)

Android示例:

static final UUID DAYDREAM_CUSTOM_SERVICE = UUID.fromString("0000fe55-0000-1000-8000-00805f9b34fb");
static final UUID DAYDREAM_CHARACTERISTIC = UUID.fromString("00000001-1000-1000-8000-00805f9b34fb");
static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
...
BluetoothGattService service = gatt.getService(DAYDREAM_CUSTOM_SERVICE);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(DAYDREAM_CHARACTERISTIC);
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue( BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);

我建议您查阅Bluetooth Gatt了解更多有关服务和特征的信息。在编写代码之前,我还使用了Play商店上的BLE Scanner应用程序来查看大量此类信息。

解析数据

设备提供了20个字节的数据。其中包括时间、方向、加速度、原始陀螺仪、触摸位置和按钮标志。

例如(平放在桌子上):

5BEBFFB825FDB000041000B00000000000000000
63EFFFB825FDB000041000B00000000000000008
6C73FFB825FDB000041000B00000000000000038

示例(使用触摸板):

480BFE87EB00E801841000B00000000191FBA008
4F8FFE47EB00E800441000B0000003FEB1FBA038
5893FE27EB00EFFF041000B0000003FF51FBA000

字节的定义如下:
Bytes:

  - 1: TTTT TTTT * T for time, loops
  - 2: TNNN NNKK * N is sequence number
  - 3: KKKK KKKK * IJK is orientation
  - 4: KKKI IIII
  - 5: IIII IIII
  - 6: JJJJ JJJJ
  - 7: JJJJ JOOO * MNO is acceleration
  - 8: OOOO OOOO
  - 9: OONN NNNN
  -10: NNNN NNNM
  -11: MMMM MMMM
  -12: MMMM CCCC * CDE for raw gyro
  -13: CCCC CCCC
  -14: CDDD DDDD
  -15: DDDD DDEE
  -16: EEEE EEEE
  -17: EEEX XXXX * All the X is the X touch position (8 bits)
  -18: XXXY YYYY * Y the Y touch position (8 bits)
  -19: YYYB BBBB * B the buttons (5 bits | [+][-][App][Home][Click])
  -20: Values vary

通过这个方法,我能让触摸板和按钮与任何我可以为其构建应用程序的蓝牙设备配合使用。此外,您需要添加重置设备位置、控制音频等功能。

在Android上使用这个定义:

static final int CLICK_BTN = 0x1;
static final int HOME_BTN = 0x2;
static final int APP_BTN = 0x4;
static final int VOL_DOWN_BTN = 0x8;
static final int VOL_UP_BTN = 0x10;
float xTouch=0, yTouch=0;
...
final boolean isClickDown = (data[18] & CLICK_BTN) > 0;
final boolean isHomeDown = (data[18] & HOME_BTN) > 0;
final boolean isAppDown = (data[18] & APP_BTN) > 0;
final boolean isVolMinusDown = (data[18] & VOL_DOWN_BTN) > 0;
final boolean isVolPlusDown = (data[18] & VOL_UP_BTN) > 0;

final int time = ((data[0] & 0xFF) << 1 | (data[1] & 0x80) >> 7 );

final int seq = (data[1] & 0x7C) >> 2;

int xOri = (data[1] & 0x03) << 11 | (data[2] & 0xFF) << 3 | (data[3] & 0xE0) >> 5;
xOri = (xOri << 19) >> 19;

int yOri = (data[3] & 0x1F) << 8 | (data[4] & 0xFF);
yOri = (yOri << 19) >> 19;

int zOri = (data[5] & 0xFF) << 5 | (data[6] & 0xF8) >> 3;
zOri = (zOri << 19) >> 19;

int xAcc = (data[6] & 0x07) << 10 | (data[7] & 0xFF) << 2 | (data[8] & 0xC0) >> 6;
xAcc = (xAcc << 19) >> 19;

int yAcc = (data[8] & 0x3F) << 7 | (data[9] & 0xFE) >>> 1;
yAcc = (yAcc << 19) >> 19;

int zAcc = (data[9] & 0x01) << 12 | (data[10] & 0xFF) << 4 | (data[11] & 0xF0) >> 4;
zAcc = (zAcc << 19) >> 19;

int xGyro = ((data[11] & 0x0F) << 9 | (data[12] & 0xFF) << 1 | (data[13] & 0x80) >> 7);
xGyro = (xGyro << 19) >> 19;

int yGyro = ((data[13] & 0x7F) << 6 | (data[14] & 0xFC) >> 2 );
yGyro = (yGyro << 19) >> 19;

int zGyro = ((data[14] & 0x03) << 11 | (data[15] & 0xFF) << 3 | (data[16] & 0xE0) >> 5);
zGyro = (zGyro << 19) >> 19;

xTouch = ((data[16] & 0x1F) << 3 | (data[17] & 0xE0) >> 5) / 255.0f;
yTouch = ((data[17] & 0x1F) << 3 | (data[18] & 0xE0) >> 5) / 255.0f;

这段代码可以进行优化,但它赋值了除最后一个字节之外的所有位。代码value = (value << 19) >> 19也可以写成value = (value >> 12) == 0 ? value : ~0x1FFF | value。这只是将有符号位扩展为32位有符号整数。
希望这能帮到您,并期待更多答案。
-- 更新2 --
在查看gvr代码后,我发现之前的假设存在一些问题。实际上是方向/加速度/陀螺仪。此外,序列还有1个位,时间则减少了1个位。我已更新字节定义和Android示例。
此外,X、Y、Z值需要缩放为浮点型。对于Unity,您可以将整数放入Vector3中,然后使用以下内容。我还对oriVector中的x和y取了反,在Unity中。
Vector3 oriVector = new Vector3 (-xOri, -yOri, zOri);
...
oriVector *= (2 * Mathf.PI / 4095.0);
accVector *= (8 * 9.8 / 4095.0);
gyroVector *= (2048 / 180 * Mathf.PI / 4095.0);

要获得旋转,只需要使用oriVector。实际上,它是作为单位向量乘以角度存储的轴角。

public Quaternion orientation = Quaternion.identity;
private Quaternion controllerPoseInSensorSpace = Quaternion.identity;
private Quaternion startFromSensorTransformation = Quaternion.identity;
...
// do this bit after getting the data and scaling it
float sqrMagnitude = oriVector.sqrMagnitude;
if (sqrMagnitude > 0) {
    // extract radian angle
    float w = Mathf.Sqrt (sqrMagnitude);
    // normalize vector
    oriVector /= w;
    // set orientation space
    setOrientationInSensorSpace (w,oriVector);
}
...
// then assign to a Transform
controller.localRotation = this.orientation;
...
// sets orientation with rotation offset
void setOrientationInSensorSpace(float angle, Vector3 axis) {
    // set orientation space
    this.controllerPoseInSensorSpace = Quaternion.AngleAxis(angle*Mathf.Rad2Deg,axis);
    // rotate based on centered offset
    this.orientation = this.startFromSensorTransformation * this.controllerPoseInSensorSpace;
}
...
// after holding home for 600 milliseconds
private void setStartFromSensorTransformation() {
    Vector3 angles = this.controllerPoseInSensorSpace.eulerAngles;
    // reset rotation on Y
    this.startFromSensorTransformation.Set(0,Mathf.Sin(-angles.y * Mathf.Deg2Rad / 2f), 0, Mathf.Cos(angles.y * Mathf.Deg2Rad / 2f));
    // could also reset all, easier to work with
    //this.startFromSensorTransformation = Quaternion.Inverse (this.controllerPoseInSensorSpace);
}

这与使用普通蓝牙设备启用日常梦想有关。我还在Unity3D中使用了以上C#代码。
--更新1--
添加了更完整的字节定义。之前缺失的值是陀螺仪、磁力计和加速度数据。它们各自具有三个13位带符号整数。还有一个顺序号与时间位一起隐藏。
前进方向
为了将设备数据与其他平台一起使用,您需要将数据通过类似于9DoF/IMU设备使用的等式处理。我不知道如何解决这个问题。
最后一个字节
这可能是保留标志,我不确定含义,但我有一些发现要列出。版本号是控制器的固件版本。
1.0.10 (out of the box): 0xF0/0xF8
1.0.10 (previously used with gvr): 0x00/0x08/0x38/0x51
1.0.15: 0x00/0x70

1
顺便说一下,@Dotphracker,我们现在开始将你的代码移植到HoloLens上,因为内置的输入方法不是很可靠。你有最近的更新吗?或者你能分享一些代码/Github链接吗?我们会非常感激的。 - Anand Agarawala
1
@Dotphracker,感谢您更新您的代码!非常有帮助。我们正在使用JS传感器融合代码这里,但仍然遇到一些问题。不知道您是否能在Github上分享您整个项目的代码? - Anand Agarawala
@Dotphracker,您能否详细说明一下您代码中的rawOrientation是什么? - jahmed
请看一下如何解析您的oriX。您写了 4: KKKI IIII。一个字节的前3位的位掩码是0xE0,但在您的代码中是 int xOri = (data[1] & 0x03) << 11 | (data[2] & 0xFF) << 3 | (data[3] & 0x80) >> 5。您使用了位掩码0x80。顺便说一下,我如何黑进Google Daydream控制器,您所称之为“方向性”在这篇文章中是指陀螺仪。 - Vertex
@ForrestPorter 所以实际上没有磁力计,IJK 是用于定位的?这是否意味着 Daydream 控制器本身具有内部融合算法,方向应该稳定而不会漂移? - undefined
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接