Skip to main content

JavaScript Layers

Pro Feature

Pro features are only available with a Professional licence. To upgrade, visit cavalry.scenegroup.co.

Intro

JavaScript can be used on various Layers including the JavaScript Utility, JavaScript Shape, JavaScript Deformer, JavaScript Emitter, JavaScript Modifier and JavaScript Emitter to set many attribute types in Cavalry.

The primary data types that can be written from an expression are:

NameattrTypeDescription
DoubledoubleA number with decimal digits. e.g. 4.153.
Double2double2A set of two doubles. e.g. Position 103.453, 487.543.
Double3double3A set of three doubles. e.g. Position (2.5D) 103.453, 487.543, 50.730.
IntintAn integer is a number without a decimal point. e.g. 4.
Int2int2A set of two integers. e.g. Composition Resolution 1920, 1080.
BoolboolA checkbox with two states - 1 or 0 (on/off).
StringstringCan contain alphanumeric characters and symbols.
ColorcolorAn attribute containing R, G, B and A values.
Point DatapointDataPoints generated by Distributions. See Using Point data.
AssetassetIdAccepts a .json, .txt or .csv Asset. See Using Assets.
LayerlayerIdAccepts any Layer. See Using Layer data.

This means that Layers that support JavaScript can be connected to any of the above data types in Cavalry.

Connecting two incompatible data types (e.g the expression returns an int, but the JavaScript is connected to a string), will trigger an attempt to automatically convert the data type however, matching the outputs correctly is highly recommended to provide better control over factors like floating point precision.

All of the above types are also available as inputs and can be referenced by their attribute name in the script. To add another attribute as a variable hit the + button at the bottom of the UI and choose the relevant data type. An input connection can then be made from another attribute with a matching data type. By default, the variable names are the letter n followed by a number, e.g n0, n1. However, these can be renamed by right clicking on the attribute and choosing Rename.

Variable Names

Variable names are case sensitive and must not contain spaces.

JavaScript imports a Context Module which contains several useful members for use with a Duplicator.

  • ctx.index - the Index from the Duplicator.
  • ctx.count - the Count from the Duplicator.
  • ctx.positionX/ ctx.positionY - the position of the Duplicator point.
  • ctx.layerId - the id of the layer that requested the JavaScript layer to calculate.
  • ctx.attributeId - the id of the Attribute that requested the JavaScript layer to calculate.

See the Create > Demo Scenes > JavaScript > Position to Color preset for an example of using these values.

The complex data types (color, int2, double2) are simple JavaScript objects that follow the pattern:

let color = {
r: 200,
g: 100,
b: 50,
a: 255
};

let someDouble2 = {
x: 200.4,
y: 55.6
};

For simplicity, input values can be modified or returned.

The primary rule for writing a JavaScript expression with this layer is that it must return a value. For example, the following expression calls a function which returns a value:

function toCelsius(fahrenheit) {
return (5/9) * (fahrenheit-32);
}

toCelsius(n0);
Global variables

When setting variables at the global level, use var rather than let/const. Because JavaScript Layers run every frame, using let/const globally will fail because when the expression runs for a second time, the variable will already exist and can't be reassigned.

Using Assets

JavaScript can utilise JSON/ Text and CSV Assets. To use one of these Asset types with the JavaScript Layers, click the + button and choose Add Text/JSON/CSV Asset. This will create a new Attribute which can take a supported Asset as an input.

  • Text Assets will be loaded as strings.
  • JSON Assets will be loaded as JSON Object Literals.
  • CSV Assets will be loaded as JSON Object Literals.
    • The top level Object properties will all match column names.
    • Each column contains:
      • rows - An array of row data (numeric or strings).
      • min - The minimum numeric value in the column (if the column is numeric).
      • max - The maximum numeric value in the column (if the column is numeric).
// Print out all the column names of a CSV connected to Attribute 'n1'.
var columns = Object.keys(n1);
for (var columnName of columns) {
console.log(columnName);
}
// Return the maximum value in a column titled 'Cavalry' from a CSV connected to Attribute 'n1'.
n1.Cavalry.max;
// If the column title contains spaces. e.g. 'Cavalry Users'.
n1["Cavalry Users"].min;

Using Point data

  1. Create a JavaScript Deformer.
  2. Click the + button towards the bottom of its Attribute Editor UI, add choose 'Add Point Data' to add a new Dynamic Attribute.
  3. Create a Duplicator.
  4. Right click on the Distribution attribute and choose Reveal Generator.
  5. Delete the Duplicator (leaving the Grid Distribution).
  6. Connect gridDistribution.idjavaScriptDeformer.n1 (this is the new Point Data attribute added in step 2).
  7. Replace the code in the JavaScript input with either:
    • console.log(n1.points[0].position.x); or;
    • console.log(n1.points.length);
  8. Create a Shape.
  9. Connect javaScriptDeformer.idshape.deformers. This is required for the JavaScript Deformer to calculate.

Step the playhead to the next frame to print the values to the JavaScript Console. The first example will output the first pointId's position.x (change the value in [0] to return other pointIds) whereas the second will return the number of points in the Distribution (9 for a default 3x3 grid).

Using Layer data

The connected Layer's type determines which objects are available:

  • Drawable (e.g. Null, Falloff) - a layer object will become available with the following 2D World Space Transform variables:
    • position (e.g. n1.layer.position, n1.layer.position.x)
    • rotation (e.g. n1.layer.rotation, n1.layer.rotation.z)
    • scale (e.g. n1.layer.scale, n1.layer.scale.x)
  • Shape (e.g. Rectangle, Duplicator) - a mesh property (e.g n1.mesh) becomes available, representing the mesh hierarchy in local space. The mesh property is an instance of the Mesh class from the Cavalry Module.

When using a Layer dynamic input, use hasInput to determine if a Layer is connected or not. For example, this can be used to prevent attempting to read non-existent mesh data.

// Assuming 'n1' is a Layer dynamic input.
console.log(n1.hasInput);
// Returns true if a Layer is connected 'n1'.
2D vs 2.5D rotation

JavaScript does not distinguish between 2D and 2.5D Layers so use rotation.z for 2D Layers.

// Closest Point on Path example.
// Paste this into the JavaScript Editor and then hit 'Run Script'.

// Create a Null and two Shapes
const nullId = api.create("null", "Move Me!!");
const ellipse1Id = api.primitive("ellipse", "Ellipse");
const ellipse2Id = api.primitive("ellipse", "Path");
api.setFill(ellipse2Id, false);
api.setStroke(ellipse2Id, true);
api.set(ellipse2Id, {"generator.radius": [350,350]});

// Create a JavaScript Utility.
const jsUtil = api.create("javaScript");
// Add the dynamic attributes and the expression.
api.addDynamic(jsUtil, "array", "layerId");
api.addDynamic(jsUtil, "array", "layerId");
const expression = 'function getPoint() {\n\xa0\xa0\xa0\xa0let toPt = n2.layer.position;\n\xa0\xa0\xa0\xa0let closestPoint = n1.path.findClosestPoint(toPt.x, toPt.y);\n\xa0\xa0\xa0\xa0return closestPoint.position;\n};\ngetPoint();'
api.set(jsUtil, {"expression": expression});

// Make the connections.
api.connect(ellipse2Id, "id", jsUtil, "array.1");
api.connect(nullId, "id", jsUtil, "array.2");
api.connect(jsUtil, "id", ellipse1Id, "position");

Output dependent calculations

The JavaScript Layers are the only place in Cavalry where exactly which layer and Attribute triggered the calculation is known. This information can be used to output different values depending on which connection is being calculated. Use the ctx.layerId and ctx.attributeId properties to access this information.

// In this example we assume a JSON Asset is connected to the JavaScript layer's n1 Attribute.
function getText() {
if (ctx.layerId == "textShape#1") {
// If textShape#1 is asking JavaScript for a value, return a given JSON value
return n1["colors"]["Green"];
} else if (ctx.layerId == "textShape#2") {
// If textShape#2 is asking JavaScript for a value, return a different JSON value
return n1["colors"]["Yellow"];
}
return "None";
}

getText();

Errors with the expression will be printed to the JavaScript Console.

caution

When running the expression via Cmd/Ctrl + Return, an output connection is required for the expression to run.

See Scripting Example JavaScript Expressions