Spark AR Notes: Custom SamplerFactoryCreating Infinite Blooming Flower
1. Knowledge requirements
2. Preparations steps:
3. Position types and position steps
4. Scale steps
5. Rotation steps
6. Script to Path Bridging
Some useful methods I’m using in this script:
getBoolean()
andsetBoolean()
getString()
andsetString()
getScalar()
andsetScalar()
getPoint2D()
andsetPoint2D()
getVector()
andsetVector()
Find all method at Spark AR Documents.
Next, create Inputs, Outputs in Spark AR Studio.
7. Array Modifier
Your code will now look like this:
//This script is relate to Array Modifier in Blender
//Uses for Spark AR Studio
//Copyright by Nguyen Dong Ho from fomalia.com
//Reference https://docs.blender.org/manual/en/2.93/modeling/modifiers/generate/array.html
const Patches = require('Patches');
const Scene = require('Scene');
const Diagnostics = require('Diagnostics');
const Reactive = require('Reactive');
const sceneRoot = Scene.root;
(async function() {
const objects = await Promise.all([
sceneRoot.findFirst('targetTracker0'),
sceneRoot.findByPath('targetTracker0/flowerHolder/digitalFlower*'),
]);
const planeTracker = objects[0];
const objectsArray = objects[1];
const n = objectsArray.length;
const PI = Math.PI;;
Diagnostics.log("Number of objects = " + n);
function radian(Degrees) {
return Reactive.mul(Degrees, PI/180);
};
const myBoolean = true;
// Send the boolean value to the Patch Editor under the name 'myBoolean'
await Patches.inputs.setBoolean('myBoolean', myBoolean);
// Get the 'positionType' string value from the Patch Editor
const positionType = (await Patches.outputs.getString('positionType')).pinLastValue();
//=====================================
//Function OBJECTS ARRAY MODIFIER
//Fixed Count
//Relative Offset
//Factor X Y Z
//=====================================
//Position X Y Z Modifier
// Get the 'positionType' string value from the Patch Editor
const positionSteps = (await Patches.outputs.getVector('positionSteps'));
var xValue = positionSteps.x; //Input X
var yValue = positionSteps.y; //Input Y
var zValue = positionSteps.z; //Input Z
switch(positionType) {
case "linear":
const xArrayLinear = new Array(objectsArray.length).fill(Number).map((_, i) => (Reactive.mul(xValue,i)));
const yArrayLinear = new Array(objectsArray.length).fill(Number).map((_, i) => (Reactive.mul(yValue, i)));
const zArrayLinear = new Array(objectsArray.length).fill(Number).map((_, i) => (Reactive.mul(zValue, i)));
for (let i = 0; i<n; i++) {
objectsArray[i].transform.x = xArrayLinear[i];
objectsArray[i].transform.y = yArrayLinear[i];
objectsArray[i].transform.z = zArrayLinear[i];
};
break;
case "ellipse":
// Get the 'rotateDirection' string value from the Patch Editor
const rotateDirection = (await Patches.outputs.getString('rotateDirection')).pinLastValue(); //Value can be XY YZ | Default = XZ
//Ellipse quation x^2/Ra^2 +y^2/Rb^2 = 1
//Ellipse = Circle => Ra = Rb = Radius ; Ra^2 = Rb^2 + Rc^2 => Rc = 0
//Ellipse Document: https://en.wikipedia.org/wiki/Ellipse
const ellipseRadius = (await Patches.outputs.getPoint2D('ellipseRadius'))
var Ra = ellipseRadius.x; //INPUT Ellipse Width
var Rb = ellipseRadius.y; //INPUT Ellipse Height
//Begin Angle and End Angle
const ellipseAngle = (await Patches.outputs.getPoint2D('ellipseAngle'))
var beginAngle = radian(ellipseAngle.x); //INPUT Begin Angle
var endAngle = radian(ellipseAngle.y); //INPUT End Angle
switch (rotateDirection) {
case "XY" :
const xArrayCircleXY = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Rb, Reactive.cos(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n)))))); //(Rb*Math.cos(beginAngle + i*(endAngle - beginAngle)/n))
const yArrayCircleXY = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Ra, Reactive.sin(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n)))))); //(Rb*Math.sin(beginAngle + i*(endAngle - beginAngle)/n))
for (let i = 0; i<n; i++) {
objectsArray[i].transform.x = xArrayCircleXY[i];
objectsArray[i].transform.y = yArrayCircleXY[i];
objectsArray[i].transform.z = zValue;
};
break;
case "YZ" :
const yArrayCircleYZ = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Rb, Reactive.cos(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n))))));
const zArrayCircleYZ = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Ra, Reactive.sin(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n))))));
for (let i = 0; i<n; i++) {
objectsArray[i].transform.x = xValue;
objectsArray[i].transform.y = yArrayCircleYZ[i];
objectsArray[i].transform.z = zArrayCircleYZ[i];
};
break;
default :
const xArrayCircleXZ = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Rb, Reactive.cos(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n))))));
const zArrayCircleXZ = new Array(n).fill(Number).map((_, i) => (Reactive.mul(Ra, Reactive.sin(Reactive.add(beginAngle, Reactive.mul((Reactive.sub(endAngle, beginAngle)), i/n))))));
for (let i = 0; i<n; i++) {
objectsArray[i].transform.x = xArrayCircleXZ[i];
objectsArray[i].transform.y = yValue;
objectsArray[i].transform.z = zArrayCircleXZ[i];
};
break;
};
break;
default:
break;
};
//Scale XYZ Modifier
const scaleStep = (await Patches.outputs.getVector('scaleSteps'))
const scaleXValue = scaleStep.x; //Input X Scale
const scaleYValue = scaleStep.y; //Input Y Scale
const scaleZValue = scaleStep.y; //Input Z Scale
const scaleBeginValue = (await Patches.outputs.getVector('scaleBeginValue'))
const scaleBeginX = scaleBeginValue.x; //INPUT begin scale X
const scaleBeginY = scaleBeginValue.y; //INPUT begin scale Y
const scaleBeginZ = scaleBeginValue.z; //INPUT begin scale Z
const scaleXArray = new Array(n).fill(Number).map((_, i) => (Reactive.add(scaleBeginX, Reactive.mul(scaleXValue, i)))); //(scaleBeginX + scaleXValue*i)
const scaleYArray = new Array(n).fill(Number).map((_, i) => (Reactive.add(scaleBeginY, Reactive.mul(scaleYValue, i)))); //(scaleBeginY + scaleYValue*i)
const scaleZArray = new Array(n).fill(Number).map((_, i) => (Reactive.add(scaleBeginZ, Reactive.mul(scaleZValue, i)))); //(scaleBeginZ + scaleZValue*i)
//Rotation XYZ Modifier
const rotationSteps = (await Patches.outputs.getVector('rotationSteps'))
const rotationXValue = rotationSteps.x; //Input X Degrees
const rotationYValue = rotationSteps.y; //Input Y Degrees
const rotationZValue = rotationSteps.z; //Input Z Degrees
const rotationXArray = new Array(n).fill(Number).map((_, i) => (Reactive.mul(radian(rotationXValue), i)));
const rotationYArray = new Array(n).fill(Number).map((_, i) => (Reactive.mul(radian(rotationYValue), i)));
const rotationZArray = new Array(n).fill(Number).map((_, i) => (Reactive.mul(radian(rotationZValue), i)));
for (let i = 0; i < n; i++) {
objectsArray[i].transform.scaleX = scaleXArray[i];
objectsArray[i].transform.scaleY = scaleYArray[i];
objectsArray[i].transform.scaleZ = scaleZArray[i];
objectsArray[i].transform.rotationX = rotationXArray[i];
objectsArray[i].transform.rotationY = rotationYArray[i];
objectsArray[i].transform.rotationZ = rotationZArray[i];
};
})();
Read the official document of Spark AR/ Learn for a completely project that animate an 3D object by script. (link below)
Thank you for reading!
8. Resources
Feel free to send me a message.
References
- Spark AR Documents. https://sparkar.facebook.com/ar-studio/learn/patch-editor/bridging#methods
- Spark AR Documents. https://sparkar.facebook.com/ar-studio/learn/reference/classes/ReactiveModule#example
- Wikipedia Ellipse. https://en.wikipedia.org/wiki/Ellipse
- Blender Array Modifier. https://docs.blender.org/manual/en/2.93/modeling/modifiers/generate/array.html