function effectsEngine(effectName){
	function loadScript(url){
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.src = url;
		script.onload = function(){
			this.remove();
			if (loadList.length){
				loadScript(loadList.pop());
			}
		}
		document.head.appendChild(script);
	}
	var loadList = [];
	if (typeof JEELIZFACEFILTER == 'undefined' || JEELIZFACEFILTER==null){
		loadList.push("./thirdparty/jeeliz/jeelizFaceFilter.js");
	} 
	if (typeof THREE == 'undefined' || THREE == null){
		loadList.push("./thirdparty/jeeliz/three/v112/three.min.js");
	} else {
		console.log("typeof THREE:"+typeof THREE);
	}
	if (typeof JeelizThreeHelper == 'undefined' || JeelizThreeHelper==null){
		loadList.push("./thirdparty/jeeliz/JeelizThreeHelper.js");
	}
	if (typeof TWEEN == 'undefined' || TWEEN == null){
		loadList.push("./thirdparty/jeeliz/Tween.min.js");
	}
	
	loadList.push("./filters/dog/libs/glfx.js");
	loadList.push("./thirdparty/jeeliz/three/customMaterials/FlexMaterial/ThreeFlexMaterial.js");
	
	if (loadList.length){
		loadList.reverse();
		loadScript(loadList.pop());
	}
	
	
	var pathname = window.location.pathname.split("/");
	pathname.pop();
	pathname = window.location.protocol + "//" + window.location.host + pathname.join("/");

	// some globals:
	let THREECAMERA = null; // should be prop of window
	let isTransformed = false;
	
	let ISDETECTED = false;
	let 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 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 = './filters/dog/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(
		'./filters/dog/models/dog/dog_ears.json',
		function (geometry) {
		  const mat = new THREE.FlexMaterial({
			map: new THREE.TextureLoader().load('./filters/dog/models/dog/texture_ears.jpg'),
			flexMap: new THREE.TextureLoader().load('./filters/dog/models/dog/flex_ears_256.jpg'),
			alphaMap: new THREE.TextureLoader().load('./filters/dog/models/dog/alpha_ears_256.jpg'),
			transparent: true,
			opacity: 1,
			bumpMap: new THREE.TextureLoader().load('./filters/dog/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(
		'./filters/dog/models/dog/dog_nose.json',
		function (geometry) {
		  const mat = new THREE.MeshPhongMaterial({
			map: new THREE.TextureLoader().load('./filters/dog/models/dog/texture_nose.jpg'),
			shininess: 1.5,
			specular: 0xffffff,
			bumpMap: new THREE.TextureLoader().load('./filters/dog/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;
		}
	  );

	  loadingManager.onLoad = () => {
		DOGOBJ3D.add(EARMESH);
		DOGOBJ3D.add(NOSEMESH);
		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;
		  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;
			  ISANIMATIONOVER = true;
			}, 150);
		  })
		  .start();
	  }
	}


	// entry point:
	function main(){
		if (session.canvasSource && document.getElementById("effectsCanvasTarget") && JEELIZFACEFILTER){
			try {
				warnlog("LOADING JEELIZ");
				THREECAMERA = null; // should be prop of window
				isTransformed = false;
				DOGOBJ3D = new THREE.Object3D();
				FRAMEOBJ3D = new THREE.Object3D();
				
				init_faceFilter("effectsCanvasTarget", session.canvasSource);
			} catch(e){
			}
		} else {
			setTimeout(function(){main();},500);
			warnlog("...retrying to load");
		}
	}


	function init_faceFilter(canvasId, videoElement){
	  JEELIZFACEFILTER.init({
		canvasId: canvasId,
		NNCPath: pathname+'/thirdparty/jeeliz/neuralNets/',
		videoSettings: {
			videoElement: videoElement
		},
		callbackReady: function (errCode, spec) {
			if (errCode) {
				errorlog(errCode);
				try{
					JEELIZFACEFILTER.destroy();
				} catch(e){}
				THREECAMERA = null; // should be prop of window
				DOGOBJ3D=null;
				FRAMEOBJ3D=null;
				isTransformed = false;
				setTimeout(function(){main();},500);
				return;
			}
			init_threeScene(spec);
		},
		callbackTrack: function (detectState) {
			if (effectName !== session.effects){
				try{
					JEELIZFACEFILTER.toggle_pause(true,false); // unload the filter when no longer active.  Leaving the track active is required, else it breaks the app
				} catch(e){errorlog(e);}
				THREECAMERA = null; // should be prop of window
				isTransformed = false;
				DOGOBJ3D=null;
				FRAMEOBJ3D=null;
				return;
			}
			
			const ISDETECTED = JeelizThreeHelper.get_isDetected();
			//if (isDetected && detectState.expressions[0] >= 0.8 && !isTransformed) { // If the person opens their mouth wide, then activate..
			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 (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;
				}
			}

			TWEEN.update();

			if (ISOPAQUE) {
				MIXER.update(0.16);
			}

			JeelizThreeHelper.render(detectState, THREECAMERA);
		}
	  }); 
	}
	return main;
}
