Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Velocity Signal Functionality #942

Open
HikariIsChillin opened this issue Feb 7, 2024 · 3 comments
Open

Add Velocity Signal Functionality #942

HikariIsChillin opened this issue Feb 7, 2024 · 3 comments
Labels
a-core Relates to the core package b-enhancement New feature or request c-accepted The issue is ready to be worked on

Comments

@HikariIsChillin
Copy link

HikariIsChillin commented Feb 7, 2024

Description

I propose the addition of Velocity Signal functionality to Motion Canvas, providing users with a powerful tool for creating motion blur effects and enhancing dynamic animations. This feature is especially valuable for users aiming to achieve realistic or stylized motion effects, such as stretching on high velocities and shrinking when slowing down.

Proposed Solution

I suggest introducing a utility function named derivateSignal to simplify the process of obtaining the velocity of a signal. This function would take a signal as an argument and return a new signal representing the velocity.

Example usage:

export default makeScene2D(function* (view) {
	const signal = Vector2.createSignal(0)
	const velocity = derivateSignal(signal)

	view.add(<Circle size={100} position={signal} fill={'red'} />)
	view.add(
		<Line endArrow points={[0, velocity]} lineWidth={10} stroke="white" />
	)

	yield* signal(500, 1)
		.to([500, -500], 0.5, easeInOutCirc, Vector2.createPolarLerp(false, 0))
		.to(-500, 1, easeInBack, Vector2.lerp)
		.to(0, 3, easeInOutElastic, Vector2.createPolarLerp(false, -250))
})

Composability Example

One of the strengths of this feature is its composability. Users can take the derivative of a velocity signal to obtain the acceleration of the original signal. Here's a simple example:

const signal = Vector2.createSignal(0)
const velocity = derivateSignal(signal)
const acceleration = derivateSignal(velocity)

With this approach, users can easily explore and incorporate higher-order derivatives into their animations, providing a rich set of tools for crafting intricate and dynamic motion effects.

Why This Feature?

  1. Simplified API: Introducing a utility function for velocity provides a clean and straightforward way to access node velocity.

  2. Enhanced Readability: The updated usage makes it easier for users to understand and implement motion effects based on node velocity.

Current possible implementation

To acchieve velocity signals currently, @aarthificial provided the following classes.

VelocitySignalContext for numbers:

class VelocitySignalContext extends SignalContext<number, number> {
	private readonly delta

	public constructor(initial: SignalValue<number> | undefined) {
		super(initial, map)
		this.delta = createSignal([0, 0])
	}

	public override set(value: SignalValue<number> | typeof DEFAULT) {
		const previous = this.last
		this.extensions.setter(value)
		this.delta([this.last - previous, usePlayback().frame])
	}

	public getDelta(): number {
		const [delta, frame] = this.delta()
		if (usePlayback().frame > frame + 1) {
			return 0
		}

		return delta
	}

	public toSignal(): Signal<number, number, void, VelocitySignalContext> {
		return this.invokable
	}
}

export default makeScene2D(function* (view) {
	const signal = new VelocitySignalContext(0).toSignal()

	yield loop(Infinity, () => console.log(signal.context.getDelta()))
	yield* signal(1, 1)
	yield* waitFor(5)
})

VelocityVectorSignalContext for Vector2:

class VelocityVectorSignalContext extends CompoundSignalContext<
	PossibleVector2,
	Vector2
> {
	public constructor(initial: SignalValue<PossibleVector2> | undefined) {
		super(
			[
				[
					'x',
					new VelocitySignalContext(
						modify(initial, (value) => new Vector2(value).x)
					).toSignal(),
				],
				[
					'y',
					new VelocitySignalContext(
						modify(initial, (value) => new Vector2(value).y)
					).toSignal(),
				],
			],
			(value) => new Vector2(value),
			initial,
			Vector2.lerp
		)
	}

	public getDelta() {
		return this.parse(
			Object.fromEntries(
				this.signals.map(([key, property]) => [
					key,
					(property.context as VelocitySignalContext).getDelta(),
				])
			) as PossibleVector2
		)
	}

	public toSignal(): CompoundSignal<
		PossibleVector2,
		Vector2,
		keyof Vector2,
		void,
		VelocityVectorSignalContext
	> & {
		x: Signal<Length, number, void, VelocitySignalContext>
		y: Signal<Length, number, void, VelocitySignalContext>
	} {
		return this.invokable
	}
}

export default makeScene2D(function* (view) {
	const vec2signal = new VelocityVectorSignalContext(0).toSignal()

	view.add(<Circle size={100} position={vec2signal} fill={'red'} />)
	const vector = createRef<Line>()
	view.add(
		<Line ref={vector} endArrow points={[0, 0]} lineWidth={10} stroke="white" />
	)

	yield loop(Infinity, () => {
		vector().points([0, vec2signal.context.getDelta()])
	})
	yield* vec2signal(500, 1)
		.to([500, -500], 0.5, easeInOutCirc, Vector2.createPolarLerp(false, 0))
		.to(-500, 1, easeInBack, Vector2.lerp)
		.to(0, 3, easeInOutElastic, Vector2.createPolarLerp(false, -250))
})

With these you can opt in to the signals that track the delta only when you need them.

Adding the Delta to Number and Vector2 Signals

An additional consideration is to automatically provide all number based signals with their derivative signal, accessible through the delta property of the signal:

signal().delta

// Incorporating it as a signal in a component
<Line points={[0, signal().delta]} />

Since delta is a signal, users can also invoke it to obtain the current value:

signal().delta()

While this approach streamlines the process and ensures users have access to the velocity signal without explicit opt-ins, it comes with a caveat. There might be a potential performance overhead even when the functionality is not actively utilized. Consequently, careful consideration should be given to the trade-off between convenience and potential performance impact when deciding on this option.

@HikariIsChillin HikariIsChillin added the b-enhancement New feature or request label Feb 7, 2024
@aarthificial
Copy link
Contributor

I think the best API would be a mix of both propositions:

const signal = Vector2.createSignal(0);
const velocity = signal.derivate();

Here, a derivate method is called on the underlying SignalContext which takes care of creating the derivative signal.
Having a derivateSignal function is problematic because the creation of the signal will be different for compound signals vs basic signals. Having it handled by a method seems better because it allows us to override that process.

At the same time, it doesn't introduce any additional performance overhead, like the delta property would.

@aarthificial aarthificial removed their assignment Feb 8, 2024
@aarthificial aarthificial added c-accepted The issue is ready to be worked on a-core Relates to the core package labels Feb 8, 2024
@JakubC0I
Copy link

Hello I would like to take on this issue with my team

@HikariIsChillin
Copy link
Author

HikariIsChillin commented Mar 23, 2024

Signal effects #945 should probably be implemented first, since that functionality would likely be very useful for this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-core Relates to the core package b-enhancement New feature or request c-accepted The issue is ready to be worked on
Projects
None yet
Development

No branches or pull requests

3 participants