"use strict"; // some globalz: let THREECAMERA = null; let ISDETECTED = false; let TONGUEMESH = null, NOSEMESH = null, EARMESH = null; let DOGOBJ3D = null, FRAMEOBJ3D = null; let ISOVERTHRESHOLD = false, ISUNDERTRESHOLD = true; let ISLOADED = false; let MIXER = null; let ACTION = null; let ISANIMATING = false; let ISOPAQUE = false; let ISTONGUEOUT = false; let ISANIMATIONOVER = false; let _flexParts = []; let _videoGeometry = null; // callback: launched if a face is detected or lost function detect_callback(isDetected) { if (isDetected) { console.log('INFO in detect_callback(): DETECTED'); } else { console.log('INFO in detect_callback(): LOST'); } } function create_mat2d(threeTexture, isTransparent){ // MT216: we put the creation of the video material in a func because we will also use it for the frame return new THREE.RawShaderMaterial({ depthWrite: false, depthTest: false, transparent: isTransparent, vertexShader: "attribute vec2 position;\n\ varying vec2 vUV;\n\ void main(void){\n\ gl_Position = vec4(position, 0., 1.);\n\ vUV = 0.5 + 0.5 * position;\n\ }", fragmentShader: "precision lowp float;\n\ uniform sampler2D samplerVideo;\n\ varying vec2 vUV;\n\ void main(void){\n\ gl_FragColor = texture2D(samplerVideo, vUV);\n\ }", uniforms:{ samplerVideo: { value: threeTexture } } }); } function applyFilter() { let canvas; try { canvas = fx.canvas(); } catch (e) { alert('Ow no! WebGL isn\'t supported...') return } const tempImage = new Image(512, 512); tempImage.src = './images/texture_pink.jpg'; tempImage.onload = () => { const texture = canvas.texture(tempImage); // Create the effet canvas.draw(texture).vignette(0.5, 0.6).update(); const canvasOpacity = document.createElement('canvas'); canvasOpacity.width = 512; canvasOpacity.height = 512; const ctx = canvasOpacity.getContext('2d'); ctx.globalAlpha = 0.2 ctx.drawImage(canvas, 0, 0, 512, 512); // Add the effect const calqueMesh = new THREE.Mesh(_videoGeometry, create_mat2d(new THREE.TextureLoader().load(canvasOpacity.toDataURL('image/png')), true)) calqueMesh.material.opacity = 0.2; calqueMesh.material.transparent = true; calqueMesh.renderOrder = 999; // render last calqueMesh.frustumCulled = false; FRAMEOBJ3D.add(calqueMesh); } } // build the 3D. called once when Jeeliz Face Filter is OK function init_threeScene(spec) { // INIT THE THREE.JS context const threeStuffs = JeelizThreeHelper.init(spec, detect_callback); _videoGeometry = threeStuffs.videoMesh.geometry; // CREATE OUR DOG EARS: // let's begin by creating a loading manager that will allow us to // have more control over the three parts of our dog model const loadingManager = new THREE.LoadingManager(); const loaderEars = new THREE.BufferGeometryLoader(loadingManager); loaderEars.load( './models/dog/dog_ears.json', function (geometry) { const mat = new THREE.FlexMaterial({ map: new THREE.TextureLoader().load('./models/dog/texture_ears.jpg'), flexMap: new THREE.TextureLoader().load('./models/dog/flex_ears_256.jpg'), alphaMap: new THREE.TextureLoader().load('./models/dog/alpha_ears_256.jpg'), transparent: true, opacity: 1, bumpMap: new THREE.TextureLoader().load('./models/dog/normal_ears.jpg'), bumpScale: 0.0075, shininess: 1.5, specular: 0xffffff, }); EARMESH = new THREE.Mesh(geometry, mat); EARMESH.scale.multiplyScalar(0.025); EARMESH.position.setY(-0.3); EARMESH.frustumCulled = false; EARMESH.renderOrder = 10000; EARMESH.material.opacity.value = 1; } ); // CREATE OUR DOG NOSE const loaderNose = new THREE.BufferGeometryLoader(loadingManager); loaderNose.load( './models/dog/dog_nose.json', function (geometry) { const mat = new THREE.MeshPhongMaterial({ map: new THREE.TextureLoader().load('./models/dog/texture_nose.jpg'), shininess: 1.5, specular: 0xffffff, bumpMap: new THREE.TextureLoader().load('./models/dog/normal_nose.jpg'), bumpScale: 0.005 }); NOSEMESH = new THREE.Mesh(geometry, mat); NOSEMESH.scale.multiplyScalar(0.018); NOSEMESH.position.setY(-0.05); NOSEMESH.position.setZ(0.15); NOSEMESH.frustumCulled = false; NOSEMESH.renderOrder = 10000; } ); // CREATE OUR DOG TONGUE const loaderTongue = new THREE.JSONLoader(loadingManager); loaderTongue.load( 'models/dog/dog_tongue.json', function (geometry) { geometry.computeMorphNormals(); const mat = new THREE.FlexMaterial({ map: new THREE.TextureLoader().load('./models/dog/dog_tongue.jpg'), flexMap: new THREE.TextureLoader().load('./models/dog/flex_tongue_256.png'), alphaMap: new THREE.TextureLoader().load('./models/dog/tongue_alpha_256.jpg'), transparent: true, morphTargets: true, opacity: 1 }); TONGUEMESH = new THREE.Mesh(geometry, mat); TONGUEMESH.material.opacity.value = 0; TONGUEMESH.scale.multiplyScalar(2); TONGUEMESH.position.setY(-0.28); TONGUEMESH.frustumCulled = false; TONGUEMESH.visible = false; if (!MIXER) { // the mixer is declared globally so we can use it in the THREE renderer MIXER = new THREE.AnimationMixer(TONGUEMESH); const clips = TONGUEMESH.geometry.animations; const clip = clips[0]; ACTION = MIXER.clipAction(clip); ACTION.noLoop = true; ACTION.play(); } } ); loadingManager.onLoad = () => { DOGOBJ3D.add(EARMESH); DOGOBJ3D.add(NOSEMESH); DOGOBJ3D.add(TONGUEMESH); addDragEventListener(DOGOBJ3D); threeStuffs.faceObject.add(DOGOBJ3D); ISLOADED = true; } // CREATE AN AMBIENT LIGHT const ambient = new THREE.AmbientLight(0xffffff, 0.8); threeStuffs.scene.add(ambient); // CREAT A DIRECTIONALLIGHT const dirLight = new THREE.DirectionalLight(0xffffff, 0.5); dirLight.position.set(100, 1000, 1000); threeStuffs.scene.add(dirLight); // CREATE THE CAMERA THREECAMERA = JeelizThreeHelper.create_camera(); threeStuffs.scene.add(FRAMEOBJ3D); // Add filter applyFilter(); } // end init_threeScene() function animateTongue (mesh, isReverse) { mesh.visible = true; if (isReverse) { ACTION.timescale = -1; ACTION.paused = false; setTimeout(() => { ACTION.paused = true; ISOPAQUE = false; ISTONGUEOUT = false; ISANIMATING = false; ISANIMATIONOVER = true; new TWEEN.Tween(mesh.material.opacity) .to({ value: 0 }, 150) .start(); }, 150); } else { ACTION.timescale = 1; ACTION.reset(); ACTION.paused = false; new TWEEN.Tween(mesh.material.opacity) .to({ value: 1 }, 100) .onComplete(() => { ISOPAQUE = true; setTimeout(() => { ACTION.paused = true; ISANIMATING = false; ISTONGUEOUT = true; ISANIMATIONOVER = true; }, 150); }) .start(); } } // Entry point: launched by body.onload() function main(){ DOGOBJ3D = new THREE.Object3D(); FRAMEOBJ3D = new THREE.Object3D(); JeelizResizer.size_canvas({ canvasId: 'jeeFaceFilterCanvas', callback: function(isError, bestVideoSettings){ init_faceFilter(bestVideoSettings); } }); } function init_faceFilter(videoSettings){ JEELIZFACEFILTER.init({ canvasId: 'jeeFaceFilterCanvas', NNCPath: '../../../neuralNets/', // root of NN_DEFAULT.json file videoSettings: videoSettings, callbackReady: function (errCode, spec) { if (errCode) { console.log('AN ERROR HAPPENS. SORRY BRO :( . ERR =', errCode); return; } console.log('INFO: JEELIZFACEFILTER IS READY'); init_threeScene(spec); }, // end callbackReady() // called at each render iteration (drawing loop) callbackTrack: function (detectState) { ISDETECTED = JeelizThreeHelper.get_isDetected(); if (ISDETECTED) { const _quat = new THREE.Quaternion(); const _eul = new THREE.Euler(); _eul.setFromQuaternion(_quat); // flex ears material: if (EARMESH && EARMESH.material.set_amortized){ EARMESH.material.set_amortized( EARMESH.getWorldPosition(new THREE.Vector3(0,0,0)), EARMESH.getWorldScale(new THREE.Vector3(0,0,0)), EARMESH.getWorldQuaternion(_eul), false, 0.1 ); } if (TONGUEMESH && TONGUEMESH.material.set_amortized){ TONGUEMESH.material.set_amortized( TONGUEMESH.getWorldPosition(new THREE.Vector3(0,0,0)), TONGUEMESH.getWorldScale(new THREE.Vector3(0,0,0)), TONGUEMESH.getWorldQuaternion(_eul), false, 0.3 ); } if (detectState.expressions[0] >= 0.85 && !ISOVERTHRESHOLD) { ISOVERTHRESHOLD = true; ISUNDERTRESHOLD = false; ISANIMATIONOVER = false; } if (detectState.expressions[0] <= 0.1 && !ISUNDERTRESHOLD) { ISOVERTHRESHOLD = false; ISUNDERTRESHOLD = true; ISANIMATIONOVER = false; } if (ISLOADED && ISOVERTHRESHOLD && !ISANIMATING && !ISANIMATIONOVER) { if (!ISTONGUEOUT) { ISANIMATING = true; animateTongue(TONGUEMESH); } else { ISANIMATING = true; animateTongue(TONGUEMESH, true); } } } TWEEN.update(); // Update the mixer on each frame: if (ISOPAQUE) { MIXER.update(0.16); } JeelizThreeHelper.render(detectState, THREECAMERA); } // end callbackTrack() }); // end JEELIZFACEFILTER.init call }