forked from playcanvas/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WebXR AR hit test support (playcanvas#1926)
* webxr hit test * lint fixes * add missing error event * fix build:all * tipes -> types * tipes -> types * Fix links * Fix links * Fix links * simplify hit-test start function * small fixes * XrHitTest.hitTestSources > XrHitTest.sources * ar hit test example * fix merge Co-authored-by: Will Eastcott <will@playcanvas.com>
- Loading branch information
1 parent
b604bc0
commit 7d3e982
Showing
11 changed files
with
746 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,5 @@ var WebAssembly = {}; | |
|
||
// WebXR | ||
var XRWebGLLayer = {}; | ||
var XRRay = {}; | ||
var DOMPoint = {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>PlayCanvas AR Hit Test</title> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | ||
<link rel="icon" type="image/png" href="../playcanvas-favicon.png" /> | ||
<script src="../../build/output/playcanvas.js"></script> | ||
<style> | ||
body { | ||
margin: 0; | ||
padding: 0; | ||
overflow: hidden; | ||
} | ||
canvas { | ||
width:100%; | ||
height:100%; | ||
|
||
} | ||
.message { | ||
position: absolute; | ||
padding: 8px 16px; | ||
left: 20px; | ||
bottom: 0px; | ||
color: #ccc; | ||
background-color: rgba(0, 0, 0, .5); | ||
font-family: "Proxima Nova", Arial, sans-serif; | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<canvas id="application-canvas"></canvas> | ||
<div> | ||
<p class="message"></p> | ||
</div> | ||
<script> | ||
// draw some axes | ||
var drawAxes = function (pos, scale) { | ||
var color = new pc.Color(1,0,0); | ||
|
||
var axis = new pc.Vec3(); | ||
var end = new pc.Vec3().copy(pos).add(axis.set(scale,0,0)); | ||
|
||
app.renderLine(pos, end, color); | ||
|
||
color.set(0, 1, 0); | ||
end.sub(axis.set(scale,0,0)).add(axis.set(0,scale,0)); | ||
app.renderLine(pos, end, color); | ||
|
||
color.set(0, 0, 1); | ||
end.sub(axis.set(0,scale,0)).add(axis.set(0,0,scale)); | ||
app.renderLine(pos, end, color); | ||
} | ||
</script> | ||
|
||
|
||
<script> | ||
var message = function (msg) { | ||
var el = document.querySelector('.message'); | ||
el.textContent = msg; | ||
} | ||
|
||
var canvas = document.getElementById('application-canvas'); | ||
var app = new pc.Application(canvas, { | ||
mouse: new pc.Mouse(canvas), | ||
touch: new pc.TouchDevice(canvas), | ||
keyboard: new pc.Keyboard(window) | ||
}); | ||
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); | ||
app.setCanvasResolution(pc.RESOLUTION_AUTO); | ||
|
||
window.addEventListener("resize", function () { | ||
app.resizeCanvas(canvas.width, canvas.height); | ||
}); | ||
|
||
// use device pixel ratio | ||
app.graphicsDevice.maxPixelRatio = window.devicePixelRatio; | ||
|
||
app.start(); | ||
|
||
// create camera | ||
var c = new pc.Entity(); | ||
c.addComponent('camera', { | ||
clearColor: new pc.Color(0, 0, 0, 0), | ||
farClip: 10000 | ||
}); | ||
app.root.addChild(c); | ||
|
||
var l = new pc.Entity(); | ||
l.addComponent("light", { | ||
type: "spot", | ||
range: 30 | ||
}); | ||
l.translate(0,10,0); | ||
app.root.addChild(l); | ||
|
||
var target = new pc.Entity(); | ||
target.addComponent("model", { | ||
type: "cylinder", | ||
}); | ||
target.setLocalScale(.5, .01, .5); | ||
app.root.addChild(target); | ||
|
||
if (app.xr.supported) { | ||
var activate = function () { | ||
if (app.xr.isAvailable(pc.XRTYPE_AR)) { | ||
c.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, function (err) { | ||
if (err) message("WebXR Immersive AR failed to start: " + err.message); | ||
}); | ||
} else { | ||
message("Immersive AR is not available"); | ||
} | ||
}; | ||
|
||
app.mouse.on("mousedown", function () { | ||
if (! app.xr.active) | ||
activate(); | ||
}); | ||
|
||
if (app.touch) { | ||
app.touch.on("touchend", function (evt) { | ||
if (! app.xr.active) { | ||
// if not in VR, activate | ||
activate(); | ||
} else { | ||
// otherwise reset camera | ||
c.camera.endXr(); | ||
} | ||
|
||
evt.event.preventDefault(); | ||
evt.event.stopPropagation(); | ||
}); | ||
} | ||
|
||
// end session by keyboard ESC | ||
app.keyboard.on('keydown', function (evt) { | ||
if (evt.key === pc.KEY_ESCAPE && app.xr.active) { | ||
app.xr.end(); | ||
} | ||
}); | ||
|
||
app.xr.on('start', function () { | ||
message("Immersive AR session has started"); | ||
|
||
if (! app.xr.hitTest.supported) | ||
return; | ||
|
||
app.xr.hitTest.start({ | ||
entityTypes: [ pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE ], | ||
callback: function(err, hitTestSource) { | ||
if (err) { | ||
message("Failed to start AR hit test"); | ||
return; | ||
} | ||
|
||
hitTestSource.on('result', function (position, rotation) { | ||
target.setPosition(position); | ||
target.setRotation(rotation); | ||
}); | ||
} | ||
}); | ||
}); | ||
app.xr.on('end', function () { | ||
message("Immersive AR session has ended"); | ||
}); | ||
app.xr.on('available:' + pc.XRTYPE_AR, function (available) { | ||
if (available) { | ||
if (app.xr.hitTest.supported) { | ||
message("Touch screen to start AR session and look at the floor or walls"); | ||
} else { | ||
message("AR Hit Test is not supported"); | ||
} | ||
} else { | ||
message("Immersive AR is unavailable"); | ||
} | ||
}); | ||
|
||
if (! app.xr.isAvailable(pc.XRTYPE_AR)) { | ||
message("Immersive AR is not available"); | ||
} else if (! app.xr.hitTest.supported) { | ||
message("AR Hit Test is not supported"); | ||
} else { | ||
message("Touch screen to start AR session and look at the floor or walls"); | ||
} | ||
} else { | ||
message("WebXR is not supported"); | ||
} | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
Object.assign(pc, function () { | ||
var poolVec3 = []; | ||
var poolQuat = []; | ||
|
||
|
||
/** | ||
* @class | ||
* @name pc.XrHitTestSource | ||
* @augments pc.EventHandler | ||
* @classdesc Represents XR hit test source, which provides access to hit results of real world geometry from AR session. | ||
* @description Represents XR hit test source, which provides access to hit results of real world geometry from AR session. | ||
* @param {pc.XrManager} manager - WebXR Manager. | ||
* @param {object} xrHitTestSource - XRHitTestSource object that is created by WebXR API. | ||
* @param {boolean} transient - True if XRHitTestSource created for input source profile. | ||
* @example | ||
* hitTestSource.on('result', function (position, rotation) { | ||
* target.setPosition(position); | ||
* }); | ||
*/ | ||
var XrHitTestSource = function (manager, xrHitTestSource, transient) { | ||
pc.EventHandler.call(this); | ||
|
||
this.manager = manager; | ||
this._xrHitTestSource = xrHitTestSource; | ||
this._transient = transient; | ||
}; | ||
XrHitTestSource.prototype = Object.create(pc.EventHandler.prototype); | ||
XrHitTestSource.prototype.constructor = XrHitTestSource; | ||
|
||
/** | ||
* @event | ||
* @name pc.XrHitTestSource#remove | ||
* @description Fired when {pc.XrHitTestSource} is removed. | ||
* @example | ||
* hitTestSource.once('remove', function () { | ||
* // hit test source has been removed | ||
* }); | ||
*/ | ||
|
||
/** | ||
* @event | ||
* @name pc.XrHitTestSource#result | ||
* @description Fired when hit test source receives new results. It provides transform information that tries to match real world picked geometry. | ||
* @param {pc.Vec3} position - Position of hit test | ||
* @param {pc.Quat} rotation - Rotation of hit test | ||
* @param {pc.XrInputSource|null} inputSource - If is transient hit test source, then it will provide related input source | ||
* @example | ||
* hitTestSource.on('result', function (position, rotation, inputSource) { | ||
* target.setPosition(position); | ||
* target.setRotation(rotation); | ||
* }); | ||
*/ | ||
|
||
/** | ||
* @function | ||
* @name pc.XrHitTestSource#remove | ||
* @description Stop and remove hit test source. | ||
*/ | ||
XrHitTestSource.prototype.remove = function () { | ||
if (! this._xrHitTestSource) | ||
return; | ||
|
||
var sources = this.manager.hitTest.sources; | ||
var ind = sources.indexOf(this); | ||
if (ind !== -1) sources.splice(ind, 1); | ||
|
||
this.onStop(); | ||
}; | ||
|
||
XrHitTestSource.prototype.onStop = function () { | ||
this._xrHitTestSource.cancel(); | ||
this._xrHitTestSource = null; | ||
|
||
this.fire('remove'); | ||
this.manager.hitTest.fire('remove', this); | ||
}; | ||
|
||
XrHitTestSource.prototype.update = function (frame) { | ||
if (this._transient) { | ||
var transientResults = frame.getHitTestResultsForTransientInput(this._xrHitTestSource); | ||
for (var i = 0; i < transientResults.length; i++) { | ||
var transientResult = transientResults[i]; | ||
var inputSource; | ||
|
||
if (transientResult.inputSource) | ||
inputSource = this.manager.input._getByInputSource(transientResult.inputSource); | ||
|
||
this.updateHitResults(transientResult.results, inputSource); | ||
} | ||
} else { | ||
this.updateHitResults(frame.getHitTestResults(this._xrHitTestSource)); | ||
} | ||
}; | ||
|
||
XrHitTestSource.prototype.updateHitResults = function (results, inputSource) { | ||
for (var i = 0; i < results.length; i++) { | ||
var pose = results[i].getPose(this.manager._referenceSpace); | ||
|
||
var position = poolVec3.pop(); | ||
if (! position) position = new pc.Vec3(); | ||
position.copy(pose.transform.position); | ||
|
||
var rotation = poolQuat.pop(); | ||
if (! rotation) rotation = new pc.Quat(); | ||
rotation.copy(pose.transform.orientation); | ||
|
||
this.fire('result', position, rotation, inputSource); | ||
this.manager.hitTest.fire('result', this, position, rotation, inputSource); | ||
|
||
poolVec3.push(position); | ||
poolQuat.push(rotation); | ||
} | ||
}; | ||
|
||
|
||
return { XrHitTestSource: XrHitTestSource }; | ||
}()); |
Oops, something went wrong.