var ARjs = ARjs || {}
var THREEx = THREEx || {}
ARjs.Context = THREEx.ArToolkitContext = function(parameters){
var _this = this
_this._updatedAt = null
// handle default parameters
this.parameters = {
// AR backend - ['artoolkit', 'aruco', 'tango']
trackingBackend: 'artoolkit',
// debug - true if one should display artoolkit debug canvas, false otherwise
debug: false,
// the mode of detection - ['color', 'color_and_matrix', 'mono', 'mono_and_matrix']
detectionMode: 'mono',
// type of matrix code - valid iif detectionMode end with 'matrix' - [3x3, 3x3_HAMMING63, 3x3_PARITY65, 4x4, 4x4_BCH_13_9_3, 4x4_BCH_13_5_5]
matrixCodeType: '3x3',
// url of the camera parameters
cameraParametersUrl: ARjs.Context.baseURL + 'parameters/camera_para.dat',
// tune the maximum rate of pose detection in the source image
maxDetectionRate: 60,
// resolution of at which we detect pose in the source image
canvasWidth: 640,
canvasHeight: 480,
// enable image smoothing or not for canvas copy - default to true
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
imageSmoothingEnabled : false,
}
// parameters sanity check
console.assert(['artoolkit', 'aruco', 'tango'].indexOf(this.parameters.trackingBackend) !== -1, 'invalid parameter trackingBackend', this.parameters.trackingBackend)
console.assert(['color', 'color_and_matrix', 'mono', 'mono_and_matrix'].indexOf(this.parameters.detectionMode) !== -1, 'invalid parameter detectionMode', this.parameters.detectionMode)
this.arController = null;
this.arucoContext = null;
_this.initialized = false
this._arMarkersControls = []
//////////////////////////////////////////////////////////////////////////////
// setParameters
//////////////////////////////////////////////////////////////////////////////
setParameters(parameters)
function setParameters(parameters){
if( parameters === undefined ) return
for( var key in parameters ){
var newValue = parameters[ key ]
if( newValue === undefined ){
console.warn( "THREEx.ArToolkitContext: '" + key + "' parameter is undefined." )
continue
}
var currentValue = _this.parameters[ key ]
if( currentValue === undefined ){
console.warn( "THREEx.ArToolkitContext: '" + key + "' is not a property of this material." )
continue
}
_this.parameters[ key ] = newValue
}
}
}
Object.assign( ARjs.Context.prototype, THREE.EventDispatcher.prototype );
// ARjs.Context.baseURL = '../'
// default to github page
ARjs.Context.baseURL = 'https://jeromeetienne.github.io/AR.js/three.js/'
ARjs.Context.REVISION = '1.5.1'
/**
* Create a default camera for this trackingBackend
* @param {string} trackingBackend - the tracking to user
* @return {THREE.Camera} the created camera
*/
ARjs.Context.createDefaultCamera = function( trackingBackend ){
console.assert(false, 'use ARjs.Utils.createDefaultCamera instead')
// Create a camera
if( trackingBackend === 'artoolkit' ){
var camera = new THREE.Camera();
}else if( trackingBackend === 'aruco' ){
var camera = new THREE.PerspectiveCamera(42, renderer.domElement.width / renderer.domElement.height, 0.01, 100);
}else if( trackingBackend === 'tango' ){
var camera = new THREE.PerspectiveCamera(42, renderer.domElement.width / renderer.domElement.height, 0.01, 100);
}else console.assert(false)
return camera
}
//////////////////////////////////////////////////////////////////////////////
// init functions
//////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype.init = function(onCompleted){
var _this = this
if( this.parameters.trackingBackend === 'artoolkit' ){
this._initArtoolkit(done)
}else if( this.parameters.trackingBackend === 'aruco' ){
this._initAruco(done)
}else if( this.parameters.trackingBackend === 'tango' ){
this._initTango(done)
}else console.assert(false)
return
function done(){
// dispatch event
_this.dispatchEvent({
type: 'initialized'
});
_this.initialized = true
onCompleted && onCompleted()
}
}
////////////////////////////////////////////////////////////////////////////////
// update function
////////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype.update = function(srcElement){
// be sure arController is fully initialized
if(this.parameters.trackingBackend === 'artoolkit' && this.arController === null) return false;
// honor this.parameters.maxDetectionRate
var present = performance.now()
if( this._updatedAt !== null && present - this._updatedAt < 1000/this.parameters.maxDetectionRate ){
return false
}
this._updatedAt = present
// mark all markers to invisible before processing this frame
this._arMarkersControls.forEach(function(markerControls){
markerControls.object3d.visible = false
})
// process this frame
if(this.parameters.trackingBackend === 'artoolkit'){
this._updateArtoolkit(srcElement)
}else if( this.parameters.trackingBackend === 'aruco' ){
this._updateAruco(srcElement)
}else if( this.parameters.trackingBackend === 'tango' ){
this._updateTango(srcElement)
}else{
console.assert(false)
}
// dispatch event
this.dispatchEvent({
type: 'sourceProcessed'
});
// return true as we processed the frame
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Add/Remove markerControls
////////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype.addMarker = function(arMarkerControls){
console.assert(arMarkerControls instanceof THREEx.ArMarkerControls)
this._arMarkersControls.push(arMarkerControls)
}
ARjs.Context.prototype.removeMarker = function(arMarkerControls){
console.assert(arMarkerControls instanceof THREEx.ArMarkerControls)
// console.log('remove marker for', arMarkerControls)
var index = this.arMarkerControlss.indexOf(artoolkitMarker);
console.assert(index !== index )
this._arMarkersControls.splice(index, 1)
}
//////////////////////////////////////////////////////////////////////////////
// artoolkit specific
//////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype._initArtoolkit = function(onCompleted){
var _this = this
// set this._artoolkitProjectionAxisTransformMatrix to change artoolkit projection matrix axis to match usual webgl one
this._artoolkitProjectionAxisTransformMatrix = new THREE.Matrix4()
this._artoolkitProjectionAxisTransformMatrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI))
this._artoolkitProjectionAxisTransformMatrix.multiply(new THREE.Matrix4().makeRotationZ(Math.PI))
// get cameraParameters
var cameraParameters = new ARCameraParam(_this.parameters.cameraParametersUrl, function(){
// init controller
var arController = new ARController(_this.parameters.canvasWidth, _this.parameters.canvasHeight, cameraParameters);
_this.arController = arController
// honor this.parameters.imageSmoothingEnabled
arController.ctx.mozImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled;
arController.ctx.webkitImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled;
arController.ctx.msImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled;
arController.ctx.imageSmoothingEnabled = _this.parameters.imageSmoothingEnabled;
// honor this.parameters.debug
if( _this.parameters.debug === true ){
arController.debugSetup();
arController.canvas.style.position = 'absolute'
arController.canvas.style.top = '0px'
arController.canvas.style.opacity = '0.6'
arController.canvas.style.pointerEvents = 'none'
arController.canvas.style.zIndex = '-1'
}
// setPatternDetectionMode
var detectionModes = {
'color' : artoolkit.AR_TEMPLATE_MATCHING_COLOR,
'color_and_matrix' : artoolkit.AR_TEMPLATE_MATCHING_COLOR_AND_MATRIX,
'mono' : artoolkit.AR_TEMPLATE_MATCHING_MONO,
'mono_and_matrix' : artoolkit.AR_TEMPLATE_MATCHING_MONO_AND_MATRIX,
}
var detectionMode = detectionModes[_this.parameters.detectionMode]
console.assert(detectionMode !== undefined)
arController.setPatternDetectionMode(detectionMode);
// setMatrixCodeType
var matrixCodeTypes = {
'3x3' : artoolkit.AR_MATRIX_CODE_3x3,
'3x3_HAMMING63' : artoolkit.AR_MATRIX_CODE_3x3_HAMMING63,
'3x3_PARITY65' : artoolkit.AR_MATRIX_CODE_3x3_PARITY65,
'4x4' : artoolkit.AR_MATRIX_CODE_4x4,
'4x4_BCH_13_9_3': artoolkit.AR_MATRIX_CODE_4x4_BCH_13_9_3,
'4x4_BCH_13_5_5': artoolkit.AR_MATRIX_CODE_4x4_BCH_13_5_5,
}
var matrixCodeType = matrixCodeTypes[_this.parameters.matrixCodeType]
console.assert(matrixCodeType !== undefined)
arController.setMatrixCodeType(matrixCodeType);
// set thresholding in artoolkit
// this seems to be the default
// arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_MANUAL)
// adatative consume a LOT of cpu...
// arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_AUTO_ADAPTIVE)
// arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_AUTO_OTSU)
// notify
onCompleted()
})
return this
}
/**
* return the projection matrix
*/
ARjs.Context.prototype.getProjectionMatrix = function(srcElement){
// FIXME rename this function to say it is artoolkit specific - getArtoolkitProjectMatrix
// keep a backward compatibility with a console.warn
if( this.parameters.trackingBackend === 'aruco' ){
console.assert(false, 'dont call this function with aruco')
}else if( this.parameters.trackingBackend === 'artoolkit' ){
console.assert(this.arController, 'arController MUST be initialized to call this function')
// get projectionMatrixArr from artoolkit
var projectionMatrixArr = this.arController.getCameraMatrix();
var projectionMatrix = new THREE.Matrix4().fromArray(projectionMatrixArr)
}else console.assert(false)
// apply context._axisTransformMatrix - change artoolkit axis to match usual webgl one
projectionMatrix.multiply(this._artoolkitProjectionAxisTransformMatrix)
// return the result
return projectionMatrix
}
ARjs.Context.prototype._updateArtoolkit = function(srcElement){
this.arController.process(srcElement)
}
//////////////////////////////////////////////////////////////////////////////
// aruco specific
//////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype._initAruco = function(onCompleted){
this.arucoContext = new THREEx.ArucoContext()
// honor this.parameters.canvasWidth/.canvasHeight
this.arucoContext.canvas.width = this.parameters.canvasWidth
this.arucoContext.canvas.height = this.parameters.canvasHeight
// honor this.parameters.imageSmoothingEnabled
var context = this.arucoContext.canvas.getContext('2d')
// context.mozImageSmoothingEnabled = this.parameters.imageSmoothingEnabled;
context.webkitImageSmoothingEnabled = this.parameters.imageSmoothingEnabled;
context.msImageSmoothingEnabled = this.parameters.imageSmoothingEnabled;
context.imageSmoothingEnabled = this.parameters.imageSmoothingEnabled;
setTimeout(function(){
onCompleted()
}, 0)
}
ARjs.Context.prototype._updateAruco = function(srcElement){
// console.log('update aruco here')
var _this = this
var arMarkersControls = this._arMarkersControls
var detectedMarkers = this.arucoContext.detect(srcElement)
detectedMarkers.forEach(function(detectedMarker){
var foundControls = null
for(var i = 0; i < arMarkersControls.length; i++){
console.assert( arMarkersControls[i].parameters.type === 'barcode' )
if( arMarkersControls[i].parameters.barcodeValue === detectedMarker.id ){
foundControls = arMarkersControls[i]
break;
}
}
if( foundControls === null ) return
var tmpObject3d = new THREE.Object3D
_this.arucoContext.updateObject3D(tmpObject3d, foundControls._arucoPosit, foundControls.parameters.size, detectedMarker);
tmpObject3d.updateMatrix()
foundControls.updateWithModelViewMatrix(tmpObject3d.matrix)
})
}
//////////////////////////////////////////////////////////////////////////////
// tango specific
//////////////////////////////////////////////////////////////////////////////
ARjs.Context.prototype._initTango = function(onCompleted){
var _this = this
// check webvr is available
if (navigator.getVRDisplays){
// do nothing
} else if (navigator.getVRDevices){
alert("Your browser supports WebVR but not the latest version. See webvr.info for more info.");
} else {
alert("Your browser does not support WebVR. See webvr.info for assistance.");
}
this._tangoContext = {
vrDisplay: null,
vrPointCloud: null,
frameData: new VRFrameData(),
}
// get vrDisplay
navigator.getVRDisplays().then(function (vrDisplays){
if( vrDisplays.length === 0 ) alert('no vrDisplays available')
var vrDisplay = _this._tangoContext.vrDisplay = vrDisplays[0]
console.log('vrDisplays.displayName :', vrDisplay.displayName)
// init vrPointCloud
if( vrDisplay.displayName === "Tango VR Device" ){
_this._tangoContext.vrPointCloud = new THREE.WebAR.VRPointCloud(vrDisplay, true)
}
// NOTE it doesnt seem necessary and it fails on tango
// var canvasElement = document.createElement('canvas')
// document.body.appendChild(canvasElement)
// _this._tangoContext.requestPresent([{ source: canvasElement }]).then(function(){
// console.log('vrdisplay request accepted')
// });
onCompleted()
});
}
ARjs.Context.prototype._updateTango = function(srcElement){
// console.log('update aruco here')
var _this = this
var arMarkersControls = this._arMarkersControls
var tangoContext= this._tangoContext
var vrDisplay = this._tangoContext.vrDisplay
// check vrDisplay is already initialized
if( vrDisplay === null ) return
// Update the point cloud. Only if the point cloud will be shown the geometry is also updated.
if( vrDisplay.displayName === "Tango VR Device" ){
var showPointCloud = true
var pointsToSkip = 0
_this._tangoContext.vrPointCloud.update(showPointCloud, pointsToSkip, true)
}
if( this._arMarkersControls.length === 0 ) return
// TODO here do a fake search on barcode/1001 ?
var foundControls = this._arMarkersControls[0]
var frameData = this._tangoContext.frameData
// read frameData
vrDisplay.getFrameData(frameData);
if( frameData.pose.position === null ) return
if( frameData.pose.orientation === null ) return
// create cameraTransformMatrix
var position = new THREE.Vector3().fromArray(frameData.pose.position)
var quaternion = new THREE.Quaternion().fromArray(frameData.pose.orientation)
var scale = new THREE.Vector3(1,1,1)
var cameraTransformMatrix = new THREE.Matrix4().compose(position, quaternion, scale)
// compute modelViewMatrix from cameraTransformMatrix
var modelViewMatrix = new THREE.Matrix4()
modelViewMatrix.getInverse( cameraTransformMatrix )
foundControls.updateWithModelViewMatrix(modelViewMatrix)
// console.log('position', position)
// if( position.x !== 0 || position.y !== 0 || position.z !== 0 ){
// console.log('vrDisplay tracking')
// }else{
// console.log('vrDisplay NOT tracking')
// }
}