aframe-ar
Version:
Basic A-Frame support for the new three.ar.js library and WebARonARKit/Core browsers, as well as WebXR Viewer.
167 lines (141 loc) • 6.3 kB
JavaScript
AFRAME.registerComponent('three-ar', {
schema: {
takeOverCamera: {default: true},
cameraUserHeight: {default: false}
},
init: function () {
this.posePosition = new THREE.Vector3();
this.poseQuaternion = new THREE.Quaternion();
this.poseEuler = new THREE.Euler(0, 0, 0, 'YXZ');
this.poseRotation = new THREE.Vector3();
this.projectionMatrix = new THREE.Matrix4();
this.onceSceneLoaded = this.onceSceneLoaded.bind(this);
if (this.el.sceneEl.hasLoaded) {
setTimeout(this.onceSceneLoaded);
} else {
this.el.sceneEl.addEventListener('loaded', this.onceSceneLoaded);
}
},
tick: function (t, dt) {
if (!this.arDisplay || !this.arDisplay.getFrameData) { return; }
// If we have an ARView, render it.
if (this.arView) { this.arView.render(); }
// Get the ARDisplay frame data with pose and projection matrix.
if (!this.frameData) { this.frameData = new VRFrameData(); }
this.arDisplay.getFrameData(this.frameData);
// Get the pose information.
this.posePosition.fromArray(this.frameData.pose.position);
this.poseQuaternion.fromArray(this.frameData.pose.orientation);
this.poseEuler.setFromQuaternion(this.poseQuaternion);
this.poseRotation.set(
THREE.Math.RAD2DEG * this.poseEuler.x,
THREE.Math.RAD2DEG * this.poseEuler.y,
THREE.Math.RAD2DEG * this.poseEuler.z);
// If we control a camera, and should apply user height, do it.
if (this.arCamera && this.data.cameraUserHeight) {
this.posePosition.y += this.arCamera.el.components.camera.data.userHeight;
}
// For A-Painter, detect bogus pose and fire poseFound / poseLost.
var poseValid = this.posePosition.x || this.posePosition.y || this.posePosition.z || this.poseQuaternion.x || this.poseQuaternion.y || this.poseQuaternion.z;
if (poseValid) {
if (this.poseLost !== false) {
this.poseLost = false;
this.el.emit('poseFound');
}
} else {
if (this.poseLost !== true) {
this.poseLost = true;
this.el.emit('poseLost', false);
}
}
// Can use either left or right projection matrix; pick left for now.
this.projectionMatrix.fromArray(this.frameData.leftProjectionMatrix);
},
takeOverCamera: function (camera) {
this.arCamera = camera;
camera.isARPerspectiveCamera = true; // HACK - is this necessary?
camera.vrDisplay = this.arDisplay; // HACK - is this necessary?
camera.el.setAttribute('ar-camera', 'enabled', true);
},
onceSceneLoaded: function () {
// Add an event listener for ardisplayconnect,
// to check for AR display if we don't have one yet.
var self = this;
window.addEventListener('ardisplayconnect', function () {
if (!self.arDisplay) { self.checkForARDisplay(); }
});
// Check now for AR display.
this.checkForARDisplay();
},
checkForARDisplay: function () {
// Get the ARDisplay, if any.
var self = this;
THREE.ARUtils.getARDisplay().then(function (display) {
self.arDisplay = display;
if (!display) { return; }
// The scene is loaded, so scene components etc. should be available.
var scene = self.el.sceneEl;
// Take over the scene camera, if so directed.
// But wait a tick, because otherwise injected camera will not be present.
if (self.data.takeOverCamera) {
setTimeout(function () { self.takeOverCamera(scene.camera); });
}
// Modify the scene renderer to allow ARView video passthrough.
scene.renderer.alpha = true;
scene.renderer.autoClearColor = THREE.ARUtils.isARKit(display) && !window.WebARonARKitSendsCameraFrames;
scene.renderer.autoClearDepth = true;
// Create the ARView.
self.arView = new THREE.ARView(display, scene.renderer);
});
},
getPosition: function () {
if (!this.arDisplay || !this.arDisplay.getFrameData) { return null; }
return this.posePosition;
},
getOrientation: function () {
if (!this.arDisplay || !this.arDisplay.getFrameData) { return null; }
return this.poseQuaternion;
},
getRotation: function () {
if (!this.arDisplay || !this.arDisplay.getFrameData) { return null; }
return this.poseRotation;
},
getProjectionMatrix: function () {
if (!this.arDisplay || !this.arDisplay.getFrameData) { return null; }
return this.projectionMatrix;
},
hitAR: (function () {
// Temporary variables, only within closure scope.
var transform = new THREE.Matrix4();
var hitpoint = new THREE.Vector3();
var hitquat = new THREE.Quaternion();
var hitscale = new THREE.Vector3();
var worldpos = new THREE.Vector3();
// The desired function, which this returns.
return function (x, y, el, raycasterEl) {
var threear = this;
if (!this.arDisplay || !this.arDisplay.hitTest) { return []; }
var hit = this.arDisplay.hitTest(x, y);
// Process AR hits.
var hitsToReturn = [];
for (var i = 0; hit && i < hit.length; i++) {
transform.fromArray(hit[0].modelMatrix);
transform.decompose(hitpoint, hitquat, hitscale);
raycasterEl.object3D.getWorldPosition(worldpos);
hitsToReturn.push({
distance: hitpoint.distanceTo(worldpos),
point: hitpoint, // Vector3
object: (el && el.object3D) || this.el.sceneEl.object3D
/*
// We don't have any of these properties...
face: undefined, // Face3
faceIndex: undefined,
index: undefined,
uv: undefined // Vector2
*/
});
}
return hitsToReturn;
}
})()
});