Skip to main content

Deformer Module

Pro Feature
Pro features are only available with a Professional licence. Starter licences can evaluate most Professional features via the Try Pro button at the top of the Viewport. To upgrade, visit cavalry.scenegroup.co.

Introduction

The following are only available to the JavaScript Deformer. Everything in this module is in the def namespace and so methods need prefixing with def..

Member Functions

Hierarchy-Based Access

The hierarchy-based methods provide access to the complete mesh tree. This is the preferred approach as it correctly handles nested groups and complex hierarchies.

getRootMesh() → cavalry.Mesh

Returns the root cavalry.Mesh of the shape being deformed. Use this to access the complete mesh hierarchy for modification.

var root = def.getRootMesh();
// Traverse and modify the mesh hierarchy
def.setRootMesh(root);

setRootMesh(mesh:cavalry.Mesh)

Sets the root mesh back after modification. This must be called to apply changes made to the mesh hierarchy.

// Helper function to recursively visit all meshes
function visitAllMeshes(mesh, func) {
for (var i = 0; i < mesh.childMeshCount(); i++) {
var child = mesh.getChildMeshAtIndex(i);
visitAllMeshes(child, func);
mesh.setChildMeshAtIndex(i, child);
}
func(mesh);
}

function deformMesh(mesh) {
var pathCount = mesh.count();
for (var p = 0; p < pathCount; p++) {
var path = mesh.getPathAtIndex(p);
var pd = path.pathData();

for (var idx = 0; idx < pd.length; idx++) {
// Move each point along its normal
pd[idx].point.x += pd[idx].normal.x * amount;
pd[idx].point.y += pd[idx].normal.y * amount;
}

path.setPathData(pd);
mesh.setPathAtIndex(p, path);
}
}

var root = def.getRootMesh();
visitAllMeshes(root, deformMesh);
def.setRootMesh(root);

Falloffs

getFalloffAtPoint(x:number, y:number) → number

Returns the combined falloff value (0-1) at the specified position. This includes the deformer's Strength setting and any connected falloff shapes. Coordinates should be in local mesh space.

function deformMesh(mesh) {
var pathCount = mesh.count();
for (var p = 0; p < pathCount; p++) {
var path = mesh.getPathAtIndex(p);
var pd = path.pathData();

for (var idx = 0; idx < pd.length; idx++) {
var pt = pd[idx].point;
var normal = pd[idx].normal;

// Get falloff at this point (includes Strength)
var falloff = def.getFalloffAtPoint(pt.x, pt.y);
if (falloff <= 0) continue;

pd[idx].point.x += normal.x * amount * falloff;
pd[idx].point.y += normal.y * amount * falloff;
}

path.setPathData(pd);
mesh.setPathAtIndex(p, path);
}
}

Bounding Box and Points

getBoundingBox() → {x:number, y:number, width:number, height:number, centre:{x:number, y:number}, left:number, right:number, top:number, bottom:number}

Return an object containing details about the selection's bounding box.

getPoints() → array[points]

Return an array of positions for each point of a Shape. These can then be looped through these to manipulate them.

// Connect the Deformer to a Shape's Deformers attribute and hit 'Return'.
var points = def.getPoints();
console.log(JSON.stringify(points));

setPoints(array[points])

Set the position for each point of a Shape.

// A simple sine wave deformer
var points = def.getPoints();
var bbox = def.getBoundingBox();
var maxX = bbox.x+bbox.width;
var frequency = 10;
var amplitude = 50;

for (let pt of points) {
let normX = cavalry.norm(pt.x, bbox.x, maxX);
pt.y += Math.sin((normX+n0*.1)*frequency)*amplitude;
}

def.setPoints(points);

Depth-Based Access

The depth-based methods access meshes at specific levels of the hierarchy.

meshDepth

The depth of the mesh tree (the highest number reported in the Mesh Explorer). This is a property/variable, not a method.

meshCountAtDepth(depth:int) → int

Return the number of meshes at a given depth.

getMeshesAtDepth(depth:int) → [cavalry.Mesh]

Return an array of cavalry.Mesh objects.

setMeshesAtDepth(depth:int, [cavalry.Mesh])

Replace the meshes at the given depth with a cavalry.Mesh. This will not remove any un-replaced meshes.

getMeshAtDepthAtIndex(depth:int, index:int) → cavalry.Mesh

Return a mesh at the given depth and index.

setMeshAtDepthAtIndex(depth:int, index:int, mesh:cavalry.Mesh)

Replace a mesh at the given depth and index.

setTransformAtDepthAtIndex(depth:int, index:int, position:object, rotation:double, scale:object)

Set the position of a mesh at the given depth and index. Position and Scale are optional objects with x and y values (e.g. {"x":20, "y": 5}).

clearTransformAtDepthAtIndex(depth:int, index:int)

Reset the transform of the mesh at a given depth and index.

centrePivotAtDepthAtIndex(depth:int, index:int)

Centres the pivot of the mesh at a given depth and index.

setMaterialAtDepthAtIndex(depth:int, index:int, material:cavalry.Material)

Sets the Material object for the mesh at a given depth and index.

highestDepthWithPath() → int

Returns the highest depth within a Mesh that contains a Path.

Examples

Replace a child-mesh with another mesh.

  1. Create a Text Shape.
  2. In the Attribute Editor, click the + button on the Text Shape's Deformers attribute and choose JavaScript Deformer.
  3. Click the + button at the bottom of the JavaScript Deformer's UI and choose 'Add Layer'.
  4. Create a Star Shape.
  5. Set the Star's Radius to 30.
  6. Connect starShape.idjavaScriptDeformer.array.1 - this is the new n1 array Id created in step 3.
  7. Replace the JavaScript Deformer's Expression with:
     var meshes = def.getMeshesAtDepth(3);
    var replaceIndex = n0;
    if (meshes.length > replaceIndex) {
    let currentMesh = def.getMeshAtDepthAtIndex(3, replaceIndex);
    let bbox = currentMesh.getBoundingBox();
    let path = n1.path;
    def.setMeshAtDepthAtIndex(3,replaceIndex, path);
    def.setTransformAtDepthAtIndex(3,replaceIndex, bbox.centre);
    }
  8. Change the JavaScript Deformer's n0 value.

Replace a child-mesh with a custom Path.

  1. Create a Text Shape.
  2. In the Attribute Editor, click the + button on the Text Shape's Deformers attribute and choose JavaScript Deformer.
  3. Replace the JavaScript Deformer's Expression with:
     var meshes = def.getMeshesAtDepth(3);
    var replaceIndex = n0;
    if (meshes.length > replaceIndex) {
    var path = new cavalry.Path()
    var newMesh = new cavalry.Mesh();
    var material = new cavalry.Material();
    material.fillColor = "#ff24e0";
    material.stroke = true;
    material.strokeColor = "#000000";
    material.strokeWidth = 2;
    let bbox = meshes[replaceIndex].getBoundingBox();
    path.addEllipse(bbox.centre.x,bbox.centre.y,30,80);
    newMesh.addPath(path, material);
    def.setMeshAtDepthAtIndex(3,replaceIndex,newMesh);
    }
  4. Change the JavaScript Deformer's n0 value.

Modify a child-mesh at a depth and index.

  1. Create a Text Shape.
  2. In the Attribute Editor, click the + button on the Text Shape's Deformers attribute and choose JavaScript Deformer.
  3. Click the + button at the bottom of the JavaScript Deformer and choose 'Add Int'.
  4. Replace the JavaScript Deformer's Expression with:
     var meshes = def.getMeshesAtDepth(3);
    var replaceIndex = n1;
    if (meshes.length > 3) {
    var material = new cavalry.Material();
    material.fillColor = "#ff24e0";
    material.stroke = true;
    material.strokeColor = "#000000";
    material.strokeWidth = 2;
    def.setMaterialAtDepthAtIndex(3,replaceIndex,material);
    def.centrePivotAtDepthAtIndex(3,replaceIndex)
    def.setTransformAtDepthAtIndex(3,replaceIndex, {}, n0, {"x": 0.8, "y": 0.8});
    }
  5. Change the JavaScript Deformer's n0 and n1 values.