Skip to content

Commit

Permalink
feat: mathematical operations for vector signals (#1030)
Browse files Browse the repository at this point in the history
Closes: #967
  • Loading branch information
mancopp committed May 16, 2024
1 parent 944b48f commit c5b02d1
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 16 deletions.
19 changes: 16 additions & 3 deletions packages/2d/src/lib/decorators/compound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,20 @@ import {getPropertyMetaOrCreate} from './signal';
* @param entries - A record mapping the property in the compound object to the
* corresponding property on the owner node.
*/
export function compound(entries: Record<string, string>): PropertyDecorator {
export function compound<
TSetterValue,
TValue extends TSetterValue,
TKeys extends keyof TValue = keyof TValue,
TOwner = void,
>(
entries: Record<string, string>,
klass: typeof CompoundSignalContext<
TSetterValue,
TValue,
TKeys,
TOwner
> = CompoundSignalContext,
): PropertyDecorator {
return (target, key) => {
const meta = getPropertyMetaOrCreate<any>(target, key);
meta.compound = true;
Expand All @@ -48,7 +61,7 @@ export function compound(entries: Record<string, string>): PropertyDecorator {

const initial = meta.default;
const parser = meta.parser.bind(instance);
const signalContext = new CompoundSignalContext(
const signalContext = new klass(
meta.compoundEntries.map(([key, property]) => {
const signal = new SignalContext(
modify(initial, value => parser(value)[key]),
Expand All @@ -57,7 +70,7 @@ export function compound(entries: Record<string, string>): PropertyDecorator {
undefined,
makeSignalExtensions(undefined, instance, property),
).toSignal();
return [key, signal];
return [key as TKeys, signal];
}),
parser,
initial,
Expand Down
8 changes: 7 additions & 1 deletion packages/2d/src/lib/decorators/vector2Signal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {PossibleVector2, Signal, Vector2} from '@motion-canvas/core';
import {
PossibleVector2,
Signal,
Vector2,
Vector2SignalContext,
} from '@motion-canvas/core';
import type {Length} from '../partials';
import {compound} from './compound';
import {wrapper} from './signal';
Expand All @@ -23,6 +28,7 @@ export function vector2Signal(
x: prefix ? `${prefix}X` : 'x',
y: prefix ? `${prefix}Y` : 'y',
},
Vector2SignalContext,
)(target, key);
wrapper(Vector2)(target, key);
};
Expand Down
244 changes: 244 additions & 0 deletions packages/core/src/signals/Vector2SignalContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import {InterpolationFunction, TimingFunction} from '../tweening';
import {PossibleVector2, Vector2} from '../types';
import {CompoundSignal, CompoundSignalContext} from './CompoundSignalContext';
import {Signal} from './SignalContext';
import {SignalExtensions, SignalGenerator, SignalValue} from './types';

export interface Vector2Edit<TOwner> {
(callback: (current: Vector2) => SignalValue<PossibleVector2>): TOwner;
(
callback: (current: Vector2) => SignalValue<PossibleVector2>,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
}

export interface Vector2Operation<TOwner> {
(value: PossibleVector2): TOwner;
(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
}

export interface Vector2SignalHelpers<TOwner> {
edit: Vector2Edit<TOwner>;
mul: Vector2Operation<TOwner>;
div: Vector2Operation<TOwner>;
add: Vector2Operation<TOwner>;
sub: Vector2Operation<TOwner>;
dot: Vector2Operation<TOwner>;
cross: Vector2Operation<TOwner>;
mod: Vector2Operation<TOwner>;
}

export type Vector2Signal<
TOwner = void,
TContext = Vector2SignalContext<TOwner>,
> = CompoundSignal<PossibleVector2, Vector2, 'x' | 'y', TOwner, TContext> &
Vector2SignalHelpers<TOwner>;

export class Vector2SignalContext<TOwner = void>
extends CompoundSignalContext<PossibleVector2, Vector2, 'x' | 'y', TOwner>
implements Vector2SignalHelpers<TOwner>
{
public constructor(
entries: ('x' | 'y' | [keyof Vector2, Signal<any, any, TOwner>])[],
parser: (value: PossibleVector2) => Vector2,
initial: SignalValue<PossibleVector2>,
interpolation: InterpolationFunction<Vector2>,
owner: TOwner = <TOwner>(<unknown>undefined),
extensions: Partial<SignalExtensions<PossibleVector2, Vector2>> = {},
) {
super(entries, parser, initial, interpolation, owner, extensions);

Object.defineProperty(this.invokable, 'edit', {
value: this.edit.bind(this),
});

Object.defineProperty(this.invokable, 'mul', {
value: this.mul.bind(this),
});

Object.defineProperty(this.invokable, 'div', {
value: this.div.bind(this),
});

Object.defineProperty(this.invokable, 'add', {
value: this.add.bind(this),
});

Object.defineProperty(this.invokable, 'sub', {
value: this.sub.bind(this),
});

Object.defineProperty(this.invokable, 'dot', {
value: this.dot.bind(this),
});

Object.defineProperty(this.invokable, 'cross', {
value: this.cross.bind(this),
});

Object.defineProperty(this.invokable, 'mod', {
value: this.mod.bind(this),
});
}

public override toSignal(): Vector2Signal<TOwner> {
return this.invokable;
}

public edit(
callback: (current: Vector2) => SignalValue<PossibleVector2>,
): TOwner;
public edit(
callback: (current: Vector2) => SignalValue<PossibleVector2>,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public edit(
callback: (current: Vector2) => SignalValue<PossibleVector2>,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const newValue = callback(this.get());
return this.invoke(
newValue,
duration,
timingFunction,
interpolationFunction,
) as SignalGenerator<PossibleVector2, Vector2> | TOwner;
}

public mul(value: PossibleVector2): TOwner;
public mul(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public mul(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.mul(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public div(value: PossibleVector2): TOwner;
public div(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public div(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.div(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public add(value: PossibleVector2): TOwner;
public add(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public add(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.add(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public sub(value: PossibleVector2): TOwner;
public sub(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public sub(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.sub(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public dot(value: PossibleVector2): TOwner;
public dot(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public dot(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.dot(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public cross(value: PossibleVector2): TOwner;
public cross(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public cross(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.cross(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}

public mod(value: PossibleVector2): TOwner;
public mod(
value: PossibleVector2,
duration: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2>;
public mod(
value: PossibleVector2,
duration?: number,
timingFunction?: TimingFunction,
interpolationFunction?: InterpolationFunction<Vector2>,
): SignalGenerator<PossibleVector2, Vector2> | TOwner {
const callback = (current: Vector2) => current.mod(value);
if (duration === undefined) return this.edit(callback);
return this.edit(callback, duration, timingFunction, interpolationFunction);
}
}
1 change: 1 addition & 0 deletions packages/core/src/signals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './CompoundSignalContext';
export * from './ComputedContext';
export * from './DependencyContext';
export * from './SignalContext';
export * from './Vector2SignalContext';
export * from './createComputed';
export * from './createComputedAsync';
export * from './createSignal';
Expand Down
21 changes: 9 additions & 12 deletions packages/core/src/types/Vector.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {
CompoundSignal,
CompoundSignalContext,
Signal,
SignalValue,
Vector2Signal,
Vector2SignalContext,
} from '../signals';
import {InterpolationFunction, arcLerp} from '../tweening';
import {clamp, map} from '../tweening/interpolationFunctions';
import {arcLerp} from '../tweening';
import {
InterpolationFunction,
clamp,
map,
} from '../tweening/interpolationFunctions';
import {DEG2RAD, RAD2DEG} from '../utils';
import {Matrix2D, PossibleMatrix2D} from './Matrix2D';
import {Direction, Origin} from './Origin';
Expand All @@ -23,13 +27,6 @@ export type PossibleVector2<T = number> =
| [T, T]
| undefined;

export type Vector2Signal<T> = CompoundSignal<
PossibleVector2,
Vector2,
'x' | 'y',
T
>;

export type SimpleVector2Signal<T> = Signal<PossibleVector2, Vector2, T>;

/**
Expand Down Expand Up @@ -80,7 +77,7 @@ export class Vector2 implements Type, WebGLConvertible {
interpolation: InterpolationFunction<Vector2> = Vector2.lerp,
owner?: any,
): Vector2Signal<void> {
return new CompoundSignalContext(
return new Vector2SignalContext(
['x', 'y'],
(value: PossibleVector2) => new Vector2(value),
initial,
Expand Down

0 comments on commit c5b02d1

Please sign in to comment.