Skip to content

Commit 6f3caea

Browse files
authored
Merge pull request #55 from De-Panther/hands_api
WebXR Hand Input support
2 parents 0953c6b + 106a1ed commit 6f3caea

File tree

17 files changed

+543
-152
lines changed

17 files changed

+543
-152
lines changed

Assets/WebGLTemplates/WebXR/webxr.js

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
this.gamepads = [];
1111
this.controllerA = new XRControllerData();
1212
this.controllerB = new XRControllerData();
13+
this.handLeft = new XRHandData();
14+
this.handRight = new XRHandData();
15+
this.frameNumber = 0;
1316
this.xrData = null;
1417
}
1518

1619
function XRControllerData() {
20+
this.frame = 0;
1721
// TODO: set enabled 0 if controller was enable and then disable
1822
this.enabled = 0;
1923
this.hand = 0;
@@ -36,6 +40,26 @@
3640
this.buttonB = 0;
3741
}
3842

43+
function XRHandData() {
44+
this.frame = 0;
45+
// TODO: set enabled 0 if hand was enable and then disable
46+
this.enabled = 0;
47+
this.hand = 0;
48+
this.trigger = 0;
49+
this.squeeze = 0;
50+
this.joints = [];
51+
for (let i = 0; i < 25; i++) {
52+
this.joints.push(new XRJointData());
53+
}
54+
}
55+
56+
function XRJointData() {
57+
this.enabled = 0;
58+
this.position = [0, 0, 0];
59+
this.rotation = [0, 0, 0, 1];
60+
this.radius = 0;
61+
}
62+
3963
function XRManager() {
4064
this.arSession = null;
4165
this.vrSession = null;
@@ -111,7 +135,7 @@
111135
window.requestAnimationFrame( tempRender );
112136
navigator.xr.requestSession('immersive-ar', {
113137
requiredFeatures: ['local-floor'], // TODO: Get this value from Unity
114-
optionalFeatures: ['dom-overlay'],
138+
optionalFeatures: ['dom-overlay', 'hand-tracking'],
115139
domOverlay: {root: this.canvas.parentElement}
116140
}).then(async (session) => {
117141
this.waitingHandheldARHack = false;
@@ -128,7 +152,8 @@
128152
XRManager.prototype.onRequestVRSession = function () {
129153
if (!this.isVRSupported) return;
130154
navigator.xr.requestSession('immersive-vr', {
131-
requiredFeatures: ['local-floor'] // TODO: Get this value from Unity
155+
requiredFeatures: ['local-floor'], // TODO: Get this value from Unity
156+
optionalFeatures: ['hand-tracking']
132157
}).then(async (session) => {
133158
session.isImmersive = true;
134159
session.isInSession = true;
@@ -212,8 +237,12 @@
212237

213238
if (hand == 0 || hand == 2) {
214239
xrData.controllerA = controller;
240+
xrData.handRight.trigger = controller.trigger;
241+
xrData.handRight.squeeze = controller.squeeze;
215242
} else {
216243
xrData.controllerB = controller;
244+
xrData.handLeft.trigger = controller.trigger;
245+
xrData.handLeft.squeeze = controller.squeeze;
217246
}
218247
}
219248
}
@@ -336,13 +365,49 @@
336365
}
337366

338367
XRManager.prototype.getXRControllersData = function(frame, inputSources, refSpace, xrData) {
368+
xrData.handLeft.enabled = 0;
369+
xrData.handRight.enabled = 0;
370+
xrData.controllerA.enabled = 0;
371+
xrData.controllerB.enabled = 0;
372+
xrData.handLeft.frame = xrData.frameNumber;
373+
xrData.handRight.frame = xrData.frameNumber;
374+
xrData.controllerA.frame = xrData.frameNumber;
375+
xrData.controllerB.frame = xrData.frameNumber;
339376
if (!inputSources || !inputSources.length) {
340377
return;
341378
}
342379
for (var i = 0; i < inputSources.length; i++) {
343380
let inputSource = inputSources[i];
344381
// Show the input source if it has a grip space
345-
if (inputSource.gripSpace) {
382+
if (inputSource.hand) {
383+
var hand = 1;
384+
var xrHand = xrData.handLeft;
385+
if (inputSource.handedness == 'right') {
386+
hand = 2;
387+
xrHand = xrData.handRight;
388+
}
389+
xrHand.enabled = 1;
390+
xrHand.hand = hand;
391+
for (let j = 0; j < 25; j++) {
392+
let joint = null;
393+
if (inputSource.hand[j] !== null) {
394+
joint = frame.getJointPose(inputSource.hand[j], refSpace);
395+
}
396+
if (joint !== null) {
397+
xrHand.joints[j].enabled = 1;
398+
xrHand.joints[j].position[0] = joint.transform.position.x;
399+
xrHand.joints[j].position[1] = joint.transform.position.y;
400+
xrHand.joints[j].position[2] = -joint.transform.position.z;
401+
xrHand.joints[j].rotation[0] = -joint.transform.orientation.x;
402+
xrHand.joints[j].rotation[1] = -joint.transform.orientation.y;
403+
xrHand.joints[j].rotation[2] = joint.transform.orientation.z;
404+
xrHand.joints[j].rotation[3] = joint.transform.orientation.w;
405+
if (joint.radius !== null) {
406+
xrHand.joints[j].radius = joint.radius;
407+
}
408+
}
409+
}
410+
} else if (inputSource.gripSpace) {
346411
let inputPose = frame.getPose(inputSource.gripSpace, refSpace);
347412
if (inputPose) {
348413
var position = inputPose.transform.position;
@@ -521,6 +586,7 @@
521586
}
522587

523588
var xrData = this.xrData;
589+
xrData.frameNumber++;
524590

525591
for (let view of pose.views) {
526592
if (view.eye === 'left') {
@@ -544,6 +610,16 @@
544610
controllerA: xrData.controllerA,
545611
controllerB: xrData.controllerB
546612
}}));
613+
614+
document.dispatchEvent(new CustomEvent('XRControllersData', { detail: {
615+
controllerA: xrData.controllerA,
616+
controllerB: xrData.controllerB
617+
}}));
618+
619+
document.dispatchEvent(new CustomEvent('XRHandsData', { detail: {
620+
handLeft: xrData.handLeft,
621+
handRight: xrData.handRight
622+
}}));
547623

548624
if (!this.didNotifyUnity)
549625
{

Assets/WebXR/Plugins/WebGL/webxr.c

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ webxr_void_int on_start_ar_ref;
99
webxr_void_int on_start_vr_ref;
1010
webxr_void on_end_xr_ref;
1111
webxr_void_string on_xr_capabilities_ref;
12-
webxr_void_string on_webxr_data_ref;
1312

1413
void set_webxr_events(
1514
webxr_void_int _on_start_ar,
1615
webxr_void_int _on_start_vr,
1716
webxr_void _on_end_xr,
18-
webxr_void_string _on_xr_capabilities,
19-
webxr_void_string _on_webxr_data
17+
webxr_void_string _on_xr_capabilities
2018
) {
2119
on_start_ar_ref = _on_start_ar;
2220
on_start_vr_ref = _on_start_vr;
2321
on_end_xr_ref = _on_end_xr;
2422
on_xr_capabilities_ref = _on_xr_capabilities;
25-
on_webxr_data_ref = _on_webxr_data;
2623
}
2724

2825
void EMSCRIPTEN_KEEPALIVE on_start_ar(int views_count)
@@ -45,9 +42,3 @@ void EMSCRIPTEN_KEEPALIVE on_xr_capabilities(const char *display_capabilities)
4542
{
4643
on_xr_capabilities_ref(display_capabilities);
4744
}
48-
49-
// TODO: find a better way to transfer array of controllers and buttons
50-
void EMSCRIPTEN_KEEPALIVE on_webxr_data(const char *webxr_data)
51-
{
52-
on_webxr_data_ref(webxr_data);
53-
}

Assets/WebXR/Plugins/WebGL/webxr.jslib

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
/* functions called from unity */
22
mergeInto(LibraryManager.library, {
3-
InitSharedArray: function(byteOffset, length) {
4-
SharedArray = new Float32Array(buffer, byteOffset, length);
3+
InitXRSharedArray: function(byteOffset, length) {
4+
XRSharedArray = new Float32Array(buffer, byteOffset, length);
55
document.dispatchEvent(new CustomEvent('UnityLoaded', {detail: 'Ready'}));
66
},
77

8+
InitControllersArray: function(byteOffset, length) {
9+
ControllersArray = new Float32Array(buffer, byteOffset, length);
10+
},
11+
12+
InitHandsArray: function(byteOffset, length) {
13+
HandsArray = new Float32Array(buffer, byteOffset, length);
14+
},
15+
816
ListenWebXRData: function() {
917
// Listen for headset updates from webxr.jspre and load data into shared Array which we pick up in Unity.
1018
document.addEventListener('XRData', function(evt) {
@@ -14,28 +22,56 @@ mergeInto(LibraryManager.library, {
1422
var dataLength = data[key].length;
1523
if (dataLength) {
1624
for (var x = 0; x < dataLength; x++) {
17-
SharedArray[index++] = data[key][x];
25+
XRSharedArray[index++] = data[key][x];
1826
}
19-
} else {
20-
SharedArray[index++] = data[key].enabled;
21-
SharedArray[index++] = data[key].hand;
22-
SharedArray[index++] = data[key].positionX;
23-
SharedArray[index++] = data[key].positionY;
24-
SharedArray[index++] = data[key].positionZ;
25-
SharedArray[index++] = data[key].rotationX;
26-
SharedArray[index++] = data[key].rotationY;
27-
SharedArray[index++] = data[key].rotationZ;
28-
SharedArray[index++] = data[key].rotationW;
29-
SharedArray[index++] = data[key].trigger;
30-
SharedArray[index++] = data[key].squeeze;
31-
SharedArray[index++] = data[key].thumbstick;
32-
SharedArray[index++] = data[key].thumbstickX;
33-
SharedArray[index++] = data[key].thumbstickY;
34-
SharedArray[index++] = data[key].touchpad;
35-
SharedArray[index++] = data[key].touchpadX;
36-
SharedArray[index++] = data[key].touchpadY;
37-
SharedArray[index++] = data[key].buttonA;
38-
SharedArray[index++] = data[key].buttonB;
27+
}
28+
});
29+
});
30+
document.addEventListener('XRControllersData', function(evt) {
31+
var data = evt.detail;
32+
var index = 0;
33+
Object.keys(data).forEach(function (key, i) {
34+
ControllersArray[index++] = data[key].frame;
35+
ControllersArray[index++] = data[key].enabled;
36+
ControllersArray[index++] = data[key].hand;
37+
ControllersArray[index++] = data[key].positionX;
38+
ControllersArray[index++] = data[key].positionY;
39+
ControllersArray[index++] = data[key].positionZ;
40+
ControllersArray[index++] = data[key].rotationX;
41+
ControllersArray[index++] = data[key].rotationY;
42+
ControllersArray[index++] = data[key].rotationZ;
43+
ControllersArray[index++] = data[key].rotationW;
44+
ControllersArray[index++] = data[key].trigger;
45+
ControllersArray[index++] = data[key].squeeze;
46+
ControllersArray[index++] = data[key].thumbstick;
47+
ControllersArray[index++] = data[key].thumbstickX;
48+
ControllersArray[index++] = data[key].thumbstickY;
49+
ControllersArray[index++] = data[key].touchpad;
50+
ControllersArray[index++] = data[key].touchpadX;
51+
ControllersArray[index++] = data[key].touchpadY;
52+
ControllersArray[index++] = data[key].buttonA;
53+
ControllersArray[index++] = data[key].buttonB;
54+
});
55+
});
56+
document.addEventListener('XRHandsData', function(evt) {
57+
var data = evt.detail;
58+
var index = 0;
59+
Object.keys(data).forEach(function (key, i) {
60+
HandsArray[index++] = data[key].frame;
61+
HandsArray[index++] = data[key].enabled;
62+
HandsArray[index++] = data[key].hand;
63+
HandsArray[index++] = data[key].trigger;
64+
HandsArray[index++] = data[key].squeeze;
65+
for (var j = 0; j < 25; j++) {
66+
HandsArray[index++] = data[key].joints[j].enabled;
67+
HandsArray[index++] = data[key].joints[j].position[0];
68+
HandsArray[index++] = data[key].joints[j].position[1];
69+
HandsArray[index++] = data[key].joints[j].position[2];
70+
HandsArray[index++] = data[key].joints[j].rotation[0];
71+
HandsArray[index++] = data[key].joints[j].rotation[1];
72+
HandsArray[index++] = data[key].joints[j].rotation[2];
73+
HandsArray[index++] = data[key].joints[j].rotation[3];
74+
HandsArray[index++] = data[key].joints[j].radius;
3975
}
4076
});
4177
});

Assets/WebXR/Plugins/WebGL/webxr.jspre

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,3 @@ Module['WebXR'].OnXRCapabilities = function (display_capabilities) {
4141
this.OnXRCapabilitiesInternal = this.OnXRCapabilitiesInternal || Module.cwrap("on_xr_capabilities", null, ["string"]);
4242
this.OnXRCapabilitiesInternal(display_capabilities);
4343
}
44-
45-
Module['WebXR'].OnWebXRData = function (webxr_data) {
46-
this.OnWebXRDataInternal = this.OnWebXRDataInternal || Module.cwrap("on_webxr_data", null, ["string"]);
47-
this.OnWebXRDataInternal(webxr_data);
48-
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
%YAML 1.1
2+
%TAG !u! tag:unity3d.com,2011:
3+
--- !u!1 &8362309011548659104
4+
GameObject:
5+
m_ObjectHideFlags: 0
6+
m_CorrespondingSourceObject: {fileID: 0}
7+
m_PrefabInstance: {fileID: 0}
8+
m_PrefabAsset: {fileID: 0}
9+
serializedVersion: 6
10+
m_Component:
11+
- component: {fileID: 8513787482403080262}
12+
- component: {fileID: 7408311752882702317}
13+
- component: {fileID: 99441339568455309}
14+
m_Layer: 0
15+
m_Name: Cube
16+
m_TagString: Untagged
17+
m_Icon: {fileID: 0}
18+
m_NavMeshLayer: 0
19+
m_StaticEditorFlags: 0
20+
m_IsActive: 1
21+
--- !u!4 &8513787482403080262
22+
Transform:
23+
m_ObjectHideFlags: 0
24+
m_CorrespondingSourceObject: {fileID: 0}
25+
m_PrefabInstance: {fileID: 0}
26+
m_PrefabAsset: {fileID: 0}
27+
m_GameObject: {fileID: 8362309011548659104}
28+
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
29+
m_LocalPosition: {x: 0, y: 0, z: 0}
30+
m_LocalScale: {x: 1, y: 1, z: 1}
31+
m_Children: []
32+
m_Father: {fileID: 0}
33+
m_RootOrder: 0
34+
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35+
--- !u!33 &7408311752882702317
36+
MeshFilter:
37+
m_ObjectHideFlags: 0
38+
m_CorrespondingSourceObject: {fileID: 0}
39+
m_PrefabInstance: {fileID: 0}
40+
m_PrefabAsset: {fileID: 0}
41+
m_GameObject: {fileID: 8362309011548659104}
42+
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
43+
--- !u!23 &99441339568455309
44+
MeshRenderer:
45+
m_ObjectHideFlags: 0
46+
m_CorrespondingSourceObject: {fileID: 0}
47+
m_PrefabInstance: {fileID: 0}
48+
m_PrefabAsset: {fileID: 0}
49+
m_GameObject: {fileID: 8362309011548659104}
50+
m_Enabled: 1
51+
m_CastShadows: 1
52+
m_ReceiveShadows: 1
53+
m_DynamicOccludee: 1
54+
m_MotionVectors: 1
55+
m_LightProbeUsage: 1
56+
m_ReflectionProbeUsage: 1
57+
m_RayTracingMode: 2
58+
m_RenderingLayerMask: 1
59+
m_RendererPriority: 0
60+
m_Materials:
61+
- {fileID: 2100000, guid: cf55eb157040bca49b365493c72ed650, type: 2}
62+
m_StaticBatchInfo:
63+
firstSubMesh: 0
64+
subMeshCount: 0
65+
m_StaticBatchRoot: {fileID: 0}
66+
m_ProbeAnchor: {fileID: 0}
67+
m_LightProbeVolumeOverride: {fileID: 0}
68+
m_ScaleInLightmap: 1
69+
m_ReceiveGI: 1
70+
m_PreserveUVs: 0
71+
m_IgnoreNormalsForChartDetection: 0
72+
m_ImportantGI: 0
73+
m_StitchLightmapSeams: 1
74+
m_SelectedEditorRenderState: 3
75+
m_MinimumChartSize: 4
76+
m_AutoUVMaxDistance: 0.5
77+
m_AutoUVMaxAngle: 89
78+
m_LightmapParameters: {fileID: 0}
79+
m_SortingLayerID: 0
80+
m_SortingLayer: 0
81+
m_SortingOrder: 0

Assets/WebXR/Samples/Desert/Prefabs/Cube.prefab.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)