Update webxr driver; rm webvr;

This commit is contained in:
bjorn 2020-08-16 00:31:20 -06:00
parent 1b5e7b2203
commit 65470f1e0e
15 changed files with 348 additions and 563 deletions

View File

@ -20,7 +20,6 @@ option(LOVR_ENABLE_JSON "Bundle with lua-cjson" ON)
option(LOVR_USE_LUAJIT "Use LuaJIT instead of Lua" ON)
option(LOVR_USE_OPENVR "Enable the OpenVR backend for the headset module" ON)
option(LOVR_USE_OPENXR "Enable the OpenXR backend for the headset module" OFF)
option(LOVR_USE_WEBVR "Enable the WebVR backend for the headset module" OFF)
option(LOVR_USE_WEBXR "Enable the WebXR backend for the headset module" OFF)
option(LOVR_USE_OCULUS "Enable the LibOVR backend for the headset module (be sure to also set LOVR_OCULUS_PATH to point to the Oculus SDK)" OFF)
option(LOVR_USE_VRAPI "Enable the VrApi backend for the headset module" OFF)
@ -50,16 +49,8 @@ if(EMSCRIPTEN)
"-s FULL_ES2=1 "
"-s FULL_ES3=1 "
"-s FORCE_FILESYSTEM=1 "
"-s \"EXPORTED_FUNCTIONS=[ "
"'_main','_lovrDestroy',"
"'_lovrCanvasCreateFromHandle',"
"'_lovrGraphicsSetCamera',"
"'_webvr_onAnimationFrame',"
"'_mat4_set','_mat4_identity','_mat4_invert','_mat4_multiply','_mat4_rotateQuat','_mat4_transform','_mat4_transformDirection',"
"'_quat_fromMat4','_quat_getAngleAxis'"
"]\" "
"-s \"EXPORTED_FUNCTIONS=['_main','_lovrDestroy','_lovrCanvasCreateFromHandle','_lovrCanvasDestroy','_lovrGraphicsSetCamera']\" "
"-s \"EXTRA_EXPORTED_RUNTIME_METHODS=['getValue','setValue']\" "
"--js-library \"${CMAKE_CURRENT_SOURCE_DIR}/src/resources/webvr.js\" "
"--js-library \"${CMAKE_CURRENT_SOURCE_DIR}/src/resources/webxr.js\" "
"--shell-file \"${CMAKE_CURRENT_SOURCE_DIR}/src/resources/lovr.html\""
)
@ -69,7 +60,6 @@ if(EMSCRIPTEN)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LOVR_EMSCRIPTEN_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LOVR_EMSCRIPTEN_FLAGS}")
set(CMAKE_EXECUTABLE_SUFFIX ".html")
set(LOVR_USE_WEBVR ON)
set(LOVR_USE_WEBXR ON)
set(LOVR_USE_OPENVR OFF)
elseif(ANDROID)
@ -342,7 +332,6 @@ set(LOVR_SRC
src/main.c
src/core/arr.c
src/core/fs.c
src/core/maf.c
src/core/map.c
src/core/png.c
src/core/ref.c
@ -490,10 +479,6 @@ if(LOVR_ENABLE_HEADSET)
add_definitions(-DLOVR_USE_PICO)
target_sources(lovr PRIVATE src/modules/headset/headset_pico.c)
endif()
if(LOVR_USE_WEBVR)
add_definitions(-DLOVR_USE_WEBVR)
target_sources(lovr PRIVATE src/modules/headset/headset_webvr.c)
endif()
if(LOVR_USE_WEBXR)
add_definitions(-DLOVR_USE_WEBXR)
target_sources(lovr PRIVATE src/modules/headset/headset_webxr.c)

View File

@ -21,7 +21,7 @@ You can use LÖVR to easily create VR experiences without much setup or programm
Features
---
- **Cross-Platform** - Runs on Windows, Mac, Linux, Android, and on the web using WebAssembly and WebVR.
- **Cross-Platform** - Runs on Windows, Mac, Linux, Android, and on the web using WebAssembly and WebXR.
- **Cross-Device** - Supports Vive/Index, Oculus Rift/Go/Quest, Pico, Windows MR, and has a VR simulator.
- **Beginner-friendly** - Simple VR scenes can be created in just a few lines of Lua.
- **Fast** - Writen in C99 and scripted with LuaJIT, includes optimized single-pass stereo rendering.

View File

@ -6,7 +6,6 @@ SRC += src/main.c
endif
SRC += src/core/arr.c
SRC += src/core/fs.c
SRC += src/core/maf.c
SRC += src/core/map.c
ifneq (@(PICO),y)
SRC += src/core/os_$(PLATFORM).c
@ -30,7 +29,6 @@ SRC_@(HEADSET)@(OPENXR) += src/modules/headset/headset_openxr.c
SRC_@(HEADSET)@(OCULUS) += src/modules/headset/headset_oculus.c
SRC_@(HEADSET)@(VRAPI) += src/modules/headset/headset_vrapi.c
SRC_@(HEADSET)@(PICO) += src/modules/headset/headset_pico.c
SRC_@(HEADSET)@(WEBVR) += src/modules/headset/headset_webvr.c
SRC_@(HEADSET)@(WEBXR) += src/modules/headset/headset_webxr.c
SRC_@(HEADSET)@(LEAP) += src/modules/headset/headset_leap.c
SRC_@(MATH) += src/modules/math/*.c

View File

@ -265,12 +265,7 @@ ifeq ($(PLATFORM),web)
LDFLAGS += -s FULL_ES3
LDFLAGS += -s GL_PREINITIALIZED_CONTEXT
LDFLAGS += -s FORCE_FILESYSTEM
LDFLAGS += -s EXPORTED_FUNCTIONS="[
LDFLAGS += '_main',
LDFLAGS += '_lovrCanvasCreateFromHandle','_lovrGraphicsSetCamera',
LDFLAGS += '_mat4_set','_mat4_identity','_mat4_invert','_mat4_multiply','_mat4_rotateQuat',
LDFLAGS += '_mat4_transform','_mat4_transformDirection'
LDFLAGS += ]"
LDFLAGS += -s EXPORTED_FUNCTIONS="['_main','_lovrCanvasCreateFromHandle','_lovrCanvasDestroy','_lovrGraphicsSetCamera']"
LDFLAGS_@(WEBXR) += --js-library $(ROOT)/src/resources/webxr.js
LDFLAGS += --shell-file $(ROOT)/src/resources/lovr.html
CFLAGS_@(THREAD) += -s USE_PTHREADS=1

View File

@ -16,7 +16,6 @@ StringEntry HeadsetDrivers[] = {
[DRIVER_OPENXR] = ENTRY("openxr"),
[DRIVER_VRAPI] = ENTRY("vrapi"),
[DRIVER_PICO] = ENTRY("pico"),
[DRIVER_WEBVR] = ENTRY("webvr"),
[DRIVER_WEBXR] = ENTRY("webxr"),
{ 0 }
};

View File

@ -1,2 +0,0 @@
#define MAF_EXPORT
#include "maf.h"

View File

@ -53,7 +53,9 @@ void lovrEventDestroy() {
for (size_t i = 0; i < state.events.length; i++) {
Event* event = &state.events.data[i];
switch (event->type) {
#if LOVR_ENABLE_THREAD
case EVENT_THREAD_ERROR: lovrRelease(Thread, event->data.thread.thread); break;
#endif
case EVENT_CUSTOM:
for (uint32_t j = 0; j < event->data.custom.count; j++) {
lovrVariantDestroy(&event->data.custom.data[j]);

View File

@ -36,9 +36,6 @@ bool lovrHeadsetInit(HeadsetDriver* drivers, size_t count, float offset, uint32_
#ifdef LOVR_USE_PICO
case DRIVER_PICO: interface = &lovrHeadsetPicoDriver; break;
#endif
#ifdef LOVR_USE_WEBVR
case DRIVER_WEBVR: interface = &lovrHeadsetWebVRDriver; break;
#endif
#ifdef LOVR_USE_WEBXR
case DRIVER_WEBXR: interface = &lovrHeadsetWebXRDriver; break;
#endif

View File

@ -18,7 +18,6 @@ typedef enum {
DRIVER_OPENXR,
DRIVER_VRAPI,
DRIVER_PICO,
DRIVER_WEBVR,
DRIVER_WEBXR
} HeadsetDriver;
@ -150,7 +149,6 @@ extern HeadsetInterface lovrHeadsetOpenVRDriver;
extern HeadsetInterface lovrHeadsetOpenXRDriver;
extern HeadsetInterface lovrHeadsetVrApiDriver;
extern HeadsetInterface lovrHeadsetPicoDriver;
extern HeadsetInterface lovrHeadsetWebVRDriver;
extern HeadsetInterface lovrHeadsetWebXRDriver;
extern HeadsetInterface lovrHeadsetDesktopDriver;
extern HeadsetInterface lovrHeadsetLeapMotionDriver;

View File

@ -1,78 +0,0 @@
#include "headset/headset.h"
#include "graphics/graphics.h"
#include <stdbool.h>
#include <stdint.h>
// Provided by resources/webvr.js
extern bool webvr_init(float offset, uint32_t msaa);
extern void webvr_destroy(void);
extern bool webvr_getName(char* name, size_t length);
extern HeadsetOrigin webvr_getOriginType(void);
extern double webvr_getDisplayTime(void);
extern void webvr_getDisplayDimensions(uint32_t* width, uint32_t* height);
extern const float* webvr_getDisplayMask(uint32_t* count);
extern uint32_t webvr_getViewCount(void);
extern bool webvr_getViewPose(uint32_t view, float* position, float* orientation);
extern bool webvr_getViewAngles(uint32_t view, float* left, float* right, float* up, float* down);
extern void webvr_getClipDistance(float* near, float* far);
extern void webvr_setClipDistance(float near, float far);
extern void webvr_getBoundsDimensions(float* width, float* depth);
extern const float* webvr_getBoundsGeometry(uint32_t* count);
extern bool webvr_getPose(Device device, float* position, float* orientation);
extern bool webvr_getVelocity(Device device, float* velocity, float* angularVelocity);
extern bool webvr_isDown(Device device, DeviceButton button, bool* down, bool* changed);
extern bool webvr_isTouched(Device device, DeviceButton button, bool* touched);
extern bool webvr_getAxis(Device device, DeviceAxis axis, float* value);
extern bool webvr_vibrate(Device device, float strength, float duration, float frequency);
extern struct ModelData* webvr_newModelData(Device device, bool animated);
extern bool webvr_animate(Device device, struct Model* model);
extern void webvr_update(float dt);
static struct {
void (*renderCallback)(void*);
void* renderData;
} state;
void webvr_onAnimationFrame(float* leftView, float* rightView, float* leftProjection, float* rightProjection) {
Camera camera = { .canvas = NULL, .stereo = true };
memcpy(camera.projection[0], leftProjection, 16 * sizeof(float));
memcpy(camera.projection[1], rightProjection, 16 * sizeof(float));
memcpy(camera.viewMatrix[0], leftView, 16 * sizeof(float));
memcpy(camera.viewMatrix[1], rightView, 16 * sizeof(float));
lovrGraphicsSetCamera(&camera, true);
state.renderCallback(state.renderData);
lovrGraphicsSetCamera(NULL, false);
}
void webvr_renderTo(void (*callback)(void*), void* userdata) {
state.renderCallback = callback;
state.renderData = userdata;
}
HeadsetInterface lovrHeadsetWebVRDriver = {
.driverType = DRIVER_WEBVR,
.init = webvr_init,
.destroy = webvr_destroy,
.getName = webvr_getName,
.getOriginType = webvr_getOriginType,
.getDisplayTime = webvr_getDisplayTime,
.getDisplayDimensions = webvr_getDisplayDimensions,
.getDisplayMask = webvr_getDisplayMask,
.getViewCount = webvr_getViewCount,
.getViewPose = webvr_getViewPose,
.getViewAngles = webvr_getViewAngles,
.getClipDistance = webvr_getClipDistance,
.setClipDistance = webvr_setClipDistance,
.getBoundsDimensions = webvr_getBoundsDimensions,
.getBoundsGeometry = webvr_getBoundsGeometry,
.getPose = webvr_getPose,
.getVelocity = webvr_getVelocity,
.isDown = webvr_isDown,
.isTouched = webvr_isTouched,
.getAxis = webvr_getAxis,
.vibrate = webvr_vibrate,
.newModelData = webvr_newModelData,
.animate = webvr_animate,
.renderTo = webvr_renderTo,
.update = webvr_update
};

View File

@ -27,7 +27,7 @@ extern void webxr_renderTo(void (*callback)(void*), void* userdata);
extern void webxr_update(float dt);
HeadsetInterface lovrHeadsetWebXRDriver = {
.driverType = DRIVER_WEBVR,
.driverType = DRIVER_WEBXR,
.init = webxr_init,
.destroy = webxr_destroy,
.getName = webxr_getName,

View File

@ -99,7 +99,7 @@ function lovr.boot()
debug = false
},
headset = {
drivers = { 'leap', 'openxr', 'oculus', 'vrapi', 'pico', 'openvr', 'webxr', 'webvr', 'desktop' },
drivers = { 'leap', 'openxr', 'oculus', 'vrapi', 'pico', 'openvr', 'webxr', 'desktop' },
offset = 1.7,
msaa = 4
},

View File

@ -56,8 +56,8 @@
});
var Module = window.Module = {
arguments: ['./'],
preRun: [findDisplay],
arguments: [],
preRun: [],
postRun: [],
print: console.log.bind(console),
printErr: console.error.bind(console),
@ -66,29 +66,51 @@
preinitializedWebGLContext: context
};
function findDisplay() {
if (navigator.getVRDisplays) {
Module.addRunDependency('lovrDisplay');
navigator.getVRDisplays().
then(function(displays) {
Module.lovrDisplay = displays[0];
container.appendChild(button);
}).finally(function() {
Module.removeRunDependency('lovrDisplay');
});
}
// To run a LÖVR project on this page, create a .lovr (zip) file of its folder and serve it
// alongside the HTML file. Then set the 'project' variable below to the project's filename.
// This downloads the .lovr file into the virtual filesystem and adds it as a virtual command
// line argument before booting up LÖVR.
// Example: var project = 'app.lovr';
var project = null;
if (project) {
Module.arguments.push(project);
Module.preRun.push(function() {
Module.FS_createPreloadedFile('/', project, project, true, false);
});
}
button.addEventListener('click', function() {
if (Module.lovrDisplay && Module.lovrDisplay.capabilities.canPresent) {
var eventName = Module.lovrDisplay.isPresenting ? 'lovr.exitvr' : 'lovr.entervr';
window.dispatchEvent(new CustomEvent(eventName));
}
});
// If WebXR is supported and immersive sessions are supported, add a button to the DOM that
// controls starting/stopping the immersive session.
if (navigator.xr) {
navigator.xr.isSessionSupported('immersive-vr').then(function(supported) {
if (!supported) return;
window.addEventListener('vrdisplaypresentchange', function() {
button.textContent = Module.lovrDisplay.isPresenting ? 'Exit VR' : 'Enter VR';
});
container.appendChild(button);
var active = false;
function onEnter() {
active = true;
button.textContent = 'Exit VR';
}
function onExit() {
active = false;
button.textContent = 'Enter VR';
}
button.addEventListener('click', function() {
if (!active) {
Module.lovr.enterVR().then(function(session) {
session.addEventListener('end', onExit);
onEnter();
});
} else {
Module.lovr.exitVR().then(onExit);
}
}
});
}
</script>
{{{ SCRIPT }}}

View File

@ -1,336 +0,0 @@
var LibraryLOVR = {
$webvr: {
buttonMap: {
'OpenVR Gamepad': [1, 1, null, 0, 2],
'Oculus Touch (Left)': [1, 1, 0, null, 2, null, null, null, 3, 4],
'Oculus Touch (Right)': [1, 1, 0, null, 2, null, 3, 4, null, null],
'Spatial Controller (Spatial Interaction Source) 045E-065D': [0, 0, 1, 3, 2, 4]
},
refreshGamepads: function(event) {
if (event.gamepad.hand) {
var device = ({
'left': C.DEVICE_HAND_LEFT,
'right': C.DEVICE_HAND_RIGHT
})[event.gamepad.hand];
if (device) {
webvr.gamepads[device] = event.gamepad;
webvr.poses[device] = event.gamepad.pose;
}
}
}
},
webvr_init: function(offset, msaa) {
if (webvr.initialized || !Module.lovrDisplay) {
return false;
}
var a, b, c, d, e, canvas, display;
webvr.initialized = true;
webvr.display = display = Module.lovrDisplay;
webvr.display.depthNear = .1;
webvr.display.depthFar = 100;
webvr.canvas = canvas = Module.canvas;
webvr.frameData = new VRFrameData();
webvr.gamepads = [];
webvr.poses = [];
webvr.offset = offset;
webvr.poseTransform = Module._malloc(64);
webvr.matA = a = Module._malloc(64);
webvr.matB = b = Module._malloc(64);
webvr.matC = c = Module._malloc(64);
webvr.matD = d = Module._malloc(64);
webvr.matE = e = Module._malloc(64);
webvr.width = display.getEyeParameters('left').renderWidth * 2;
webvr.height = display.getEyeParameters('left').renderHeight;
Browser.setCanvasSize(webvr.width, webvr.height);
webvr.onentervr = function() {
if (!display.isPresenting) {
display.requestPresent([{ source: canvas }]);
}
};
webvr.onexitvr = function() {
if (display.isPresenting) {
display.exitPresent();
}
};
webvr.frameId = display.requestAnimationFrame(function onAnimationFrame() {
webvr.frameId = display.requestAnimationFrame(onAnimationFrame);
display.getFrameData(webvr.frameData);
webvr.poses[0] = webvr.frameData.pose;
if (webvr.display.stageParameters && webvr.display.stageParameters.sittingToStandingTransform) {
HEAPF32.set(webvr.display.stageParameters.sittingToStandingTransform, webvr.poseTransform >> 2);
} else {
Module._mat4_identity(webvr.poseTransform);
HEAPF32[webvr.poseTransform >> 2 + 13] = webvr.offset;
}
Module._mat4_set(e, webvr.poseTransform);
Module._mat4_invert(e);
HEAPF32.set(webvr.frameData.leftViewMatrix, a >> 2);
HEAPF32.set(webvr.frameData.rightViewMatrix, b >> 2);
HEAPF32.set(webvr.frameData.leftProjectionMatrix, c >> 2);
HEAPF32.set(webvr.frameData.rightProjectionMatrix, d >> 2);
Module._mat4_multiply(a, e);
Module._mat4_multiply(b, e);
Module._webvr_onAnimationFrame(a, b, c, d);
if (display.isPresenting) {
display.submitFrame();
}
});
window.addEventListener('lovr.entervr', webvr.onentervr);
window.addEventListener('lovr.exitvr', webvr.onexitvr);
window.addEventListener('vrdisplaypresentchange', webvr.onvrdisplaypresentchange);
window.addEventListener('gamepadconnected', webvr.refreshGamepads);
window.addEventListener('gamepaddisconnected', webvr.refreshGamepads);
return true;
},
webvr_destroy: function() {
if (!webvr.initialized) {
return;
}
webvr.initialized = false;
Module._free(webvr.poseTransform);
Module._free(webvr.matA);
Module._free(webvr.matB);
Module._free(webvr.matC);
Module._free(webvr.matD);
Module._free(webvr.matE);
window.removeEventListener('lovr.entervr', webvr.onentervr);
window.removeEventListener('lovr.exitvr', webvr.onexitvr);
window.removeEventListener('vrdisplaypresentchange', webvr.onvrdisplaypresentchange);
window.removeEventListener('gamepadconnected', webvr.refreshGamepads);
window.removeEventListener('gamepaddisconnected', webvr.refreshGamepads);
if (webvr.frameId) {
webvr.display.cancelAnimationFrame(webvr.frameId);
}
},
webvr_getName: function() {
return false;
},
webvr_getOriginType: function() {
return webvr.display.stageParameters ? C.ORIGIN_FLOOR : C.ORIGIN_HEAD;
},
webvr_getDisplayTime: function() {
return webvr.frameData.timestamp / 1000;
},
webvr_getDisplayDimensions: function(width, height) {
HEAPU32[width >> 2] = webvr.width;
HEAPU32[height >> 2] = webvr.height;
},
webvr_getDisplayMask: function(count) {
HEAPU32[count >> 2] = 0;
return 0;
},
webvr_getViewCount: function() {
return 2;
},
webvr_getViewPose: function(view, position, orientation) {
return false; // TODO
},
webvr_getViewAngles: function(view, left, right, up, down) {
return false; // TODO
},
webvr_getClipDistance: function(clipNear, clipFar) {
HEAPF32[clipNear >> 2] = webvr.display.depthNear;
HEAPF32[clipFar >> 2] = webvr.display.depthFar;
},
webvr_setClipDistance: function(clipNear, clipFar) {
webvr.display.depthNear = clipNear;
webvr.display.depthFar = clipFar;
},
webvr_getBoundsDimensions: function(width, depth) {
var stage = webvr.display.stageParameters;
if (stage) {
HEAPF32[width >> 2] = stage.sizeX;
HEAPF32[depth >> 2] = stage.sizeZ;
} else {
HEAPF32[width >> 2] = HEAPF32[depth >> 2] = 0;
}
},
webvr_getBoundsGeometry: function(count) {
HEAP32[count >> 2] = 0;
return 0;
},
webvr_getPose: function(device, position, orientation) {
var pose = webvr.poses[device];
if (!pose) { return false; }
if (pose.position) {
HEAPF32.set(pose.position, position >> 2);
Module._mat4_transform(webvr.poseTransform, position);
} else {
HEAPF32.fill(0, position >> 2, position >> 2 + 3);
}
if (pose.orientation) {
HEAPF32.set(pose.orientation, orientation >> 2);
Module._mat4_set(webvr.matA, webvr.poseTransform);
Module._mat4_rotateQuat(webvr.matA, orientation);
Module._quat_fromMat4(orientation, webvr.matA);
} else {
HEAPF32.fill(0, orientation >> 2, orientation >> 2 + 4);
}
return true;
},
webvr_getVelocity: function(device, velocity, angularVelocity) {
var pose = webvr.poses[device];
if (!pose) { return false; }
if (pose.linearVelocity) {
HEAPF32.set(pose.linearVelocity, velocity >> 2);
Module._mat4_transformDirection(webvr.poseTransform, velocity);
} else {
HEAPF32.fill(0, velocity >> 2, velocity >> 2 + 3);
}
if (pose.angularVelocity) {
HEAPF32.set(pose.angularVelocity, angularVelocity >> 2);
Module._mat4_transformDirection(webvr.poseTransform, angularVelocity);
} else {
HEAPF32.fill(0, angularVelocity >> 2, angularVelocity >> 2 + 3);
}
return true;
},
webvr_isDown: function(device, button, down, changed) {
var gamepad = webvr.gamepads[device];
if (!gamepad || !gamepad.id || !webvr.buttonMap[gamepad.id] || !webvr.buttonMap[gamepad.id][button]) {
return false;
}
HEAPF32[down >> 2] = gamepad.buttons[webvr.buttonMap[gamepad.id][button]].pressed;
HEAPF32[changed >> 2] = false; // TODO
return true;
},
webvr_isTouched: function(device, button, touched) {
var gamepad = webvr.gamepads[device];
if (!gamepad || !gamepad.id || !webvr.buttonMap[gamepad.id] || !webvr.buttonMap[gamepad.id][button]) {
return false;
}
HEAPF32[touched >> 2] = gamepad.buttons[webvr.buttonMap[gamepad.id][button]].touched;
return true;
},
webvr_getAxis: function(device, axis, value) {
var gamepad = webvr.gamepads[device];
if (!gamepad) {
return false;
}
if (gamepad.id.startsWith('OpenVR')) {
switch (axis) {
case C.AXIS_TRIGGER: HEAPF32[value >> 2] = gamepad.buttons[1].value; return true;
case C.AXIS_TOUCHPAD:
HEAPF32[value >> 2 + 0] = gamepad.axes[0];
HEAPF32[value >> 2 + 1] = gamepad.axes[1];
return true;
default: return false;
}
} else if (gamepad.id.startsWith('Oculus')) {
switch (axis) {
case C.AXIS_TRIGGER: HEAPF32[value >> 2] = gamepad.buttons[1].value; return true;
case C.AXIS_GRIP: HEAPF32[value >> 2] = gamepad.buttons[2].value; return true;
case C.AXIS_THUMBSTICK:
HEAPF32[value >> 2 + 0] = gamepad.axes[0];
HEAPF32[value >> 2 + 1] = gamepad.axes[1];
return true;
default: return false;
}
} else if (gamepad.id.startsWith('Spatial Controller')) {
switch (axis) {
case C.AXIS_TRIGGER: HEAPF32[value >> 2] = gamepad.buttons[0].value; return true;
case C.AXIS_THUMBSTICK:
HEAPF32[value >> 2 + 0] = gamepad.axes[0];
HEAPF32[value >> 2 + 1] = gamepad.axes[1];
return true;
case C.AXIS_TOUCHPAD:
HEAPF32[value >> 2 + 0] = gamepad.axes[2];
HEAPF32[value >> 2 + 1] = gamepad.axes[3];
return true;
default: return false;
}
}
return false;
},
webvr_vibrate: function(device, strength, duration, frequency) {
var gamepad = webvr.gamepads[device];
if (gamepad && gamepad.hapticActuators && gamepad.hapticActuators[0]) {
gamepad.hapticActuators[0].pulse(strength, duration * 1000);
return true;
}
return false;
},
webvr_newModelData: function(device, animated) {
return C.NULL;
},
webvr_animate: function(device, model) {
return false;
},
webvr_update: function(dt) {
//
},
$C: {
NULL: 0,
// HeadsetOrigin
ORIGIN_HEAD: 0,
ORIGIN_FLOOR: 1,
// Device
DEVICE_HAND_LEFT: 0,
DEVICE_HAND_RIGHT: 1,
// DeviceAxis
AXIS_TRIGGER: 0,
AXIS_THUMBSTICK: 1,
AXIS_TOUCHPAD: 2,
AXIS_PINCH: 3,
AXIS_GRIP: 4
}
};
autoAddDeps(LibraryLOVR, '$webvr');
autoAddDeps(LibraryLOVR, '$C');
mergeInto(LibraryManager.library, LibraryLOVR);

View File

@ -6,92 +6,187 @@ var webxr = {
return false;
}
state.layer = null;
state.sessions = {};
state.session = null;
state.frame = null;
state.canvas = null;
state.camera = null;
state.clipNear = .1;
state.clipFar = 1000.0;
state.referenceSpaceType = null;
state.renderCallback = null;
state.renderUserdata = null;
state.animationFrame = null;
state.displayTime = null;
state.camera = Module._malloc(264 /* sizeof(Camera) */);
state.boundsGeometry = 0; /* NULL */
state.boundsGeometryCount = 0;
navigator.xr.requestSession('inline').then(function(session) {
state.referenceSpaceType = 'viewer';
session.requestReferenceSpace(state.referenceSpaceType).then(function(referenceSpace) {
state.session = session;
state.layer = new XRWebGLLayer(session, Module.preinitializedWebGLContext);
var mappings = {
'oculus-touch-left': [0, 3, null, 1, null, null, null, 4, 5],
'oculus-touch-right': [0, 3, null, 1, null, 4, 5, null, null],
'valve-index': [0, 3, 2, 1, null, 4, null, 4, null],
'microsoft-mixed-reality': [0, 3, 2, 1],
'htc-vive': [0, null, 2, 1],
'generic-trigger': [0],
'generic-trigger-touchpad': [0, null, 2],
'generic-trigger-thumbstick': [0, 3],
'generic-trigger-touchpad-thumbstick': [0, 3, 2],
'generic-trigger-squeeze': [0, null, null, 1],
'generic-trigger-squeeze-touchpad': [0, null, 2, 1],
'generic-trigger-squeeze-touchpad-thumbstick': [0, 3, 2, 1],
'generic-trigger-squeeze-thumbstick': [0, 3, null, 1],
'generic-hand-select': [0],
};
var framebuffer = 0;
function startSession(mode, options) {
return navigator.xr.requestSession(mode, options).then(function(session) {
var spaces = {
'inline': ['viewer'],
'immersive-vr': ['bounded-floor', 'local-floor']
};
if (state.layer.framebuffer) {
framebuffer = GL.getNewId(GL.framebuffers);
GL.framebuffers[framebuffer] = state.layer.framebuffer;
}
// This is confusing but it basically keeps trying to request successive reference spaces
// until one succeeds. $space is a promise that resolves to a reference space
var $space = spaces[mode].reduce(function(chain, spaceType) {
return chain.catch(function() {
session.spaceType = spaceType;
return session.requestReferenceSpace(spaceType);
});
}, Promise.reject());
var sizeof_CanvasFlags = 16;
var flags = Module.stackAlloc(sizeof_CanvasFlags);
HEAPU8.fill(0, flags, flags + sizeof_CanvasFlags); // memset(&flags, 0, sizeof(CanvasFlags));
var width = state.layer.framebufferWidth;
var height = state.layer.framebufferHeight;
state.canvas = Module['_lovrCanvasCreateFromHandle'](width, height, flags, framebuffer, 0, 0, 1, true);
Module.stackRestore(flags);
session.inputSources = [];
var sizeof_Camera = 264;
state.camera = Module._malloc(sizeof_Camera);
HEAPU32[(state.camera + 4) >> 2] = state.canvas; // state.camera.canvas = state.canvas
return $space.then(function(space) {
state.session = session;
state.sessions[mode] = session;
session.layer = new XRWebGLLayer(session, Module.preinitializedWebGLContext);
session.updateRenderState({
baseLayer: session.layer,
inlineVerticalFieldOfView: mode === 'inline' ? (67.0 * Math.PI / 180.0) : undefined
});
session.updateRenderState({
baseLayer: state.layer
});
state.animationFrame = session.requestAnimationFrame(function onFrame(t, frame) {
state.animationFrame = session.requestAnimationFrame(onFrame);
state.displayTime = t;
state.frame = frame;
var views = frame.getViewerPose(referenceSpace).views;
var stereo = views.length > 1;
HEAPU8[state.camera + 0] = stereo; // camera.stereo = stereo
var matrices = (state.camera + 8) >> 2;
HEAPF32.set(views[0].transform.inverse.matrix, matrices + 0);
HEAPF32.set(views[0].projectionMatrix, matrices + 32);
if (stereo) {
HEAPF32.set(views[1].transform.inverse.matrix, matrices + 16);
HEAPF32.set(views[1].projectionMatrix, matrices + 48);
if (session.spaceType.includes('floor')) {
session.space = space;
} else {
session.space = space.getOffsetReferenceSpace(new XRRigidTransform({ y: -offset }));
}
Module['_lovrGraphicsSetCamera'](state.camera, true);
session.framebufferId = 0;
if (state.renderCallback) {
if (session.layer.framebuffer) {
session.framebufferId = GL.getNewId(GL.framebuffers);
GL.framebuffers[session.framebufferId] = session.layer.framebuffer;
}
var sizeof_CanvasFlags = 16;
var flags = Module.stackAlloc(sizeof_CanvasFlags);
HEAPU8.fill(0, flags, flags + sizeof_CanvasFlags); // memset(&flags, 0, sizeof(CanvasFlags));
HEAPU8[flags + 12] = mode === 'inline' ? 0 : 1; // flags.stereo
var width = session.layer.framebufferWidth;
var height = session.layer.framebufferHeight;
session.canvas = Module['_lovrCanvasCreateFromHandle'](width, height, flags, session.framebufferId, 0, 0, 1, true);
Module.stackRestore(flags);
session.animationFrame = session.requestAnimationFrame(function onFrame(t, frame) {
session.animationFrame = session.requestAnimationFrame(onFrame);
session.displayTime = t;
session.frame = frame;
session.viewer = frame.getViewerPose(session.space);
if (!state.renderCallback) return;
var views = session.viewer.views;
var stereo = views.length > 1;
var matrices = (state.camera + 8) >> 2;
HEAPU8[state.camera + 0] = stereo; // camera.stereo = stereo
HEAPU32[(state.camera + 4) >> 2] = session.canvas; // camera.canvas = session.canvas
HEAPF32.set(views[0].transform.inverse.matrix, matrices + 0);
HEAPF32.set(views[0].projectionMatrix, matrices + 32);
if (stereo) {
HEAPF32.set(views[1].transform.inverse.matrix, matrices + 16);
HEAPF32.set(views[1].projectionMatrix, matrices + 48);
}
Module['_lovrGraphicsSetCamera'](state.camera, true);
Module['dynCall_vi'](state.renderCallback, state.renderUserdata);
}
Module['_lovrGraphicsSetCamera'](0, false);
});
Module['_lovrGraphicsSetCamera'](0, false);
session.addEventListener('inputsourceschange', function(event) {
session.inputSources.forEach(function(inputSource, i) {
if (event.removed.includes(inputSource)) {
session.inputSources[i] = null;
}
});
event.added.forEach(function(inputSource) {
if (inputSource.handedness === 'left') {
session.inputSources[1 /* DEVICE_HAND_LEFT */] = inputSource;
} else if (inputSource.handedness === 'right') {
session.inputSources[2 /* DEVICE_HAND_RIGHT */] = inputSource;
}
for (var i = 0; i < inputSource.profiles.length; i++) {
var profile = inputSource.profiles[i];
// So far Oculus touch controllers are the only "meaningfully handed" controllers
// If more appear then a more general approach should be used
if (profile === 'oculus-touch') {
profile = profile + '-' + inputSource.handedness;
}
if (mappings[profile]) {
inputSource.mapping = mappings[profile];
break;
}
}
});
});
session.addEventListener('end', function() {
delete state.sessions[session.mode];
if (session.canvas) {
Module['_lovrCanvasDestroy'](session.canvas);
Module._free(session.canvas - 4);
}
if (session.framebufferId) {
GL.framebuffers[session.framebufferId].name = 0;
GL.framebuffers[session.framebufferId] = null;
}
// If the immersive session ends (for any reason), switch back to the inline session
if (session.mode === 'immersive-vr') {
state.session = state.sessions.inline;
}
});
return session;
});
});
});
}
Module.lovr = Module.lovr || {};
Module.lovr.enterVR = function() {
return startSession('immersive-vr', {
requiredFeatures: ['local-floor'],
optionalFeatures: ['bounded-floor']
});
};
Module.lovr.exitVR = function() {
return (state.session && state.session.mode === 'immersive-vr') ? state.session.end() : Promise.resolve();
};
startSession('inline');
return true;
},
webxr_destroy: function() {
function cleanup() {
// TODO release canvas
Module._free(state.camera|0);
for (mode in state.sessions) {
state.sessions[mode].end();
}
if (state.session) {
state.session.cancelAnimationFrame(state.animationFrame);
state.session.end().then(cleanup);
} else {
cleanup();
}
Module._free(state.camera|0);
Module._free(state.boundsGeometry|0);
},
webxr_getName: function(name, size) {
@ -99,20 +194,22 @@ var webxr = {
},
webxr_getOriginType: function() {
if (state.referenceSpaceType === 'local-floor' || state.referenceSpaceType === 'bounded-floor') {
return 1; /* ORIGIN_FLOOR */
}
return 0; /* ORIGIN_HEAD */
if (!state.session) return 0;
return state.session.spaceType.includes('floor') ? 1 /* ORIGIN_FLOOR */ : 0 /* ORIGIN_HEAD */;
},
webxr_getDisplayTime: function() {
return state.displayTime;
return state.session ? (state.session.displayTime / 1000.0) : 0;
},
webxr_getDisplayDimensions: function(width, height) {
HEAPU32[width >> 2] = state.layer.framebufferWidth;
HEAPU32[height >> 2] = state.layer.framebufferHeight;
if (state.session) {
HEAPU32[width >> 2] = state.session.layer.framebufferWidth;
HEAPU32[height >> 2] = state.session.layer.framebufferHeight;
} else {
HEAPU32[width >> 2] = 0;
HEAPU32[height >> 2] = 0;
}
},
webxr_getDisplayFrequency: function() {
@ -124,34 +221,37 @@ var webxr = {
},
webxr_getViewCount: function() {
if (!state.frame) {
if (!state.session) {
return 0;
}
return getViewerPose(state.frame).views.length;
return state.session.viewer.views.length;
},
webxr_getViewPose: function(index, position, orientation) {
if (!state.frame) {
if (!state.session || !state.session.viewer) {
return false;
}
var view = getViewerPose(state.frame).views[index];
if (view) {
HEAPF32[position >> 2 + 0] = view.transform.position.x;
HEAPF32[position >> 2 + 1] = view.transform.position.y;
HEAPF32[position >> 2 + 2] = view.transform.position.z;
HEAPF32[position >> 2 + 3] = view.transform.position.w;
HEAPF32[orientation >> 2 + 0] = view.transform.orientation.x;
HEAPF32[orientation >> 2 + 1] = view.transform.orientation.y;
HEAPF32[orientation >> 2 + 2] = view.transform.orientation.z;
HEAPF32[orientation >> 2 + 3] = view.transform.orientation.w;
var view = state.session.viewer.views[index];
if (state.session.viewer.views[index]) {
var transform = view.transform;
HEAPF32[position >> 2 + 0] = transform.position.x;
HEAPF32[position >> 2 + 1] = transform.position.y;
HEAPF32[position >> 2 + 2] = transform.position.z;
HEAPF32[position >> 2 + 3] = transform.position.w;
HEAPF32[orientation >> 2 + 0] = transform.orientation.x;
HEAPF32[orientation >> 2 + 1] = transform.orientation.y;
HEAPF32[orientation >> 2 + 2] = transform.orientation.z;
HEAPF32[orientation >> 2 + 3] = transform.orientation.w;
return true;
}
return false;
},
webxr_getViewAngles: function(index, left, right, up, down) {
return false;
return false; // TODO
},
webxr_getClipDistance: function(clipNear, clipFar) {
@ -160,6 +260,7 @@ var webxr = {
},
webxr_setClipDistance: function(clipNear, clipFar) {
if (!state.session) return;
state.clipNear = clipNear;
state.clipFar = clipFar;
state.session.updateRenderState({
@ -169,40 +270,144 @@ var webxr = {
},
webxr_getBoundsDimensions: function(width, depth) {
HEAPF32[width >> 2] = 0.0;
HEAPF32[width >> 2] = 0.0; // Unsupported, see #557
HEAPF32[depth >> 2] = 0.0;
},
webxr_getBoundsGeometry: function(count) {
return 0; /* NULL */ // TODO
if (!state.session || !(state.session.space instanceof XRBoundedReferenceSpace)) {
return 0; /* NULL */
}
var points = state.session.space.boundsGeometry;
if (state.boundsGeometryCount < points.length) {
Module._free(state.boundsGeometry|0);
state.boundsGeometry = Module._malloc(4 * 4 * points.length);
if (state.boundsGeometry === 0) {
return state.boundsGeometry;
}
}
for (var i = 0; i < points.length; i++) {
HEAPF32.set(points[i], state.boundsGeometry + 4 * i);
}
return state.boundsGeometry;
},
webxr_getPose: function(device, position, orientation) {
return false; // TODO
if (!state.session || !state.session.viewer) return false;
if (device === 0 /* DEVICE_HEAD */) {
var transform = state.session.viewer.transform;
HEAPF32[position >> 2 + 0] = transform.position.x;
HEAPF32[position >> 2 + 1] = transform.position.y;
HEAPF32[position >> 2 + 2] = transform.position.z;
HEAPF32[position >> 2 + 3] = transform.position.w;
HEAPF32[orientation >> 2 + 0] = transform.orientation.x;
HEAPF32[orientation >> 2 + 1] = transform.orientation.y;
HEAPF32[orientation >> 2 + 2] = transform.orientation.z;
HEAPF32[orientation >> 2 + 3] = transform.orientation.w;
return true;
}
if (state.session.inputSources[device]) {
var inputSource = state.session.inputSources[device];
var space = inputSource.gripSpace || inputSource.targetRaySpace;
var transform = state.session.frame.getPose(space, state.session.space).transform;
HEAPF32[position >> 2 + 0] = transform.position.x;
HEAPF32[position >> 2 + 1] = transform.position.y;
HEAPF32[position >> 2 + 2] = transform.position.z;
HEAPF32[position >> 2 + 3] = transform.position.w;
HEAPF32[orientation >> 2 + 0] = transform.orientation.x;
HEAPF32[orientation >> 2 + 1] = transform.orientation.y;
HEAPF32[orientation >> 2 + 2] = transform.orientation.z;
HEAPF32[orientation >> 2 + 3] = transform.orientation.w;
return true;
}
return false;
},
webxr_getVelocity: function(device, velocity, angularVelocity) {
return false; // TODO
return false; // Unsupported, see #619
},
webxr_isDown: function(device, button, down, changed) {
return false; // TODO
if (!state.session) return false;
var inputSource = state.session.inputSources[device];
if (!inputSource || !inputSource.gamepad || !inputSource.mapping || !inputSource.mapping[button]) {
return false;
}
HEAPU32[down >> 2] = inputSource.gamepad.buttons[inputSource.mapping[button]].pressed ? 1 : 0;
HEAPU32[changed >> 2] = 0; // TODO
return true;
},
webxr_isTouched: function(device, button, touched) {
return false; // TODO
if (!state.session) return false;
var inputSource = state.session.inputSources[device];
if (!inputSource || !inputSource.gamepad || !inputSource.mapping || !inputSource.mapping[button]) {
return false;
}
HEAPU32[touched >> 2] = inputSource.gamepad.buttons[inputSource.mapping[button]].touched ? 1 : 0;
return true;
},
webxr_getAxis: function(device, axis, value) {
return false; // TODO
if (!state.session) return false;
var inputSource = state.session.inputSources[device];
if (!inputSource || !inputSource.gamepad || !inputSource.mapping) {
return false;
}
switch (axis) {
// These 1D axes are queried as buttons in the Gamepad API
// The DeviceAxis enumerants match the DeviceButton ones, so they're interchangeable
case 0: /* AXIS_TRIGGER */
case 3: /* AXIS_GRIP */
if (inputSource.mapping[axis]) {
HEAPF32[value >> 2] = inputSource.gamepad.buttons[inputSource.mapping[axis]].value;
return true;
}
return false;
case 1: /* AXIS_THUMBSTICK */
HEAPF32[value >> 2 + 0] = inputSource.gamepad.axes[2];
HEAPF32[value >> 2 + 1] = inputSource.gamepad.axes[3];
return true;
case 2: /* AXIS_TOUCHPAD */
HEAPF32[value >> 2 + 0] = inputSource.gamepad.axes[0];
HEAPF32[value >> 2 + 1] = inputSource.gamepad.axes[1];
return true;
default:
return false;
}
},
webxr_vibrate: function(device, strength, duration, frequency) {
return false; // TODO
if (!state.session) return false;
var inputSource = state.session.inputSources[device];
if (!inputSource || !inputSource.gamepad || !inputSource.gamepad.hapticActuators || !inputSource.gamepad.hapticActuators[0]) {
return false;
}
// Not technically an official WebXR feature, but widely supported
inputSource.gamepad.hapticActuators[0].pulse(strength, duration * 1000);
return true;
},
webxr_newModelData: function(device, animated) {
return 0; /* NULL */ // TODO
return 0; /* NULL */
},
webxr_animate: function(device, model) {