Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Draw a shape based on input values

Draw Shape Expression

Function that draws a shape based on input values. Controllable properties include position, scale, rotation, anchor point, height, width, and roundness. Via roundness, the function can also draw a circle and an ellipse.

Usage

Designed as an After Effects expression to be applied to path properties. The overall approach should work anywhere, though. This implentation gets controller values from an effect control, which is packaged in this After Effects preset:

Path Expression - Shape

License

The MIT License

/************************************************************************************
Draw Shape Expression v1.02
Description: Draws a parametric rectangle path
Author: Thomas Alberti <ta@thomasalberti.com>
************************************************************************************/
function getMatrixComposite(tMatrix1, tMatrix2){ // Multiply matrices, 2x2 * 2x2
let cMatrix = [];
for(let j = 0; j <= 1; j++) {
cMatrix[j] = [];
for(let k = 0; k <= 1; k++) {
let mProduct = 0;
for(let i = 0; i <= 1; i++) {
mProduct += tMatrix1[i][k] * tMatrix2[j][i];
}
cMatrix[j].push(mProduct);
}
}
return cMatrix;
}
function getVecTransform(tMatrix,vec){ // Multiply matrices, 2x2 * 2x1
let tVec = [];
for (let i = 0; i <= 1; i++) {
let mProduct = 0;
for (let j = 0; j <= 1; j++) {
mProduct += tMatrix[j][i] * vec[j];
}
tVec.push(mProduct);
}
return tVec;
}
function drawRectangle(xLeft, yTop, xWidth, yHeight) { // Define four points of a rectangle
let rectPoints = [
[xLeft + xWidth, yTop + yHeight],
[xLeft, yTop + yHeight],
[xLeft, yTop],
[xLeft + xWidth, yTop]
];
return rectPoints;
}
function getChamferPoints(shapePoints, chamferAmount){ // For each corner, move a new point along each intersecting side, define tangents
let chamferPoints = [];
let chamferInTangents = [];
let chamferOutTangents = [];
let numPoints = shapePoints.length;
for (let i = 0; i < numPoints; i++) {
let pointA = shapePoints[i];
let pointB = shapePoints[(i + 1) % numPoints];
let uAb = normalize(pointA - pointB);
let newPointAb = pointA - chamferAmount * uAb; // formula for moving a point along a vector
let pointAbInTangent = uAb * chamferAmount * 0.551915024494; // in tangent is plotted c or kappa of some amount along that vector, see Bezier math
let pointAbOutTangent = [0, 0];
let pointC = shapePoints[(i + numPoints - 1) % numPoints];
let uAc = normalize(pointA - pointC);
let newPointAc = pointA - chamferAmount * uAc;
let pointAcInTangent = [0, 0];
let pointAcOutTangent = uAc * chamferAmount * 0.551915024494;
chamferPoints.push(newPointAc, newPointAb);
chamferInTangents.push(pointAcInTangent, pointAbInTangent);
chamferOutTangents.push(pointAcOutTangent, pointAbOutTangent);
}
let chamferPointsAndTangents = [chamferPoints, chamferInTangents, chamferOutTangents];
return chamferPointsAndTangents;
}
function getTransformPoints(shapePoints, tMatrixRot, tMatrixScl, posXY){
let tShape = [[],[],[]];
let cRotSclMatrix = getMatrixComposite(tMatrixRot, tMatrixScl);
if (shapePoints.length == 3) { // check for tangent arrays and transform them if they're there
for (let i = shapePoints[0].length-1; i >= 0; i--) {
let tVertex = getVecTransform(cRotSclMatrix, shapePoints[0][i]) + posXY;
let tIn = getVecTransform(cRotSclMatrix, shapePoints[1][i]);
let tOut = getVecTransform(cRotSclMatrix, shapePoints[2][i]);
tShape[0].push(tVertex);
tShape[1].push(tOut); // swap tangent arrays because decrement loop reverses vertex order
tShape[2].push(tIn);
}
} else { // if no tangents, only transform points
for (let i = shapePoints.length-1; i >= 0; i--) {
let tVertex = getVecTransform(cRotSclMatrix, shapePoints[i]) + posXY;
tShape[0].push(tVertex);
}
}
return tShape;
}
// Get controller values
var controlWidth = (effect("Path Expression - Shape")("Path - Size X").value || .01);
var controlHeight = (effect("Path Expression - Shape")("Path - Size Y").value || .01);
var controlRoundness = effect("Path Expression - Shape")("Path - Roundness");
var minDimension = Math.min(Math.abs(controlWidth), Math.abs(controlHeight));
var roundnessMax = clamp(controlRoundness, 0, minDimension / 2);
var controlAnchorX = -effect("Path Expression - Shape")("Path - Anchor X") / 100;
var controlAnchorY = -effect("Path Expression - Shape")("Path - Anchor Y") / 100;
var controlPositionX = effect("Path Expression - Shape")("Path - Position X");
var controlPositionY = effect("Path Expression - Shape")("Path - Position Y");
var movePos = [controlPositionX, controlPositionY];
var controlRotation = degreesToRadians(effect("Path Expression - Shape")("Path - Rotation"));
var rotMatrix = [[Math.cos(controlRotation), Math.sin(controlRotation)],[-Math.sin(controlRotation), Math.cos(controlRotation)]];
var controlUniform = effect("Path Expression - Shape")("Path - Uniform Scale");
var controlScaleX = effect("Path Expression - Shape")("Path - Scale X") / 100;
var controlScaleY = effect("Path Expression - Shape")("Path - Scale Y") / 100;
if (controlUniform == 1){
var sclMatrix = [[controlScaleX,0],[0,controlScaleX]];
} else {
var sclMatrix = [[controlScaleX,0],[0,controlScaleY]];
}
// Define shape
var rectLeft = controlAnchorX * controlWidth;
var rectTop = controlAnchorY * controlHeight;
var rectWidth = controlWidth;
var rectHeight = controlHeight;
var rect1 = drawRectangle(rectLeft, rectTop, rectWidth, rectHeight);
if (roundnessMax > 0){
var roundRect1 = getChamferPoints(rect1, roundnessMax);
var transformRect1 = getTransformPoints(roundRect1, rotMatrix, sclMatrix, movePos);
} else {
var transformRect1 = getTransformPoints(rect1, rotMatrix, sclMatrix, movePos);
}
createPath(transformRect1[0], transformRect1[1], transformRect1[2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment