mirror of
https://github.com/bjornbytes/lovr.git
synced 2024-07-22 21:53:35 +00:00
421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
var webxr = {
|
|
$state: {},
|
|
|
|
// Derived from github:immersive-web/webxr-input-profiles#5052b76
|
|
$buttons: {
|
|
'oculus-touch': {
|
|
left: [0, 3, null, 1, null, null, null, 4, 5],
|
|
right: [0, 3, null, 1, null, 4, 5, null, null],
|
|
},
|
|
'valve-index': [0, 3, 2, 1, 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]
|
|
},
|
|
$axes: {
|
|
'oculus-touch': { touchpad: false, thumbstick: true },
|
|
'valve-index': { touchpad: true, thumbstick: true },
|
|
'microsoft-mixed-reality': { touchpad: true, thumbstick: true },
|
|
'htc-vive': { touchpad: true, thumbstick: false },
|
|
'generic-trigger': { touchpad: false, thumbstick: false },
|
|
'generic-trigger-touchpad': { touchpad: true, thumbstick: false },
|
|
'generic-trigger-thumbstick': { touchpad: false, thumbstick: true },
|
|
'generic-trigger-touchpad-thumbstick': { touchpad: true, thumbstick: true },
|
|
'generic-trigger-squeeze': { touchpad: false, thumbstick: false },
|
|
'generic-trigger-squeeze-touchpad': { touchpad: true, thumbstick: false },
|
|
'generic-trigger-squeeze-touchpad-thumbstick': { touchpad: true, thumbstick: true },
|
|
'generic-trigger-squeeze-thumbstick': { touchpad: false, thumbstick: true },
|
|
'generic-hand-select': { touchpad: false, thumbstick: false },
|
|
},
|
|
|
|
$writePose: function(transform, position, orientation) {
|
|
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;
|
|
},
|
|
|
|
webxr_init__deps: ['$buttons', '$axes'],
|
|
webxr_init: function(supersample, offset, msaa, overlay) {
|
|
if (!navigator.xr) {
|
|
return false;
|
|
}
|
|
|
|
Module.lovr = Module.lovr || {};
|
|
Module.lovr.enterVR = function() {
|
|
var options = {
|
|
requiredFeatures: ['local-floor'],
|
|
optionalFeatures: ['bounded-floor', 'hand-tracking']
|
|
};
|
|
|
|
return navigator.xr.requestSession('immersive-vr', options).then(function(session) {
|
|
var space = session.requestReferenceSpace('bounded-floor').catch(function() {
|
|
return session.requestReferenceSpace('local-floor');
|
|
});
|
|
|
|
return Promise.all([space, Module.ctx.makeXRCompatible()]).then(function(data) {
|
|
state.session = session;
|
|
state.space = data[0];
|
|
state.clipNear = .1;
|
|
state.clipFar = 1000.0;
|
|
state.boundsGeometry = 0; /* NULL */
|
|
state.boundsGeometryCount = 0;
|
|
state.layer = new XRWebGLLayer(session, Module.ctx);
|
|
state.session.updateRenderState({ baseLayer: state.layer });
|
|
state.fbo = GL.getNewId(GL.framebuffers);
|
|
GL.framebuffers[state.fbo] = state.layer.framebuffer;
|
|
|
|
// Canvas
|
|
var sizeof_CanvasFlags = 16;
|
|
var width = state.layer.framebufferWidth;
|
|
var height = state.layer.framebufferHeight;
|
|
var flags = Module.stackAlloc(sizeof_CanvasFlags);
|
|
HEAPU8.fill(0, flags, flags + sizeof_CanvasFlags); // memset(&flags, 0, sizeof(CanvasFlags));
|
|
HEAPU8[flags + 12] = 1; // flags.stereo
|
|
state.canvas = Module['_lovrCanvasCreateFromHandle'](width, height, flags, state.fbo, 0, 0, 1, true);
|
|
Module.stackRestore(flags);
|
|
|
|
state.hands = [];
|
|
state.lastButtonState = [];
|
|
session.addEventListener('inputsourceschange', function(event) {
|
|
state.hands.splice(0, state.hands.length);
|
|
session.inputSources.forEach(function(inputSource) {
|
|
state.hands[({ left: 1, right: 2 })[inputSource.handedness]] = inputSource;
|
|
|
|
var profile = inputSource.profiles.find(function(profile) { return buttons[profile]; });
|
|
|
|
if (!inputSource.gamepad || !profile) {
|
|
inputSource.buttons = [];
|
|
inputSource.axes = {};
|
|
return;
|
|
}
|
|
|
|
inputSource.axes = axes[profile];
|
|
var mapping = buttons[profile][inputSource.handedness] || buttons[profile] || [];
|
|
inputSource.buttons = mapping.map(function(buttonIndex) {
|
|
return inputSource.gamepad.buttons[buttonIndex];
|
|
});
|
|
});
|
|
});
|
|
|
|
session.addEventListener('end', function() {
|
|
Module._free(state.boundsGeometry|0);
|
|
|
|
if (state.canvas) {
|
|
Module['_lovrCanvasDestroy'](state.canvas);
|
|
Module._free(state.canvas - 4);
|
|
}
|
|
|
|
if (state.fbo) {
|
|
GL.framebuffers[state.fbo].name = 0;
|
|
GL.framebuffers[state.fbo] = null;
|
|
}
|
|
|
|
Browser.mainLoop.pause();
|
|
Module['_webxr_detach']();
|
|
Browser.requestAnimationFrame = window.requestAnimationFrame.bind(window);
|
|
Browser.mainLoop.resume();
|
|
state.session = null;
|
|
});
|
|
|
|
// Trick emscripten into using the session's requestAnimationFrame, also make ourselves
|
|
// the headset driver using webxr_attach. These are both undone when the session ends
|
|
// so that the mouse/keyboard driver can be used when the session is inactive.
|
|
Browser.mainLoop.pause();
|
|
Module['_webxr_attach']();
|
|
Browser.requestAnimationFrame = function(fn) {
|
|
return session.requestAnimationFrame(function(t, frame) {
|
|
state.frame = frame;
|
|
state.lastDisplayTime = state.displayTime || state.frame.predictedDisplayTime;
|
|
state.displayTime = state.frame.predictedDisplayTime;
|
|
state.viewer = state.frame.getViewerPose(state.space);
|
|
fn();
|
|
state.hands.forEach(function(inputSource, i) {
|
|
state.lastButtonState[i] = inputSource && inputSource.buttons.map(function(button) {
|
|
return button && button.pressed;
|
|
});
|
|
});
|
|
});
|
|
};
|
|
Browser.mainLoop.resume();
|
|
return session;
|
|
});
|
|
});
|
|
};
|
|
|
|
Module.lovr.exitVR = function() {
|
|
return state.session ? state.session.end() : Promise.resolve();
|
|
};
|
|
|
|
// WebXR is not set as the display driver immediately, it uses webxr_attach to make itself the
|
|
// headset driver when a session starts.
|
|
return false;
|
|
},
|
|
|
|
webxr_start: function() {
|
|
// Session is handled asynchronously
|
|
},
|
|
|
|
webxr_destroy: function() {
|
|
if (state.session) {
|
|
state.session.end();
|
|
}
|
|
},
|
|
|
|
webxr_getName: function(name, size) {
|
|
return false;
|
|
},
|
|
|
|
webxr_getOriginType: function() {
|
|
return 1; /* ORIGIN_FLOOR */
|
|
},
|
|
|
|
webxr_getDisplayTime: function() {
|
|
return state.displayTime / 1000.0;
|
|
},
|
|
|
|
webxr_getDeltaTime: function() {
|
|
return (state.displayTime - state.lastDisplayTime) / 1000.0;
|
|
},
|
|
|
|
webxr_getDisplayDimensions: function(width, height) {
|
|
HEAPU32[width >> 2] = state.layer.framebufferWidth;
|
|
HEAPU32[height >> 2] = state.layer.framebufferHeight;
|
|
},
|
|
|
|
webxr_getDisplayFrequency: function() {
|
|
return 0.0;
|
|
},
|
|
|
|
webxr_getViewCount: function() {
|
|
return state.viewer ? state.viewer.views.length : 0;
|
|
},
|
|
|
|
webxr_getViewPose__deps: ['$writePose'],
|
|
webxr_getViewPose: function(index, position, orientation) {
|
|
var view = state.viewer && state.viewer.views[index];
|
|
view && writePose(view.transform, position, orientation);
|
|
return !!view;
|
|
},
|
|
|
|
webxr_getViewAngles: function(index, left, right, up, down) {
|
|
return false; // TODO
|
|
},
|
|
|
|
webxr_getClipDistance: function(clipNear, clipFar) {
|
|
HEAPF32[clipNear >> 2] = state.clipNear;
|
|
HEAPF32[clipFar >> 2] = state.clipFar;
|
|
},
|
|
|
|
webxr_setClipDistance: function(clipNear, clipFar) {
|
|
state.clipNear = clipNear;
|
|
state.clipFar = clipFar;
|
|
state.session.updateRenderState({
|
|
clipNear: clipNear,
|
|
clipFar: clipFar
|
|
});
|
|
},
|
|
|
|
webxr_getBoundsDimensions: function(width, depth) {
|
|
HEAPF32[width >> 2] = 0.0; // Unsupported, see #557
|
|
HEAPF32[depth >> 2] = 0.0;
|
|
},
|
|
|
|
webxr_getBoundsGeometry: function(count) {
|
|
if (!(state.space instanceof XRBoundedReferenceSpace)) {
|
|
return 0; /* NULL */
|
|
}
|
|
|
|
var points = state.space.boundsGeometry;
|
|
|
|
if (state.boundsGeometryCount < points.length) {
|
|
Module._free(state.boundsGeometry|0);
|
|
state.boundsGeometryCount = points.length;
|
|
state.boundsGeometry = Module._malloc(4 * 4 * state.boundsGeometryCount);
|
|
if (state.boundsGeometry === 0) {
|
|
return 0; /* NULL */
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < points.length; i++) {
|
|
HEAPF32[(state.boundsGeometry >> 2) + 4 * i + 0] = points[i].x;
|
|
HEAPF32[(state.boundsGeometry >> 2) + 4 * i + 1] = points[i].y;
|
|
HEAPF32[(state.boundsGeometry >> 2) + 4 * i + 2] = points[i].z;
|
|
HEAPF32[(state.boundsGeometry >> 2) + 4 * i + 3] = points[i].w;
|
|
}
|
|
|
|
HEAPU32[count >> 2] = points.length;
|
|
return state.boundsGeometry;
|
|
},
|
|
|
|
webxr_getPose__deps: ['$writePose'],
|
|
webxr_getPose: function(device, position, orientation) {
|
|
if (device === 0 /* DEVICE_HEAD */) {
|
|
state.viewer && writePose(state.viewer.transform, position, orientation);
|
|
return !!state.viewer;
|
|
}
|
|
|
|
if (state.hands[device]) {
|
|
var space = state.hands[device].gripSpace || state.hands[device].targetRaySpace;
|
|
var pose = state.frame.getPose(space, state.space);
|
|
pose && writePose(pose.transform, position, orientation);
|
|
return !!pose;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
webxr_getVelocity: function(device, velocity, angularVelocity) {
|
|
var pose;
|
|
|
|
if (device === 0 /* DEVICE_HEAD */) {
|
|
pose = state.viewer;
|
|
} else if (state.hands[device]) {
|
|
var space = state.hands[device].gripSpace || state.hands[device].targetRaySpace;
|
|
pose = state.frame.getPose(space, state.space);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (pose && pose.linearVelocity && pose.angularVelocity) {
|
|
HEAPF32[(velocity >> 2) + 0] = pose.linearVelocity.x;
|
|
HEAPF32[(velocity >> 2) + 1] = pose.linearVelocity.y;
|
|
HEAPF32[(velocity >> 2) + 2] = pose.linearVelocity.z;
|
|
HEAPF32[(angularVelocity >> 2) + 0] = pose.angularVelocity.x;
|
|
HEAPF32[(angularVelocity >> 2) + 1] = pose.angularVelocity.y;
|
|
HEAPF32[(angularVelocity >> 2) + 2] = pose.angularVelocity.z;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
webxr_isDown: function(device, button, down, changed) {
|
|
var b = state.hands[device] && state.hands[device].buttons[button];
|
|
var c = state.lastButtonState[device];
|
|
HEAPU8[down] = b && b.pressed;
|
|
HEAPU8[changed] = b && c && (c[button] ^ b.pressed);
|
|
return !!b;
|
|
},
|
|
|
|
webxr_isTouched: function(device, button, touched) {
|
|
var b = state.hands[device] && state.hands[device].buttons[button];
|
|
HEAPU8[touched] = b && b.touched;
|
|
return !!b;
|
|
},
|
|
|
|
webxr_getAxis: function(device, axis, value) {
|
|
var hand = state.hands[device];
|
|
|
|
if (!hand || !hand.gamepad) {
|
|
return false;
|
|
}
|
|
|
|
switch (axis) {
|
|
case 0: /* AXIS_TRIGGER */
|
|
case 3: /* AXIS_GRIP */
|
|
HEAPF32[value >> 2] = hand.buttons[axis] && hand.buttons[axis].value;
|
|
return !!hand.buttons[axis];
|
|
|
|
case 1: /* AXIS_THUMBSTICK */
|
|
HEAPF32[(value >> 2) + 0] = hand.gamepad.axes[2];
|
|
HEAPF32[(value >> 2) + 1] = hand.gamepad.axes[3];
|
|
return hand.axes.thumbstick;
|
|
|
|
case 2: /* AXIS_TOUCHPAD */
|
|
HEAPF32[(value >> 2) + 0] = hand.gamepad.axes[0];
|
|
HEAPF32[(value >> 2) + 1] = hand.gamepad.axes[1];
|
|
return hand.axes.touchpad;
|
|
|
|
default: return false;
|
|
}
|
|
},
|
|
|
|
webxr_getSkeleton__deps: ['$writePose'],
|
|
webxr_getSkeleton: function(device, poses) {
|
|
var inputSource = state.hands[device];
|
|
|
|
if (!inputSource || !inputSource.hand) {
|
|
return false;
|
|
}
|
|
|
|
var space = state.hands[device].gripSpace;
|
|
var pose = state.frame.getPose(space, state.space);
|
|
pose && writePose(pose.transform, poses, poses + 16);
|
|
poses += 32;
|
|
|
|
// TODO use fillPoses
|
|
for ([joint, space] of inputSource.hand) {
|
|
var pose = state.frame.getJointPose(space, state.space);
|
|
if (!pose) return false;
|
|
writePose(pose.transform, poses, poses + 16);
|
|
poses += 32;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
// Not an official WebXR feature, but widely supported
|
|
webxr_vibrate: function(device, strength, duration, frequency) {
|
|
var hand = state.deviceMap[device];
|
|
var actuator = hand && hand.gamepad && hand.gamepad.hapticActuators && hand.gamepad.hapticActuators[0];
|
|
actuator && actuator.pulse(strength, duration * 1000);
|
|
return !!actuator;
|
|
},
|
|
|
|
webxr_newModelData: function(device, animated) {
|
|
return 0; /* NULL */
|
|
},
|
|
|
|
webxr_animate: function(device, model) {
|
|
return false;
|
|
},
|
|
|
|
webxr_renderTo: function(callback, userdata) {
|
|
var matrix = Module.stackAlloc(16 * 4);
|
|
if (state.viewer) {
|
|
var views = state.viewer.views;
|
|
HEAPF32.set(views[0].transform.inverse.matrix, matrix >> 2);
|
|
Module._lovrGraphicsSetViewMatrix(0, matrix);
|
|
HEAPF32.set(views[1].transform.inverse.matrix, matrix >> 2);
|
|
Module._lovrGraphicsSetViewMatrix(1, matrix);
|
|
HEAPF32.set(views[0].projectionMatrix, matrix >> 2);
|
|
Module._lovrGraphicsSetProjection(0, matrix);
|
|
HEAPF32.set(views[1].projectionMatrix, matrix >> 2);
|
|
Module._lovrGraphicsSetProjection(1, matrix);
|
|
} else {
|
|
HEAPF32.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], matrix >> 2);
|
|
Module._lovrGraphicsSetViewMatrix(0, matrix);
|
|
Module._lovrGraphicsSetViewMatrix(1, matrix);
|
|
// TODO projection?
|
|
}
|
|
Module.stackRestore(matrix);
|
|
Module._lovrGraphicsSetBackbuffer(state.canvas, true, true);
|
|
{{{ makeDynCall('vi', 'callback') }}} (userdata);
|
|
Module._lovrGraphicsSetBackbuffer(0, false, false);
|
|
},
|
|
|
|
webxr_isFocused: function() {
|
|
return true;
|
|
},
|
|
|
|
webxr_update: function() {
|
|
return (state.displayTime - state.lastDisplayTime) / 1000.0;
|
|
}
|
|
};
|
|
|
|
autoAddDeps(webxr, '$state');
|
|
mergeInto(LibraryManager.library, webxr);
|