Skip to content

Instantly share code, notes, and snippets.

@keiranlovett
Last active January 5, 2021 21:05
Show Gist options
  • Save keiranlovett/ee0683e6c4ac13f9aeb6956466e5a71d to your computer and use it in GitHub Desktop.
Save keiranlovett/ee0683e6c4ac13f9aeb6956466e5a71d to your computer and use it in GitHub Desktop.
Facebook Camera Effects - Toggle various elements on tap.
/* ----------- 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