• GOWAAA

Async, Await Spark AR Scripting Guide For Beginners

Updated: 5 days ago

One of the most exciting (and most underrated) updates in Spark AR V91 is the support for Async/await scripting that makes scripting in promise a lot easier to understand! In this article tutorial, I am going to show a few examples of how you can use Async, Await to script in Spark AR much like the article I wrote on scripting in Promise.


For those unfamiliar with Async/await and are probably wondering what's the differences with promise, here's a brief explanation.

  • All functions that return a promise is an async function

  • Await is used to call an async function when it is resolved or rejected

  • Promise.all runs all promise in parallel

  • Await only runs the next line of code when the current promise is resolved

If the explanation above is too technical for you, you can just think of Async/await as a friendlier alternative for scripting in promise in Spark AR. You can learn both or just learn one to script smoothly without a problem in Spark AR.

Finding Objects In Spark AR With Scripting

//Pre Promise (V85) Way Of Finding Object
const plane = Scene.root.find('plane0')

//Post Promise (V85-90) Way of Finding Object
Promise.all([
 S.root.findFirst('plane0'),
]).then(function(results){
 const plane = results[0];
 plane.transform.x = 0.5;
})

From V85 - V90, one of the most common way of finding objects from Spark AR in scripting is to use Promise.all. With the support of async, await, you can now find objects in script easier with the following 2 methods.

//V91 Method 1
(async function (){
 const planes = await S.root.findByPath('**/plane*');
 const canvas = await S.root.findFirst('canvas0');
 const text = await S.root.findFirst('3dText0');
 const nullobj = await S.root.findFirst('nullObject0');

 planes.forEach(plane=>{
 plane.transform.x = 0.5;
    })

 canvas.transform.x = 0.1;

 text.text = 'yay to async!';

 nullobj.transform.y = 0.1;
})();

In method 1, you can write an empty async function, locate the objects from Spark AR with "await S.root.findFirst('your object') and assign a constant variable name to it just like pre promise time. After that, you can write all the code needed in your filter within the same async function and it will run the same as writing your code in .then(). Just make sure you bracket the whole async function and end off with another pair of brackets for the purpose of calling the function so the codes within it will run.

//Method 2
async function findobj(){
 const planes = await S.root.findByPath('**/plane*');
 const canvas = await S.root.findFirst('canvas0');
 const text = await S.root.findFirst('3dText0');
 const nullobj = await S.root.findFirst('nullObject0');

 return {
 planes: planes,
 canvas: canvas,
 text: text,
 nullobj: nullobj
    }
}

//calls the async function which returns a promise
findobj().then(obj =>{
 obj.planes.forEach(plane=>{
 plane.transform.x = 0.5;
    })

 obj.canvas.transform.x = 0.1;

 obj.text.text = 'yay to async!';

 obj.nullobj.transform.y = 0.1;
})

Method 2 is probably more organised but there is probably no difference in term of efficiency compared to method 1. You start by writing a findobj async function with the sole purpose of finding the objects from Spark AR, assign a constant variable and return them when all objects are found. Next, you can call the function findobj which will return a promise; in which case you need to use .then() just like promise.all and write all your code within it. The only difference as compared to using promise.all is you no longer need to remember which obj[num] refers to which objects! You get the object you need, you just need to type obj.yourobjectname which was defined in the async function findobj, within the return {}. As long as you assigned the obj name properly in the async function, calling the objects and changing their value is as simple as pre promise time. The downside of method 2 is that you have more lines of code to write as compared to method 1.


For both methods, if you have a new object to add halfway through your project, you would not need to worried about your arrangement of findFirst code and how it will affect the sequence which you called and assigned them like in promise.all. All you need is to assign a new variable name and call it in your code to use it!


NOTE: Each line of await is a promise waiting to be resolved before Spark AR will move on to the next line. So the efficiency of your Instagram filter might be affected especially when you have many objects to find from the project. If you are worried about this, stick back to using Promise.all to be safe as that method find objects in parallel instead of line by line using await.

Personally I prefer method 2, but I will show a mixture of both methods in the examples below.


Native UI Picker With Script


Even though we now have a ready to use patch for native UI, I still insist to use native UI as an example to prevent the art of native UI scripting from being forgotten.

const S = require('Scene');
const T = require('Textures');
const M = require('Materials');
const NativeUI = require('NativeUI');

//write an async function to find objects
async function findobj(){
 const plane = await S.root.findFirst('plane0');

 const button1 = await T.findFirst('1');
 const button2 = await T.findFirst('2');
 const button3 = await T.findFirst('3');

 const red = await M.findFirst('red');
 const blue = await M.findFirst('blue');
 const green = await M.findFirst('green');

 return {
 plane:plane,
 button1:button1,
 button2:button2,
 button3:button3,
 red:red,
 blue:blue,
 green:green
    }
};

//Calls the async function which return a promise, write the remaining codes in it
findobj().then(obj=>{
 //Setup configuration
 const configuration = {
 selectedIndex: 0,
 
 items: [
               {image_texture: obj.button1},
               {image_texture: obj.button2},
               {image_texture: obj.button3}
             ],
 mats: [
               {material: obj.red},
               {material: obj.green},
               {material: obj.blue}
             ]
 
 };
 
 const picker = NativeUI.picker;
 // Load configuration
 picker.configure(configuration);
 // Show the NativeUI Picker
 picker.visible = true;

 // This is a monitor that watches for the picker to be used.
 picker.selectedIndex.monitor().subscribe(function(val) {

 // When a button is selected, we select the corresponding material.
 // When they pick the first button then the first material loads, etc
 
  obj.plane.material = configuration.mats[val.newValue].material
 
    });

});

The code is definitely neater now without the need to count & assign obj[num].

Script To Patch Bridge


Lucky for us, there is no major change from V85 when you want to pass value from script to patch.

 var mynum = 10;
 //Pre Promise
 Patches.setScalarValue("num",mynum);
 //Post Promise
 Patches.inputs.setScalar("num",mynum);

Patch to script, however, has some minor change that makes our life easier.

//V85-V90 Promise.all method: 8 lines of code
Promise.all([
 S.root.findFirst('2DText0')
 //Finding object
 ]).then(function(results){
 const scoretext = results[0];
 
 P.outputs.getScalar('numnew').then(val=>{
 val.monitor().subscribe(({newValue}) => {
 scoretext.text = newValue.toString();
 }) })})

//V91 async,await method 1: 5 lines of code
(async function getvalue(){
 const value = await P.outputs.getScalar('numnew');
 const scoretext = await S.root.findFirst('2dText0');

 scoretext.text = value.toString();
})();

The number of lines of code reduces from 8 to 5, and most important of all, the async, await method makes a lot more sense than promise.all when we just need to get a number from patch to script!


Look at API


Now we look at how async, await can be used to script look at API.

const S = require('Scene');
const R = require('Reactive');
const Time = require('Time');

async function findobj(){
 const plane = await S.root.findFirst('plane0');
 const followNull = await S.root.findFirst('followNull');
 const targetNull = await S.root.findFirst('targetNull');

 return{
 plane:plane,
 followNull:followNull,
 targetNull:targetNull
    }
};

findobj().then(obj => {
 // Finds an element in the scene
// - Parameters:
//      e: The object to find
//const find = e => S.root.find(e); *NOT NEEDED FOR V85*

// Gets the position of an object as an R.point
// - Parameters:
//      e: The object to get the transform from
const getPosition = e => R.point(e.transform.x, e.transform.y, e.transform.z);

// Sets the rotation based on a transform
// - Parameters:
//      e: The object to rotate
//      p: The transform to use
const setRotation = (e, p) => {
 e.transform.rotationX = p.rotationX;
 e.transform.rotationY = p.rotationY;
 e.transform.rotationZ = p.rotationZ;
};

// Look at utility function.
// Because of reactive stupidness, we can't actually apply the lookat directly to the looker itself
// We get around this by nesting the looker object inside a null with no values applied to it's transform
//
// - Parameters:
//      _target: The object in the scene you want to face
//      _lookerParent: The parent object of the object you want to rotate. Should have no transform applied to it
//      _looker: The object that you want to rotate towards the target
const lookAt = (_target, _lookerParent, _looker) => {
 const ptToLookAt = getPosition(_target);
 const lookAtTransform = _lookerParent.transform.lookAt(ptToLookAt);
 setRotation(_looker, lookAtTransform);
};

// Random animation
const scl = R.val(0.1);
obj.targetNull.transform.x = R.sin(Time.ms.mul(R.val(0.001))).mul(scl);
obj.targetNull.transform.y = R.cos(Time.ms.mul(R.val(0.0007))).mul(scl);
obj.targetNull.transform.z = R.sin(Time.ms.mul(R.val(0.0005))).mul(scl);

// Do the look at
lookAt(obj.targetNull, obj.followNull, obj.plane);
})


Getting 2D Face Position from Script


Finally, we look at how we can get 2D face position with code based on Josh Beckwith’s Vignettes and 2d Face Tracking tutorial.

const S = require('Scene');
const R = require('Reactive');
const FaceTracking = require('FaceTracking')

const face = FaceTracking.face(0)
const camCoords = face.cameraTransform.applyTo(face.nose.tip)

//Method 1
(async function(){

 const camera = await S.root.findFirst('Camera');

 const focalPlane = camera.focalPlane
 const u = R.add(R.div(R.div(camCoords.x, R.div(focalPlane.width, 2)), 2), .5)
 var v = R.add(R.div(R.div(camCoords.y, R.div(focalPlane.height, 2)), 2), .5)
 v = R.sub(1, v)

 P.inputs.setPoint(`face_2d`, R.point(u, v, 0));

})();

Summary


Async, await really helps to make coding in promise a lot neater and organised! If you have not yet gotten used to promise, it is highly recommended to just go ahead and get used to async, await! If you are like me, spent hours to get used to promise.all before, life is gonna get better for you buddy!


I hope this gets you to understand async, await more and let me know if there is anything more you wish for me to add in this article!


Find more useful tips for making Instagram & Facebook filters at GOWAAA!

Follow us on Instagram to see our filters!


Watch our Swizzle Hack Livestream on our Youtube Channel!


0 views
  • White Instagram Icon
  • White Facebook Icon
  • Tiktok-logo-01
  • YouTube
  • LinkedIn

contact us: info@gowaaa.com

22 Sin Ming Lane #06-76 Midview City Singapore 573969

© 2020 GOWAAA

Logo-Web.png