Last active
January 5, 2021 21:05
-
-
Save keiranlovett/ee0683e6c4ac13f9aeb6956466e5a71d to your computer and use it in GitHub Desktop.
Facebook Camera Effects - Toggle various elements on tap.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ----------- API ------------- *\ | |
// Available modules include (this is not a complete list): | |
var Scene = require('Scene'); | |
var Textures = require('Textures'); | |
var Materials = require('Materials'); | |
var FaceTracking = require('FaceTracking'); | |
var Animation = require('Animation'); | |
var Reactive = require('Reactive'); | |
// Example script | |
// Loading required modules | |
var Scene = require('Scene'); | |
var FaceTracking = require('FaceTracking'); | |
// Binding an object's property to a value provided by the face tracker | |
Scene.root.child('object0').transform.rotationY = FaceTracking.face(0).transform.rotationX; | |
// If you want to log objects, use the Diagnostics module. | |
var Diagnostics = require('Diagnostics'); | |
Diagnostics.log(Scene.root); | |
*/ | |
//Dependencies | |
const S = require('Scene'); | |
const FT = require('FaceTracking'); | |
const FG = require('FaceGestures'); | |
const R = require('Reactive'); | |
const D = require('Diagnostics'); | |
const A = require('Animation'); | |
const T = require("TouchGestures"); | |
const AU = require("Audio"); | |
const TE = require("Textures"); | |
const M = require('Materials'); | |
const I = require('Instruction'); | |
I.automaticInstructionEnabled = false; | |
I.show('TAP_TO_CHANGE'); | |
const face = FT.face(0); | |
// Monitor facial features to trigger new activity. | |
var mouthDriver = A.valueDriver(face.mouth.openness, 0.2, 0.4); | |
var graphicOpacitySampler = A.samplers.linear(0, 1); | |
var graphicOpacityAnimation = A.animate(mouthDriver, graphicOpacitySampler).expSmooth(100); | |
//Editor Components | |
var body; | |
var effectElement; | |
var effectElements = []; | |
var effectCurrent = 0; | |
var effectTotal = 4; | |
var bodyTransform = getBodyTransform(face, 5, 20, 15); | |
var faceMesh = S.root.find('facehigh0'); | |
var signMesh = S.root.find('planeSign'); | |
var canvas = S.root.find('2DCanvasEffects'); | |
var bodyMeshes = []; | |
var bodyMeshCount = 2; | |
var light_rotate_time = 300000; | |
var currentFace = 0; | |
var maxFace = 13; | |
var signCurrent = 0; | |
var signTotal = 3; | |
var texture_patterns = TE.get('pattern-animation'); | |
var texture_sign = TE.get('sign-animation'); | |
var material_sign = M.get("M_Sign"); | |
function getBodyRotator(faceRotation, defaultMultiplier, minHeadRotation, maxHeadRotation) { | |
var maxRatio = R.max(defaultMultiplier, | |
FT.count.lt(1).or(faceRotation.eq(0)).ifThenElse( | |
defaultMultiplier, | |
faceRotation.lt(0).ifThenElse( | |
faceRotation.sub(toRadians(minHeadRotation)), | |
faceRotation.sub(toRadians(maxHeadRotation))).div(faceRotation))); | |
return maxRatio.mul(faceRotation); | |
} | |
function getBodyTransform(face) { | |
var bodyTranslateSmooth = 20; | |
var bodyRotationSmooth = 25; | |
var defaultRotationXMultiplier = 0.15; | |
var defaultRotationYMultiplier = 0.25; | |
var defaultRotationZMultiplier = 0.15; | |
var minHeadRotationX = -15; | |
var maxHeadRotationX = 30; | |
var maxHeadRotationY = 25; | |
var maxHeadRotationZ = 50; | |
var baseOfNeck = calculateBaseOfNeck(face); | |
return { | |
x: baseOfNeck.x.expSmooth(bodyTranslateSmooth), | |
y: baseOfNeck.y.expSmooth(bodyTranslateSmooth), | |
z: baseOfNeck.z.expSmooth(bodyTranslateSmooth), | |
rotationX: getBodyRotator(face.cameraTransform.rotationX, defaultRotationXMultiplier, minHeadRotationX, maxHeadRotationX).expSmooth(bodyRotationSmooth), | |
rotationY: getBodyRotator(face.cameraTransform.rotationY, defaultRotationYMultiplier, -maxHeadRotationY, maxHeadRotationY).expSmooth(bodyRotationSmooth), | |
rotationZ: getBodyRotator(face.cameraTransform.rotationZ, defaultRotationZMultiplier, -maxHeadRotationZ, maxHeadRotationZ).expSmooth(bodyRotationSmooth) | |
}; | |
} | |
function calculateBaseOfNeck(face) { | |
var faceToCenterOfHeadDepth = 120; | |
var centerOfHeadToBaseOfNeck = 100; | |
return { | |
x: face.cameraTransform.x | |
.sub(R.sin(face.cameraTransform.rotationY).mul(faceToCenterOfHeadDepth).mul(1)) | |
.sum(R.sin(face.cameraTransform.rotationZ).mul(centerOfHeadToBaseOfNeck).mul(0.75)) | |
.sub(R.sin(face.cameraTransform.rotationZ).mul(R.sin(face.cameraTransform.rotationX)).mul(centerOfHeadToBaseOfNeck)), | |
y: face.cameraTransform.y | |
.sum(R.sin(face.cameraTransform.rotationX).mul(faceToCenterOfHeadDepth).mul(1.2)) | |
.sub(R.cos(face.cameraTransform.rotationZ).mul(centerOfHeadToBaseOfNeck).mul(0.5)), | |
z: face.cameraTransform.z.sub(R.val(faceToCenterOfHeadDepth)) | |
.sum(R.sin(face.cameraTransform.rotationX).abs().mul(faceToCenterOfHeadDepth / 4)) | |
.sum(R.sin(face.cameraTransform.rotationY).abs().mul(faceToCenterOfHeadDepth / 3)) | |
}; | |
} | |
function updateTransformFromState(object, state) { | |
object.transform.x = state.x; | |
object.transform.y = state.y; | |
object.transform.z = state.z; | |
object.transform.rotationX = state.rotationX; | |
object.transform.rotationY = state.rotationY; | |
object.transform.rotationZ = state.rotationZ; | |
} | |
function setBodyMeshProperties(i) { | |
//Hide current before we select the next | |
if(body) { | |
body.hidden = true; | |
} | |
if (i == bodyMeshCount-1){ | |
body = bodyMeshes[0].mover; | |
} else { | |
body = bodyMeshes[i+1].mover; | |
} | |
body.hidden = false; | |
updateTransformFromState(body, bodyTransform); | |
} | |
function setFaceMeshProperties(i) { | |
if (currentFace == maxFace){ | |
currentFace = 0; | |
} else { | |
currentFace = currentFace+i; | |
} | |
texture_patterns.currentFrame = currentFace; | |
} | |
function setSignProperties(i) { | |
if (signCurrent == signTotal){ | |
signCurrent = 0; | |
} else { | |
signCurrent = signCurrent+i; | |
} | |
texture_sign.currentFrame = signCurrent; | |
} | |
function setEffectProperties(i) { | |
//Hide current before we select the next | |
if(effectElement) { | |
effectElement.hidden = true; | |
} | |
if(effectCurrent == effectTotal){ | |
effectCurrent = 0; | |
} else { | |
effectCurrent = effectCurrent+i; | |
} | |
effectElement = effectElements[effectCurrent].mover; | |
effectElement.hidden = false; | |
} | |
function trackMouthOpen() { | |
material_sign.opacity = graphicOpacityAnimation; | |
} | |
//Tap on body mesh to toggle form | |
var tapRegistrarBody = function(mesh, i) { | |
T.onTap(mesh.mover).subscribe(function(event) { | |
// AU.play(S.root.child("Device").child("Camera").child("Focal Distance").child("audiosource0")); | |
setBodyMeshProperties(i); | |
}); | |
} | |
//Tap on face mesh to toggle form | |
var tapRegistrarFace = function(mesh, i) { | |
T.onTap(mesh).subscribe(function(event) { | |
setFaceMeshProperties(i); | |
}); | |
} | |
var tapRegistrarSign = function(mesh, i) { | |
T.onTap(mesh).subscribe(function(event) { | |
setSignProperties(i); | |
}); | |
} | |
//Tap on body mesh to toggle form | |
var tapRegistrarCanvas = function(mesh, i) { | |
T.onTap(mesh).subscribe(function(event) { | |
setEffectProperties(i); | |
}); | |
} | |
FT.count.monitor().subscribe(function (updatedCount) { | |
if (updatedCount.newValue > 0) { | |
trackMouthOpen(); | |
S.root.child("Device").child("Camera").child("Focal Distance").child("MeshGroup").hidden = false; | |
} else { | |
S.root.child("Device").child("Camera").child("Focal Distance").child("MeshGroup").hidden = true; | |
} | |
}); | |
FG.hasEyebrowsRaised(face).monitor().subscribe(function(changedValue) { | |
if (changedValue.newValue) { | |
canvas.hidden = false; | |
} else { | |
canvas.hidden = true; | |
} | |
}); | |
//ASSIGN TAPS | |
tapRegistrarFace(faceMesh, 1); | |
tapRegistrarSign(signMesh, 1); | |
tapRegistrarCanvas(canvas, 1); | |
for (var i=0; i<bodyMeshCount; i++){ | |
bodyMeshes[i] = { | |
mover: S.root.child("Device").child("Camera").child("Focal Distance").child("MeshGroup").child("mesh_"+i) | |
} | |
tapRegistrarBody(bodyMeshes[i], i); | |
} | |
for (var i=0; i<effectTotal; i++){ | |
effectElements[i] = { | |
mover: S.root.child("Device").child("Camera").child("Focal Distance").child("2DCanvasEffects").child("effect_"+i) | |
} | |
effectElements[i].mover.hidden = true; | |
} | |
canvas.hidden = true; | |
setBodyMeshProperties(0); | |
setEffectProperties(0); | |
trackMouthOpen(); | |
//HELPER FUNCTIONS | |
function getRandomArbitrary(min, max) { | |
return Math.random() * (max - min) + min; | |
} | |
function toRadians (angle) { | |
return angle * (Math.PI / 180); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment