/**
* VisageAR
*
* @constructor
*/
function VisageAR() {
var v_width;
var v_height;
var v_renderer;
var v_scene, v_backgroundScene, v_maskScene, v_candideScene;
var v_camera, v_backgroundCamera, v_maskCamera, v_candideCamera;
var v_time;
var v_ppixels;
var v_pixels;
var v_video;
var v_videoCanvas;
var v_videoContext;
var v_videoTexture;
var v_frameCanvas ;
var v_frameContext ;
var v_glasses;
var v_mask, v_candideMask;
var v_z_offset;
var v_z_increment;
var v_scale_factor;
var v_scale_increment;
this.v_tracker_pom = 0;
var v_tracker;
var v_faceData;
var v_trackingStatus;
var v_tracking = false;
var v_faceDataArray;
var v_container;
var v_urlOccluder;
var v_urlObject;
/*
* Calculates parameters that change related to filling (croping) strategy
*/
var setupViewingFrustrum = function(input_w, input_h, output_w, output_h) {
var parameters = [];
var input_aspect = input_w / input_h;
var output_aspect = output_w / output_h;
parameters.perspective = [];
parameters.orto = [];
parameters.perspective.output_aspect = output_aspect;
if(input_aspect >= output_aspect){
//fillY
parameters.orto.left = - (input_h * output_aspect) / 2;
parameters.orto.right = (input_h * output_aspect) / 2;
parameters.orto.top = input_h / 2;
parameters.orto.bottom = - input_h / 2;
if(input_w >= input_h){
parameters.perspective.fov = 2 * Math.atan( 1 / 3 ) * 180 / Math.PI;
} else {
parameters.perspective.fov = 2 * Math.atan(1 / input_aspect / 3) * 180 / Math.PI;
}
} else {
//fillX
parameters.orto.left = - input_w / 2;
parameters.orto.right = input_w / 2;
parameters.orto.top = (input_w * 1 / output_aspect) / 2;
parameters.orto.bottom = - (input_w * 1 / output_aspect) / 2;
if(input_w >= input_h){
parameters.perspective.fov = 2 * Math.atan( 2 * input_w / input_h * 1 / output_aspect / (2 * 3) ) * 180 / Math.PI;
} else {
parameters.perspective.fov = 2 * Math.atan( 2 * 1 / output_aspect / (2 * 3) ) * 180 / Math.PI;
}
}
return parameters;
}
var initialize_ = function(container, video) {
// get camera stream size
v_width = video.videoWidth;
v_height = video.videoHeight;
// get output size
output_w = video.width;
output_h = video.height;
v_container = container;
v_ppixels = VisageModule._malloc(v_width*v_height*4);
v_pixels = new Uint8Array(VisageModule.HEAPU8.buffer, v_ppixels, v_width*v_height*4);
// create webcam canvas
v_video = video;
v_videoCanvas = document.createElement('canvas');
v_videoCanvas.width = v_width;
v_videoCanvas.height = v_height;
v_videoContext = v_videoCanvas.getContext('2d', {willReadFrequently: true});
v_videoCanvas.setAttribute('style', 'display: none');
v_frameCanvas = document.createElement('canvas');
v_frameCanvas.width = v_width;
v_frameCanvas.height = v_height;
v_frameContext = v_frameCanvas.getContext('2d', {willReadFrequently: true});
v_frameCanvas.setAttribute('style', 'display: none; z-index: 1');
v_videoTexture = new THREE.Texture(v_frameCanvas);
// init tracker
this.v_tracker_pom = new VisageModule.VisageTracker("Head Tracker.cfg");
v_faceDataArray = new VisageModule.FaceDataVector();
v_faceDataArray.push_back(new VisageModule.FaceData());
v_tracker = this.v_tracker_pom;
// setup renderer
v_renderer = new THREE.WebGLRenderer({antialias: true});
v_renderer.setClearColor(0x0055FF, 1);
v_renderer.autoClearDepth = false;
v_renderer.autoClear = false;
v_renderer.autoClearColor = false;
v_renderer.setSize(output_w, output_h);
v_renderer.sortObject = false;
v_renderer.toneMappingExposure = 1;
v_renderer.shadowMap.enabled = true;
v_renderer.setPixelRatio( window.devicePixelRatio );
container.appendChild(v_renderer.domElement);
// setup scenes
v_scene = new THREE.Scene();
v_backgroundScene = new THREE.Scene();
v_maskScene = new THREE.Scene();
v_candideScene = new THREE.Scene();
v_time = new THREE.Clock(true);
var parameters = setupViewingFrustrum(v_width, v_height, output_w, output_h);
var left = parameters.orto.left;
var right = parameters.orto.right;
var top = parameters.orto.top;
var bottom = parameters.orto.bottom;
// setup video plane camera
v_backgroundCamera = new THREE.OrthographicCamera( left, right, top, bottom, 0.1, 1000);
v_backgroundCamera.lookAt(new THREE.Vector3(0, 0, -1));
v_backgroundScene.add(v_backgroundCamera);
// setup video plane
var plane = new THREE.Mesh(new THREE.PlaneGeometry(v_width, v_height, 1, 1), new THREE.MeshBasicMaterial({color: 0xFFFFFF, map: v_videoTexture}));
plane.position.set(0, 0, -500);
v_backgroundScene.add(plane);
var fov = parameters.perspective.fov;
var output_aspect = parameters.perspective.output_aspect;
// setup glasses camera
v_camera = new THREE.PerspectiveCamera(fov, output_aspect, 0.1, 1000);
v_camera.lookAt(new THREE.Vector3(0, 0, 1));
v_scene.add(v_camera);
// setup mask camera
v_maskCamera = new THREE.PerspectiveCamera(fov, output_aspect, 0.1, 1000);
v_maskCamera.lookAt(new THREE.Vector3(0, 0, 1));
v_maskScene.add(v_maskCamera);
v_candideCamera = new THREE.PerspectiveCamera(fov, output_aspect, 0.1, 1000);
v_candideCamera.lookAt(new THREE.Vector3(0, 0, 1));
v_candideScene.add(v_candideCamera);
// setup masking cube
var geometry = new THREE.CubeGeometry(0.1, 1, 0.2);
var geometry2 = new THREE.CubeGeometry(0.5, 1, 0.2);
var material = new THREE.MeshBasicMaterial({color: 0x000000, opacity: 0, transparent: true});
v_mask = new THREE.Object3D();
var v_maskChild = new THREE.Mesh(geometry, material);
v_maskChild.position.set(0, 0, -0.1);
var v_maskChild2 = new THREE.Mesh(geometry2, material);
v_maskChild2.position.set(0, 0, -0.18);
v_maskScene.add(v_mask);
// setup ambient light
var ambientLight = new THREE.AmbientLight(0xFFFFFF);
v_scene.add(ambientLight);
// setup point light
var pointLight = new THREE.PointLight(0xA0A0A0, 0.1, 0.0, 2);
pointLight.position.set(0, 0, 0);
v_scene.add(pointLight);
}
this.getScene = function () {
return v_scene;
}
/**
* Initializes Visage AR and sets up rendering and tracking. The video resolution is used for the canvas resolution.
* @param {element}container - The HTML element in which to put the rendering canvas.
* @param {element}video - The HTML video element required for camera access.
*/
this.initialize = initialize_;
function splitString(url)
{
var string = url;
var rest = string.substring(0, string.lastIndexOf("/") + 1);
var last = string.substring(string.lastIndexOf("/") + 1, string.length);
return [rest, last];
}
var loadOccluder_ = function(urlOccluder) {
v_urlOccluder = urlOccluder;
var maskLoader = new THREE.OBJLoader();
var manager = new THREE.LoadingManager();
var mtlLoader = new THREE.MTLLoader(manager);
var temp = splitString(urlOccluder);
var pathMtl = temp[0];
var model = temp[1];
mtlLoader.setPath(pathMtl);
mtlLoader.load( model + ".mtl", function( materials ) {
materials.preload();
maskLoader.setMaterials( materials );
maskLoader.load(urlOccluder + ".obj", function ( object )
{
if (v_candideMask)
v_mask.remove(v_candideMask);
if (typeof object.children === 'undefined' || object.children.length != 1)
{
console.log ("Error - occluder object invalid");
}
object.children[0].material.transparent = true;
object.children[0].material.opacity = 0;
v_candideMask = new THREE.Object3D();
v_candideMask.add(object.children[0]);
v_mask.add(v_candideMask);
//remove the occluder object from original glasses
object.remove(object.children[0]);
});
});
}
/**
* Loads the occlusion mask in OBJ format and its material in MTL format.
*
modeling guide on how to prepare models for use with VisageAR.
* @param {string} urlObject - The URL from which to load the model, without the extension.
*/
this.loadObject = loadObject_;
/**
* Starts tracking the face and displaying (rendering) any 3D objects loaded using loadObject() function. Object is
* overlayed on the face.
*/
this.startTracking = function() {
v_tracking = true;
}
/**
* Stops tracking.
*/
this.stopTracking = function() {
v_tracking = false;
}
this.setIPD = function(ipd) {
if (v_tracking = true && v_tracker)
{
//convert to meters
v_tracker.setIPD(ipd/1000);
}
}
/*
* Updates the tracker with a new video image.
*/
var updateTracker = function() {
// update video texture
if (v_video.readyState === v_video.HAVE_ENOUGH_DATA) {
v_videoContext.drawImage(v_video, 0, 0, v_width, v_height);
v_videoTexture.needsUpdate = true;
}
if (!v_tracking)
return;
// fetch image data from canvas
var dataBuffer = v_videoContext.getImageData(0, 0, v_width, v_height);
var imageData = dataBuffer.data;
//
v_pixels.set(imageData);
v_frameContext.putImageData(dataBuffer,0 ,0);
//
v_trackingStatus = v_tracker.track(
v_width,
v_height,
v_ppixels,
v_faceDataArray,
VisageModule.VisageTrackerImageFormat.VISAGE_FRAMEGRABBER_FMT_RGBA.value,
VisageModule.VisageTrackerOrigin.VISAGE_FRAMEGRABBER_ORIGIN_TL.value,
0,
-1
);
// fetch faceData of the first face
v_faceData = v_faceDataArray.get(0);
}
/*
* Updates the glasses model position and the face mask position.
*/
var update = function() {
if (v_tracking && v_trackingStatus[0] === VisageModule.VisageTrackerStatus.TRACK_STAT_OK.value)
{
// move mask
v_mask.position.set(v_faceData.getFaceTranslation()[0], v_faceData.getFaceTranslation()[1], v_faceData.getFaceTranslation()[2]);
v_mask.rotation.set(v_faceData.getFaceRotation()[0], v_faceData.getFaceRotation()[1] + 3.14, v_faceData.getFaceRotation()[2]);
v_mask.rotation.order = "YXZ";
// move glasses
v_glasses.position.set(v_faceData.getFaceTranslation()[0], v_faceData.getFaceTranslation()[1], v_faceData.getFaceTranslation()[2]);
v_glasses.rotation.set(v_faceData.getFaceRotation()[0], v_faceData.getFaceRotation()[1] + 3.14, v_faceData.getFaceRotation()[2]);
v_glasses.rotation.order = "YXZ";
} else {
v_mask.position.set(0, 0, -5);
v_glasses.position.set(0, 0, -5);
}
}
/*
* Renders the scene.
*/
var render = function() {
v_renderer.clear(1, 1, 1);
v_renderer.render(v_backgroundScene, v_backgroundCamera);
v_renderer.clear(0, 1, 1);
v_renderer.render(v_maskScene, v_maskCamera);
v_renderer.render(v_scene, v_camera);
}
var clear_ = function() {
//dispose three.js objects
if(v_backgroundScene != undefined)
v_backgroundScene.remove.apply(v_backgroundScene, v_backgroundScene.children);
if(v_scene != undefined)
v_scene.remove.apply(v_scene, v_scene.children);
if(v_maskScene != undefined)
v_maskScene.remove.apply(v_maskScene, v_maskScene.children);
if(v_candideScene != undefined)
v_candideScene.remove.apply(v_candideScene, v_candideScene.children);
//free v_ppixels
if(this.v_ppixels) {
VisageModule._free(this.v_ppixels);
}
//remove canvas
if(v_renderer != undefined)
v_renderer.domElement.remove();
}
/*
* Checks if video dimension have changed and reinitalizes scene, 3D objects, pixels buffer and canvas.
*/
var updateOrientation = function() {
if(v_video.videoWidth == v_width
&& v_video.videoHeight == v_height ){
return;
}
clear_();
initialize_(v_container, v_video);
loadOccluder_(v_urlOccluder);
loadObject_(v_urlObject);
}
/**
*
* Clears the scene, 3D objects, pixels buffer and canvas for displaying the frame
*/
this.clear = clear_;
/*
* Main loop.
*/
var loop = function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
requestAnimationFrame(loop);
if (!v_video)
return;
updateOrientation();
updateTracker();
update();
render();
};
/**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
loop();
}