Skip to content

Commit

Permalink
flip curve (#508)
Browse files Browse the repository at this point in the history
  • Loading branch information
mck authored Nov 8, 2024
1 parent 030b8a0 commit b60c9d0
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-pears-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/graph-engine": minor
---

Add Flip Float Curve node, this lets you change the curves as needed for light, dark mode for example.
87 changes: 87 additions & 0 deletions packages/graph-engine/src/nodes/curves/flipFloatCurve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { BooleanSchema, FloatCurveSchema } from '../../schemas/index.js';
import {
FloatCurve,
INodeDefinition,
ToInput,
ToOutput,
Vec2
} from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { setToPrecision } from '../../utils/precision.js';

export default class NodeDefinition extends Node {
static title = 'Flip Float Curve';
static type = 'studio.tokens.curve.flipFloat';
static description = 'Flips a float curve horizontally, vertically, or both';

declare inputs: ToInput<{
curve: FloatCurve;
flipHorizontal: boolean;
flipVertical: boolean;
}>;

declare outputs: ToOutput<{
curve: FloatCurve;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('curve', {
type: FloatCurveSchema
});
this.addInput('flipHorizontal', {
type: {
...BooleanSchema,
default: false
}
});
this.addInput('flipVertical', {
type: {
...BooleanSchema,
default: false
}
});
this.addOutput('curve', {
type: FloatCurveSchema
});
}

flipPoint(point: Vec2, flipH: boolean, flipV: boolean): Vec2 {
const [x, y] = point;
return [
setToPrecision(flipV ? 1 - x : x, 5),
setToPrecision(flipH ? 1 - y : y, 5)
];
}

execute(): void | Promise<void> {
const { curve, flipHorizontal, flipVertical } = this.getAllInputs();

if (!flipHorizontal && !flipVertical) {
this.outputs.curve.set(curve);
return;
}

const flippedSegments = curve.segments.map(segment =>
this.flipPoint(segment, flipHorizontal, flipVertical)
);

const flippedControlPoints = curve.controlPoints.map(([c1, c2]) => [
this.flipPoint(c1, flipHorizontal, flipVertical),
this.flipPoint(c2, flipHorizontal, flipVertical)
]);

// If flipping horizontally, we need to reverse the order of points
if (flipHorizontal) {
flippedSegments.reverse();
flippedControlPoints.reverse();
// Also swap control points within each pair when flipping horizontally
flippedControlPoints.forEach(points => points.reverse());
}

this.outputs.curve.set({
segments: flippedSegments,
controlPoints: flippedControlPoints as [Vec2, Vec2][]
});
}
}
2 changes: 2 additions & 0 deletions packages/graph-engine/src/nodes/curves/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import bezier from './bezier.js';
import constructFloatCurve from './constructFloatCurve.js';
import deconstructFloatCurve from './deconstructFloatCurve.js';
import flipFloatCurve from './flipFloatCurve.js';
import floatCurve from './floatCurve.js';
import presetBezier from './presetBeziers.js';
import sample from './sample.js';
Expand All @@ -11,5 +12,6 @@ export const nodes = [
presetBezier,
sample,
deconstructFloatCurve,
flipFloatCurve,
constructFloatCurve
];
100 changes: 100 additions & 0 deletions packages/graph-engine/tests/suites/nodes/curves/flipFloatCurve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/curves/flipFloatCurve.js';

describe('curves/flipFloatCurve', () => {
const sampleCurve = {
segments: [
[0, 0],
[1, 1]
],
controlPoints: [
[
[0.25, 0.1],
[0.75, 0.9]
]
]
};

test('flips curve horizontally', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.curve.setValue(sampleCurve);
node.inputs.flipHorizontal.setValue(true);
node.inputs.flipVertical.setValue(false);

await node.execute();

const result = node.outputs.curve.value;
expect(result.segments).to.eql([
[1, 0],
[0, 1]
]);
expect(result.controlPoints).to.eql([
[
[0.75, 0.1],
[0.25, 0.9]
]
]);
});

test('flips curve vertically', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.curve.setValue(sampleCurve);
node.inputs.flipHorizontal.setValue(false);
node.inputs.flipVertical.setValue(true);

await node.execute();

const result = node.outputs.curve.value;
expect(result.segments).to.eql([
[1, 0],
[0, 1]
]);
expect(result.controlPoints).to.eql([
[
[0.75, 0.1],
[0.25, 0.9]
]
]);
});

test('flips curve both horizontally and vertically', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.curve.setValue(sampleCurve);
node.inputs.flipHorizontal.setValue(true);
node.inputs.flipVertical.setValue(true);

await node.execute();

const result = node.outputs.curve.value;
expect(result.segments).to.eql([
[0, 0],
[1, 1]
]);
expect(result.controlPoints).to.eql([
[
[0.25, 0.1],
[0.75, 0.9]
]
]);
});

test('returns original curve when no flips are selected', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.curve.setValue(sampleCurve);
node.inputs.flipHorizontal.setValue(false);
node.inputs.flipVertical.setValue(false);

await node.execute();

expect(node.outputs.curve.value).to.eql(sampleCurve);
});
});

0 comments on commit b60c9d0

Please sign in to comment.