PathGeometry
Sampling points, tangents and normals along a path's outline by arc length, the tool behind point-on-path setups.
M10 DOM2-3: a read-only arc-length sampler over the EFFECTIVE outline, piecewise-linear over the E27 chord flatten (16 stops per cubic - deterministic, cross-host bit-stable; the dossier's veto D14-a). Fractions clamp 0..1; multiple subpaths concatenate by arc length (samplers never lerp across the gaps); an empty geometry answers {0, 0} with length 0.
length
Type: number (accessor).
The total flattened arc length (px).
Determinism: pure.
PathGeometry samples the flattened effective outline by ARC LENGTH: sixteen deterministic stops per segment (the same chord flatten the feather band and the outliner walk), fractions clamped to 0..1, multiple subpaths concatenated end to end with zero-length jumps the samplers never lerp across. An empty geometry answers a zero length and every sampler answers the origin point.
Worked example: Measuring the rail
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Rail length and paste:
export const outputs = ["layer_2:transform.opacity"];
export function frame() {
const rail = layer("Wave").path();
set("layer_2:transform.opacity", rail.length / 4);
}
Line by line:
- Lines 4-5: The wave's two straight runs measure 200 and 100 px, so the flattened arc length is exactly 300 and the chip settles at 75 percent.
What you see. The chip fades to 75 percent opacity: one quarter of the rail's 300 px flattened length.
closed
Type: boolean (accessor).
The FIRST drawable subpath's closed flag (pinned).
Determinism: pure.
Worked example: Open wave, closed rim
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Closed flags and paste:
export const outputs = ["layer_2:transform.opacity"];
export function frame() {
const wave = layer("Wave").path();
const rim = layer("Plate").maskPath("mask_1");
set("layer_2:transform.opacity", !wave.closed && rim.closed ? 65 : 5);
}
Line by line:
- Lines 4-6: closed reports the FIRST drawable subpath's flag: the wave is an open polyline, the mask rim a closed rectangle. Closed rails wrap for the samplers; open rails clamp.
What you see. The chip rests at 65 percent opacity: the wave read open and the rim read closed.
numSubpaths
Type: number (accessor).
The drawable subpath count.
Determinism: pure.
Worked example: Counting drawable subpaths
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Subpath count and paste:
export const outputs = ["layer_2:transform.opacity"];
export function frame() {
const rail = layer("Wave").path();
set("layer_2:transform.opacity", 50 + rail.numSubpaths * 10);
}
Line by line:
- Lines 4-5: The wave draws one contour, so numSubpaths answers 1. A path layer with several contents leaves concatenates them all and counts each drawable subpath.
What you see. The chip lands at 60 percent opacity: fifty plus ten for the wave's single drawable subpath.
pointAtFraction
pointAtFraction(f: number): {x, y}
The outline point at the arc-length fraction (layer-local px).
Determinism: pure.
Worked example: Walking the outline by fraction
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Fraction walker and paste:
export const outputs = ["layer_2:transform.position"];
export function frame() {
const rail = layer("Wave").path();
const p = rail.pointAtFraction(0.75);
set("layer_2:transform.position", [p.x, p.y]);
}
Line by line:
- Lines 4-5: Three quarters of 300 px is 225 px of arc: past the 200 px horizontal run and 25 px down the vertical one, the layer-local point (300, 225). Fractions outside 0..1 clamp.
- Line 6: The chip takes the sample directly; the wave sits at the origin so local px read as comp coordinates.
What you see. The chip parks a quarter of the way down the wave's vertical drop at (300, 225).
tangentAtFraction
tangentAtFraction(f: number): {x, y}
The unit tangent along the walk at the fraction.
Determinism: pure.
Worked example: Riding the wave, facing the travel
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Orient to path and paste:
export const outputs = [
"layer_2:transform.position",
"layer_2:transform.rotation",
];
export function frame() {
const rail = layer("Wave").path();
const p = rail.pointAtFraction(0.75);
const t = rail.tangentAtFraction(0.75);
set("layer_2:transform.position", [p.x, p.y]);
set("layer_2:transform.rotation", t.x === 0 && t.y === 1 ? 90 : 0);
}
Line by line:
- Lines 7-9: The point and the unit tangent at the same fraction: on the wave's vertical drop the tangent points straight down, (0, 1) in y-down space.
- Lines 10-11: The chip parks on the rail and turns to face the travel, the auto-orient-to-path pattern the feedback asked for. The proof drives 90 degrees only while the tangent reads exactly straight down.
What you see. The chip sits on the vertical drop rotated 90 degrees, facing along the rail like a wave rider.
normalAtFraction
normalAtFraction(f: number): {x, y}
The unit normal - the tangent rotated +90 degrees in y-down space (the E15 side convention).
Determinism: pure.
Worked example: Standing off the rail on the normal
The scene. A Shoreline comp holding an open two-segment Wave path at the origin, a small Chip ready to ride it, and a Plate solid wearing a rectangular mask whose rim a script can sample.
- The comp Shoreline: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Wave: a path layer, at [0, 0].
- Chip: a solid layer, at [320, 180], 24 by 24, filled ffd166.
- Plate: a solid layer, at [480, 80], 160 by 40, filled 1a1e26.
The script. Add a project script named Normal offset and paste:
export const outputs = ["layer_2:transform.position"];
export function frame() {
const rail = layer("Wave").path();
const p = rail.pointAtFraction(0.5);
const n = rail.normalAtFraction(0.5);
set("layer_2:transform.position", [p.x + n.x * 40, p.y + n.y * 40]);
}
Line by line:
- Lines 4-6: The normal is the tangent rotated ninety degrees in y-down space: on the horizontal run the tangent reads (1, 0) and the normal (0, 1), pointing below the rail.
- Line 7: The chip hovers 40 px off the rail along the normal, the offset-rider pattern.
What you see. The chip floats 40 px beneath the middle of the wave's horizontal run, at (250, 240).