mirror of https://github.com/bjornbytes/lovr.git
OpenXR: Add support for XR_FB_hand_tracking_mesh;
This adds the ability to load and animate a mesh for hand tracking on the Oculus Quest. It is more or less identical to the current functionality on the vrapi driver. One key part of this change is that getPose in OpenXR will see if action spaces are active before locating their spaces. This is due to some behavior observed on the Oculus Quest with hand tracking where pose actions for controllers would return invalid data with all of the location flags erroneously set. The only way to detect and work around this is to check the pose action state. When this happens, we fall back to returning the pose of the wrist joint, which is where the Oculus hand mesh wants to be drawn. In the event that both controllers and hand tracking are active, the controller pose will be returned by getPose but the wrist joint can still be accessed using getSkeleton. Note that this does not yet include support for properly scaling the hand mesh. There are numerous opportunities for optimization here that may be investigated in the future, though performance is well within an acceptable range right now.
This commit is contained in:
parent
5113290e1b
commit
29cbe63b1d
|
@ -1,8 +1,10 @@
|
|||
#include "headset/headset.h"
|
||||
#include "data/blob.h"
|
||||
#include "event/event.h"
|
||||
#include "filesystem/filesystem.h"
|
||||
#include "graphics/graphics.h"
|
||||
#include "graphics/canvas.h"
|
||||
#include "graphics/model.h"
|
||||
#include "graphics/texture.h"
|
||||
#include "core/os.h"
|
||||
#include "core/util.h"
|
||||
|
@ -108,11 +110,13 @@ EGLConfig os_get_egl_config(void);
|
|||
X(xrAttachSessionActionSets)\
|
||||
X(xrGetActionStateBoolean)\
|
||||
X(xrGetActionStateFloat)\
|
||||
X(xrGetActionStatePose)\
|
||||
X(xrSyncActions)\
|
||||
X(xrApplyHapticFeedback)\
|
||||
X(xrCreateHandTrackerEXT)\
|
||||
X(xrDestroyHandTrackerEXT)\
|
||||
X(xrLocateHandJointsEXT)
|
||||
X(xrLocateHandJointsEXT)\
|
||||
X(xrGetHandMeshFB)
|
||||
|
||||
#define XR_DECLARE(fn) static PFN_##fn fn;
|
||||
#define XR_LOAD(fn) xrGetInstanceProcAddr(state.instance, #fn, (PFN_xrVoidFunction*) &fn);
|
||||
|
@ -149,6 +153,7 @@ static struct {
|
|||
XrHandTrackerEXT handTrackers[2];
|
||||
struct {
|
||||
bool handTracking;
|
||||
bool handTrackingMesh;
|
||||
bool overlay;
|
||||
} features;
|
||||
} state;
|
||||
|
@ -171,6 +176,30 @@ static bool hasExtension(XrExtensionProperties* extensions, uint32_t count, cons
|
|||
return false;
|
||||
}
|
||||
|
||||
// Hand trackers are created lazily because on some implementations xrCreateHandTrackerEXT will
|
||||
// return XR_ERROR_FEATURE_UNSUPPORTED if called too early.
|
||||
static XrHandTrackerEXT getHandTracker(Device device) {
|
||||
if (!state.features.handTracking || (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT)) {
|
||||
return XR_NULL_HANDLE;
|
||||
}
|
||||
|
||||
XrHandTrackerEXT* tracker = &state.handTrackers[device == DEVICE_HAND_RIGHT];
|
||||
|
||||
if (!*tracker) {
|
||||
XrHandTrackerCreateInfoEXT info = {
|
||||
.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
|
||||
.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT,
|
||||
.hand = device == DEVICE_HAND_RIGHT ? XR_HAND_RIGHT_EXT : XR_HAND_LEFT_EXT
|
||||
};
|
||||
|
||||
if (XR_FAILED(xrCreateHandTrackerEXT(state.session, &info, tracker))) {
|
||||
return XR_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
return *tracker;
|
||||
}
|
||||
|
||||
static void openxr_destroy();
|
||||
|
||||
static bool openxr_init(float supersample, float offset, uint32_t msaa, bool overlay) {
|
||||
|
@ -218,6 +247,11 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
state.features.handTracking = true;
|
||||
}
|
||||
|
||||
if (hasExtension(extensions, extensionCount, XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME)) {
|
||||
enabledExtensionNames[enabledExtensionCount++] = XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME;
|
||||
state.features.handTrackingMesh = true;
|
||||
}
|
||||
|
||||
#ifdef XR_EXTX_overlay
|
||||
// Provisional extension.
|
||||
if (overlay && hasExtension(extensions, extensionCount, XR_EXTX_OVERLAY_EXTENSION_NAME)) {
|
||||
|
@ -705,7 +739,62 @@ static bool openxr_getPose(Device device, vec3 position, quat orientation) {
|
|||
return false;
|
||||
}
|
||||
|
||||
XrSpaceLocation location = { .type = XR_TYPE_SPACE_LOCATION, .next = NULL };
|
||||
// If there's a pose action for this device, see if the action is active before locating its space
|
||||
XrAction action = XR_NULL_HANDLE;
|
||||
switch (device) {
|
||||
case DEVICE_HAND_LEFT: action = state.actions[ACTION_HAND_POSE]; break;
|
||||
case DEVICE_HAND_RIGHT: action = state.actions[ACTION_HAND_POSE]; break;
|
||||
case DEVICE_HAND_LEFT_POINT: action = state.actions[ACTION_POINTER_POSE]; break;
|
||||
case DEVICE_HAND_RIGHT_POINT: action = state.actions[ACTION_POINTER_POSE]; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (action) {
|
||||
XrActionStateGetInfo info = {
|
||||
.type = XR_TYPE_ACTION_STATE_GET_INFO,
|
||||
.action = action,
|
||||
.subactionPath = state.actionFilters[device == DEVICE_HAND_RIGHT || device == DEVICE_HAND_RIGHT_POINT]
|
||||
};
|
||||
|
||||
XrActionStatePose poseState = {
|
||||
.type = XR_TYPE_ACTION_STATE_POSE
|
||||
};
|
||||
|
||||
XR(xrGetActionStatePose(state.session, &info, &poseState));
|
||||
|
||||
// If the action isn't active, try fallbacks for some devices (hand tracking)
|
||||
if (!poseState.isActive) {
|
||||
XrHandTrackerEXT tracker = getHandTracker(device);
|
||||
|
||||
if (!tracker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrHandJointsLocateInfoEXT info = {
|
||||
.type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
||||
.baseSpace = state.referenceSpace,
|
||||
.time = state.frameState.predictedDisplayTime
|
||||
};
|
||||
|
||||
XrHandJointLocationEXT joints[26];
|
||||
XrHandJointLocationsEXT hand = {
|
||||
.type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT,
|
||||
.jointCount = sizeof(joints) / sizeof(joints[0]),
|
||||
.jointLocations = joints
|
||||
};
|
||||
|
||||
if (XR_FAILED(xrLocateHandJointsEXT(tracker, &info, &hand)) || !hand.isActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrPosef* pose = &joints[XR_HAND_JOINT_WRIST_EXT].pose;
|
||||
memcpy(orientation, &pose->orientation, 4 * sizeof(float));
|
||||
memcpy(position, &pose->position, 3 * sizeof(float));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
XrSpaceLocation location = { .type = XR_TYPE_SPACE_LOCATION };
|
||||
xrLocateSpace(state.spaces[device], state.referenceSpace, state.frameState.predictedDisplayTime, &location);
|
||||
memcpy(orientation, &location.pose.orientation, 4 * sizeof(float));
|
||||
memcpy(position, &location.pose.position, 3 * sizeof(float));
|
||||
|
@ -806,30 +895,12 @@ static bool openxr_getAxis(Device device, DeviceAxis axis, float* value) {
|
|||
}
|
||||
|
||||
static bool openxr_getSkeleton(Device device, float* poses) {
|
||||
if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) {
|
||||
XrHandTrackerEXT tracker = getHandTracker(device);
|
||||
|
||||
if (!tracker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!state.features.handTracking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrHandTrackerEXT* handTracker = &state.handTrackers[device - DEVICE_HAND_LEFT];
|
||||
|
||||
// Hand trackers are created lazily because on some implementations xrCreateHandTrackerEXT will
|
||||
// return XR_ERROR_FEATURE_UNSUPPORTED if called too early.
|
||||
if (!*handTracker) {
|
||||
XrHandTrackerCreateInfoEXT info = {
|
||||
.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
|
||||
.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT,
|
||||
.hand = device == DEVICE_HAND_LEFT ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT
|
||||
};
|
||||
|
||||
if (XR_FAILED(xrCreateHandTrackerEXT(state.session, &info, handTracker))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
XrHandJointsLocateInfoEXT info = {
|
||||
.type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
||||
.baseSpace = state.referenceSpace,
|
||||
|
@ -843,7 +914,7 @@ static bool openxr_getSkeleton(Device device, float* poses) {
|
|||
.jointLocations = joints
|
||||
};
|
||||
|
||||
if (XR_FAILED(xrLocateHandJointsEXT(*handTracker, &info, &hand)) || !hand.isActive) {
|
||||
if (XR_FAILED(xrLocateHandJointsEXT(tracker, &info, &hand)) || !hand.isActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -881,11 +952,286 @@ static bool openxr_vibrate(Device device, float power, float duration, float fre
|
|||
}
|
||||
|
||||
static struct ModelData* openxr_newModelData(Device device, bool animated) {
|
||||
return NULL;
|
||||
if (!state.features.handTrackingMesh) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XrHandTrackerEXT tracker = getHandTracker(device);
|
||||
|
||||
if (!tracker) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XrHandTrackingMeshFB mesh = { .type = XR_TYPE_HAND_TRACKING_MESH_FB };
|
||||
XrResult result = xrGetHandMeshFB(tracker, &mesh);
|
||||
|
||||
if (XR_FAILED(result)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t jointCount = mesh.jointCapacityInput = mesh.jointCountOutput;
|
||||
uint32_t vertexCount = mesh.vertexCapacityInput = mesh.vertexCountOutput;
|
||||
uint32_t indexCount = mesh.indexCapacityInput = mesh.indexCountOutput;
|
||||
|
||||
size_t sizes[10];
|
||||
size_t totalSize = 0;
|
||||
size_t alignment = 8;
|
||||
totalSize += sizes[0] = ALIGN(jointCount * sizeof(XrPosef), alignment);
|
||||
totalSize += sizes[1] = ALIGN(jointCount * sizeof(float), alignment);
|
||||
totalSize += sizes[2] = ALIGN(jointCount * sizeof(XrHandJointEXT), alignment);
|
||||
totalSize += sizes[3] = ALIGN(vertexCount * sizeof(XrVector3f), alignment);
|
||||
totalSize += sizes[4] = ALIGN(vertexCount * sizeof(XrVector3f), alignment);
|
||||
totalSize += sizes[5] = ALIGN(vertexCount * sizeof(XrVector2f), alignment);
|
||||
totalSize += sizes[6] = ALIGN(vertexCount * sizeof(XrVector4sFB), alignment);
|
||||
totalSize += sizes[7] = ALIGN(vertexCount * sizeof(XrVector4f), alignment);
|
||||
totalSize += sizes[8] = ALIGN(indexCount * sizeof(int16_t), alignment);
|
||||
totalSize += sizes[9] = ALIGN(jointCount * 16 * sizeof(float), alignment);
|
||||
|
||||
char* meshData = malloc(totalSize);
|
||||
if (!meshData) return NULL;
|
||||
|
||||
size_t offset = 0;
|
||||
mesh.jointBindPoses = (XrPosef*) (meshData + offset), offset += sizes[0];
|
||||
mesh.jointRadii = (float*) (meshData + offset), offset += sizes[1];
|
||||
mesh.jointParents = (XrHandJointEXT*) (meshData + offset), offset += sizes[2];
|
||||
mesh.vertexPositions = (XrVector3f*) (meshData + offset), offset += sizes[3];
|
||||
mesh.vertexNormals = (XrVector3f*) (meshData + offset), offset += sizes[4];
|
||||
mesh.vertexUVs = (XrVector2f*) (meshData + offset), offset += sizes[5];
|
||||
mesh.vertexBlendIndices = (XrVector4sFB*) (meshData + offset), offset += sizes[6];
|
||||
mesh.vertexBlendWeights = (XrVector4f*) (meshData + offset), offset += sizes[7];
|
||||
mesh.indices = (int16_t*) (meshData + offset), offset += sizes[8];
|
||||
float* inverseBindMatrices = (float*) (meshData + offset); offset += sizes[9];
|
||||
lovrAssert(offset == totalSize, "Unreachable");
|
||||
|
||||
result = xrGetHandMeshFB(tracker, &mesh);
|
||||
if (XR_FAILED(result)) {
|
||||
free(meshData);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ModelData* model = calloc(1, sizeof(ModelData));
|
||||
lovrAssert(model, "Out of memory");
|
||||
model->ref = 1;
|
||||
model->blobCount = 1;
|
||||
model->bufferCount = 6;
|
||||
model->attributeCount = 6;
|
||||
model->primitiveCount = 1;
|
||||
model->skinCount = 1;
|
||||
model->jointCount = jointCount;
|
||||
model->childCount = jointCount + 1;
|
||||
model->nodeCount = 2 + jointCount;
|
||||
lovrModelDataAllocate(model);
|
||||
|
||||
model->blobs[0] = lovrBlobCreate(meshData, totalSize, "Hand Mesh Data");
|
||||
|
||||
model->buffers[0] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.vertexPositions - (char*) meshData,
|
||||
.data = (char*) mesh.vertexPositions,
|
||||
.size = sizeof(mesh.vertexPositions[0]) * vertexCount,
|
||||
.stride = sizeof(mesh.vertexPositions[0])
|
||||
};
|
||||
|
||||
model->buffers[1] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.vertexNormals - (char*) meshData,
|
||||
.data = (char*) mesh.vertexNormals,
|
||||
.size = sizeof(mesh.vertexNormals[0]) * vertexCount,
|
||||
.stride = sizeof(mesh.vertexNormals[0])
|
||||
};
|
||||
|
||||
model->buffers[2] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.vertexUVs - (char*) meshData,
|
||||
.data = (char*) mesh.vertexUVs,
|
||||
.size = sizeof(mesh.vertexUVs[0]) * vertexCount,
|
||||
.stride = sizeof(mesh.vertexUVs[0])
|
||||
};
|
||||
|
||||
model->buffers[3] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.vertexBlendIndices - (char*) meshData,
|
||||
.data = (char*) mesh.vertexBlendIndices,
|
||||
.size = sizeof(mesh.vertexBlendIndices[0]) * vertexCount,
|
||||
.stride = sizeof(mesh.vertexBlendIndices[0])
|
||||
};
|
||||
|
||||
model->buffers[4] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.vertexBlendWeights - (char*) meshData,
|
||||
.data = (char*) mesh.vertexBlendWeights,
|
||||
.size = sizeof(mesh.vertexBlendWeights[0]) * vertexCount,
|
||||
.stride = sizeof(mesh.vertexBlendWeights[0])
|
||||
};
|
||||
|
||||
model->buffers[5] = (ModelBuffer) {
|
||||
.offset = (char*) mesh.indices - (char*) meshData,
|
||||
.data = (char*) mesh.indices,
|
||||
.size = sizeof(mesh.indices[0]) * indexCount,
|
||||
.stride = sizeof(mesh.indices[0])
|
||||
};
|
||||
|
||||
model->attributes[0] = (ModelAttribute) { .buffer = 0, .type = F32, .components = 3 };
|
||||
model->attributes[1] = (ModelAttribute) { .buffer = 1, .type = F32, .components = 3 };
|
||||
model->attributes[2] = (ModelAttribute) { .buffer = 2, .type = F32, .components = 2 };
|
||||
model->attributes[3] = (ModelAttribute) { .buffer = 3, .type = I16, .components = 4 };
|
||||
model->attributes[4] = (ModelAttribute) { .buffer = 4, .type = F32, .components = 4 };
|
||||
model->attributes[5] = (ModelAttribute) { .buffer = 5, .type = U16, .count = indexCount };
|
||||
|
||||
model->primitives[0] = (ModelPrimitive) {
|
||||
.mode = DRAW_TRIANGLES,
|
||||
.attributes = {
|
||||
[ATTR_POSITION] = &model->attributes[0],
|
||||
[ATTR_NORMAL] = &model->attributes[1],
|
||||
[ATTR_TEXCOORD] = &model->attributes[2],
|
||||
[ATTR_BONES] = &model->attributes[3],
|
||||
[ATTR_WEIGHTS] = &model->attributes[4]
|
||||
},
|
||||
.indices = &model->attributes[5],
|
||||
.material = ~0u
|
||||
};
|
||||
|
||||
// The nodes in the Model correspond directly to the joints in the skin, for convenience
|
||||
uint32_t* children = model->children;
|
||||
model->skins[0].joints = model->joints;
|
||||
model->skins[0].jointCount = model->jointCount;
|
||||
model->skins[0].inverseBindMatrices = inverseBindMatrices;
|
||||
for (uint32_t i = 0; i < model->jointCount; i++) {
|
||||
model->joints[i] = i;
|
||||
|
||||
// Joint node
|
||||
model->nodes[i] = (ModelNode) {
|
||||
.transform.properties.translation = { 0.f, 0.f, 0.f },
|
||||
.transform.properties.rotation = { 0.f, 0.f, 0.f, 1.f },
|
||||
.transform.properties.scale = { 1.f, 1.f, 1.f },
|
||||
.skin = ~0u
|
||||
};
|
||||
|
||||
// Inverse bind matrix
|
||||
XrPosef* pose = &mesh.jointBindPoses[i];
|
||||
float* inverseBindMatrix = inverseBindMatrices + 16 * i;
|
||||
mat4_fromQuat(inverseBindMatrix, &pose->orientation.x);
|
||||
memcpy(inverseBindMatrix + 12, &pose->position.x, 3 * sizeof(float));
|
||||
mat4_invert(inverseBindMatrix);
|
||||
|
||||
// Add child bones by looking for any bones that have a parent of the current bone.
|
||||
// This is somewhat slow; use the fact that bones are sorted to reduce the work a bit.
|
||||
model->nodes[i].childCount = 0;
|
||||
model->nodes[i].children = children;
|
||||
for (uint32_t j = i + 1; j < jointCount; j++) {
|
||||
if (mesh.jointParents[j] == i) {
|
||||
model->nodes[i].children[model->nodes[i].childCount++] = j;
|
||||
children++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a node that holds the skinned mesh
|
||||
model->nodes[model->jointCount] = (ModelNode) {
|
||||
.transform.properties.translation = { 0.f, 0.f, 0.f },
|
||||
.transform.properties.rotation = { 0.f, 0.f, 0.f, 1.f },
|
||||
.transform.properties.scale = { 1.f, 1.f, 1.f },
|
||||
.primitiveIndex = 0,
|
||||
.primitiveCount = 1,
|
||||
.skin = 0
|
||||
};
|
||||
|
||||
// The root node has the mesh node and root joint as children
|
||||
model->rootNode = model->jointCount + 1;
|
||||
model->nodes[model->rootNode] = (ModelNode) {
|
||||
.matrix = true,
|
||||
.transform = { MAT4_IDENTITY },
|
||||
.childCount = 2,
|
||||
.children = children,
|
||||
.skin = ~0u
|
||||
};
|
||||
|
||||
// Add the children to the root node
|
||||
*children++ = XR_HAND_JOINT_WRIST_EXT;
|
||||
*children++ = model->jointCount;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
static bool openxr_animate(Device device, struct Model* model) {
|
||||
return false;
|
||||
XrHandTrackerEXT tracker = getHandTracker(device);
|
||||
|
||||
if (!tracker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO might be nice to cache joints so getSkeleton/animate only locate joints once (profile)
|
||||
XrHandJointsLocateInfoEXT info = {
|
||||
.type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
||||
.baseSpace = state.referenceSpace,
|
||||
.time = state.frameState.predictedDisplayTime
|
||||
};
|
||||
|
||||
XrHandJointLocationEXT joints[26];
|
||||
XrHandJointLocationsEXT hand = {
|
||||
.type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT,
|
||||
.jointCount = sizeof(joints) / sizeof(joints[0]),
|
||||
.jointLocations = joints
|
||||
};
|
||||
|
||||
if (XR_FAILED(xrLocateHandJointsEXT(tracker, &info, &hand)) || !hand.isActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lovrModelResetPose(model);
|
||||
|
||||
// This is kinda brittle, ideally we would use the jointParents from the actual mesh object
|
||||
uint32_t jointParents[26] = {
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
~0u,
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
XR_HAND_JOINT_THUMB_METACARPAL_EXT,
|
||||
XR_HAND_JOINT_THUMB_PROXIMAL_EXT,
|
||||
XR_HAND_JOINT_THUMB_DISTAL_EXT,
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
XR_HAND_JOINT_INDEX_METACARPAL_EXT,
|
||||
XR_HAND_JOINT_INDEX_PROXIMAL_EXT,
|
||||
XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT,
|
||||
XR_HAND_JOINT_INDEX_DISTAL_EXT,
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
XR_HAND_JOINT_MIDDLE_METACARPAL_EXT,
|
||||
XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT,
|
||||
XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT,
|
||||
XR_HAND_JOINT_MIDDLE_DISTAL_EXT,
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
XR_HAND_JOINT_RING_METACARPAL_EXT,
|
||||
XR_HAND_JOINT_RING_PROXIMAL_EXT,
|
||||
XR_HAND_JOINT_RING_INTERMEDIATE_EXT,
|
||||
XR_HAND_JOINT_RING_DISTAL_EXT,
|
||||
XR_HAND_JOINT_WRIST_EXT,
|
||||
XR_HAND_JOINT_LITTLE_METACARPAL_EXT,
|
||||
XR_HAND_JOINT_LITTLE_PROXIMAL_EXT,
|
||||
XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT,
|
||||
XR_HAND_JOINT_LITTLE_DISTAL_EXT
|
||||
};
|
||||
|
||||
// The following can be optimized a lot (ideally we would set the global transform for the nodes)
|
||||
for (uint32_t i = 0; i < sizeof(joints) / sizeof(joints[0]); i++) {
|
||||
if (jointParents[i] == ~0u) {
|
||||
float position[4] = { 0.f, 0.f, 0.f };
|
||||
float orientation[4] = { 0.f, 0.f, 0.f, 1.f };
|
||||
lovrModelPose(model, i, position, orientation, 1.f);
|
||||
} else {
|
||||
XrPosef* parent = &joints[jointParents[i]].pose;
|
||||
XrPosef* pose = &joints[i].pose;
|
||||
|
||||
// Convert global pose to parent-local pose (premultiply with inverse of parent pose)
|
||||
// TODO there should be maf for this
|
||||
float position[4], orientation[4];
|
||||
vec3_init(position, &pose->position.x);
|
||||
vec3_sub(position, &parent->position.x);
|
||||
|
||||
quat_init(orientation, &parent->orientation.x);
|
||||
quat_conjugate(orientation);
|
||||
|
||||
quat_rotate(orientation, position);
|
||||
quat_mul(orientation, orientation, &pose->orientation.x);
|
||||
|
||||
lovrModelPose(model, i, position, orientation, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void openxr_renderTo(void (*callback)(void*), void* userdata) {
|
||||
|
|
Loading…
Reference in New Issue