Zephyrnet Logo

Async, Await Spark AR Scripting Guide For Beginners

Date:

//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.

1. How to use subtle AR filters to survive your Zoom meetings?

2. The First No-Headset Virtual Monitor

3. Augmented reality (AR) is the future of Restaurant Menu?

4. Creating remote MR productions

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.

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].

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!

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);
})

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));

})();

Source: https://arvrjourney.com/async-await-spark-ar-scripting-guide-for-beginners-3226dd836b02?source=rss—-d01820283d6d—4

spot_img

Latest Intelligence

spot_img