UNPKG

mind-ar

Version:

web augmented reality framework

330 lines (281 loc) 9.7 kB
//const {Controller, UI} = window.MINDAR.FACE; import {Controller,UI} from './index.js' const THREE = AFRAME.THREE; const needsDOMRefresh=document.readyState === 'complete'||document.readyState=='interactive'; //console.log("Registering custom AFRAME stuff. Needs Refresh:",needsDOMRefresh,document.readyState); AFRAME.registerSystem('mindar-face-system', { container: null, video: null, shouldFaceUser: true, lastHasFace: false, init: function() { this.anchorEntities = []; this.faceMeshEntities = []; }, setup: function({uiLoading, uiScanning, uiError, filterMinCF, filterBeta}) { this.ui = new UI({uiLoading, uiScanning, uiError}); this.filterMinCF = filterMinCF; this.filterBeta = filterBeta; }, registerFaceMesh: function(el) { this.faceMeshEntities.push({el}); }, registerAnchor: function(el, anchorIndex) { this.anchorEntities.push({el: el, anchorIndex}); }, start: function() { this.ui.showLoading(); this.container = this.el.sceneEl.parentNode; //this.__startVideo(); this._startVideo(); }, stop: function() { this.pause(); const tracks = this.video.srcObject.getTracks(); tracks.forEach(function(track) { track.stop(); }); this.video.remove(); }, switchCamera: function() { this.shouldFaceUser = !this.shouldFaceUser; this.stop(); this.start(); }, pause: function(keepVideo=false) { if (!keepVideo) { this.video.pause(); } this.controller.stopProcessVideo(); }, unpause: function() { this.video.play(); this.controller.processVideo(this.video); }, // mock a video with an image __startVideo: function() { this.video = document.createElement("img"); this.video.onload = async () => { this.video.videoWidth = this.video.width; this.video.videoHeight = this.video.height; await this._setupAR(); this._processVideo(); this.ui.hideLoading(); } this.video.style.position = 'absolute' this.video.style.top = '0px' this.video.style.left = '0px' this.video.style.zIndex = '-2' this.video.src = "./assets/face1.jpeg"; this.container.appendChild(this.video); }, _startVideo: function() { this.video = document.createElement('video'); this.video.setAttribute('autoplay', ''); this.video.setAttribute('muted', ''); this.video.setAttribute('playsinline', ''); this.video.style.position = 'absolute' this.video.style.top = '0px' this.video.style.left = '0px' this.video.style.zIndex = '-2' this.container.appendChild(this.video); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { this.el.emit("arError", {error: 'VIDEO_FAIL'}); this.ui.showCompatibility(); return; } navigator.mediaDevices.getUserMedia({audio: false, video: { facingMode: (this.shouldFaceUser? 'face': 'environment'), }}).then((stream) => { this.video.addEventListener( 'loadedmetadata', async () => { this.video.setAttribute('width', this.video.videoWidth); this.video.setAttribute('height', this.video.videoHeight); await this._setupAR(); this._processVideo(); this.ui.hideLoading(); }); this.video.srcObject = stream; }).catch((err) => { console.log("getUserMedia error", err); this.el.emit("arError", {error: 'VIDEO_FAIL'}); }); }, _processVideo: function() { this.controller.onUpdate = ({hasFace, estimateResult}) => { if (hasFace && !this.lastHasFace) { this.el.emit("targetFound"); } if (!hasFace && this.lastHasFace) { this.el.emit("targetLost"); } this.lastHasFace = hasFace; if (hasFace) { const {faceMatrix} = estimateResult; for (let i = 0; i < this.anchorEntities.length; i++) { const landmarkMatrix = this.controller.getLandmarkMatrix(this.anchorEntities[i].anchorIndex); this.anchorEntities[i].el.updateVisibility(true); this.anchorEntities[i].el.updateMatrix(landmarkMatrix); } for (let i = 0; i < this.faceMeshEntities.length; i++) { this.faceMeshEntities[i].el.updateVisibility(true); this.faceMeshEntities[i].el.updateMatrix(faceMatrix); } } else { for (let i = 0; i < this.anchorEntities.length; i++) { this.anchorEntities[i].el.updateVisibility(false); } for (let i = 0; i < this.faceMeshEntities.length; i++) { this.faceMeshEntities[i].el.updateVisibility(false); } } } this.controller.processVideo(this.video); }, _setupAR: async function() { this.controller = new Controller({ filterMinCF: this.filterMinCF, filterBeta: this.filterBeta, }); this._resize(); await this.controller.setup(this.video); await this.controller.dummyRun(this.video); const {fov, aspect, near, far} = this.controller.getCameraParams(); const camera = new THREE.PerspectiveCamera(); camera.fov = fov; camera.aspect = aspect; camera.near = near; camera.far = far; camera.updateProjectionMatrix(); const cameraEle = this.container.getElementsByTagName("a-camera")[0]; cameraEle.setObject3D('camera', camera); cameraEle.setAttribute('camera', 'active', true); for (let i = 0; i < this.faceMeshEntities.length; i++) { this.faceMeshEntities[i].el.addFaceMesh(this.controller.createThreeFaceGeometry(THREE)); } this._resize(); window.addEventListener('resize', this._resize.bind(this)); this.el.emit("arReady"); }, _resize: function() { const video = this.video; const container = this.container; let vw, vh; // display css width, height const videoRatio = video.videoWidth / video.videoHeight; const containerRatio = container.clientWidth / container.clientHeight; if (videoRatio > containerRatio) { vh = container.clientHeight; vw = vh * videoRatio; } else { vw = container.clientWidth; vh = vw / videoRatio; } this.video.style.top = (-(vh - container.clientHeight) / 2) + "px"; this.video.style.left = (-(vw - container.clientWidth) / 2) + "px"; this.video.style.width = vw + "px"; this.video.style.height = vh + "px"; const sceneEl = container.getElementsByTagName("a-scene")[0]; sceneEl.style.top = this.video.style.top; sceneEl.style.left = this.video.style.left; sceneEl.style.width = this.video.style.width; sceneEl.style.height = this.video.style.height; } }); AFRAME.registerComponent('mindar-face', { dependencies: ['mindar-face-system'], schema: { autoStart: {type: 'boolean', default: true}, faceOccluder: {type: 'boolean', default: true}, uiLoading: {type: 'string', default: 'yes'}, uiScanning: {type: 'string', default: 'yes'}, uiError: {type: 'string', default: 'yes'}, filterMinCF: {type: 'number', default: -1}, filterBeta: {type: 'number', default: -1}, }, init: function() { const arSystem = this.el.sceneEl.systems['mindar-face-system']; if (this.data.faceOccluder) { const faceOccluderMeshEntity = document.createElement('a-entity'); faceOccluderMeshEntity.setAttribute("mindar-face-default-face-occluder", true); this.el.sceneEl.appendChild(faceOccluderMeshEntity); } arSystem.setup({ uiLoading: this.data.uiLoading, uiScanning: this.data.uiScanning, uiError: this.data.uiError, filterMinCF: this.data.filterMinCF === -1? null: this.data.filterMinCF, filterBeta: this.data.filterBeta === -1? null: this.data.filterBeta, }); if (this.data.autoStart) { this.el.sceneEl.addEventListener('renderstart', () => { arSystem.start(); }); } }, }); AFRAME.registerComponent('mindar-face-target', { dependencies: ['mindar-face-system'], schema: { anchorIndex: {type: 'number'}, }, init: function() { const arSystem = this.el.sceneEl.systems['mindar-face-system']; arSystem.registerAnchor(this, this.data.anchorIndex); const root = this.el.object3D; root.visible = false; root.matrixAutoUpdate = false; }, updateVisibility(visible) { this.el.object3D.visible = visible; }, updateMatrix(matrix) { const root = this.el.object3D; root.matrix.set(...matrix); } }); AFRAME.registerComponent('mindar-face-occluder', { init: function() { const root = this.el.object3D; this.el.addEventListener('model-loaded', () => { this.el.getObject3D('mesh').traverse((o) => { if (o.isMesh) { const material = new THREE.MeshStandardMaterial({ colorWrite: false, }); o.material = material; } }); }); }, }); AFRAME.registerComponent('mindar-face-default-face-occluder', { init: function() { const arSystem = this.el.sceneEl.systems['mindar-face-system']; arSystem.registerFaceMesh(this); const root = this.el.object3D; root.matrixAutoUpdate = false; }, updateVisibility(visible) { this.el.object3D.visible = visible; }, updateMatrix(matrix) { const root = this.el.object3D; root.matrix.set(...matrix); }, addFaceMesh(faceGeometry) { const material = new THREE.MeshBasicMaterial({colorWrite: false}); //const material = new THREE.MeshBasicMaterial({colorWrite: '#CCCCCC'}); const mesh = new THREE.Mesh(faceGeometry, material); this.el.setObject3D('mesh', mesh); }, }); /* This is a hack. If the user's browser has cached A-Frame, then A-Frame will process the webpage *before* the system and components get registered. Resulting in a blank page. This happens because module loading is deferred. */ /* if(needsDOMRefresh){ console.log("mindar-face-aframe::Refreshing DOM...") document.body.innerHTML=document.body.innerHTML; } */