diff --git a/renderer/src/remotion/WheelComposition.jsx b/renderer/src/remotion/WheelComposition.jsx index 18ee826..5e7050a 100644 --- a/renderer/src/remotion/WheelComposition.jsx +++ b/renderer/src/remotion/WheelComposition.jsx @@ -6,6 +6,7 @@ import { getFinalWheelRotationDegrees, getSliceCenterDegrees, getSliceDegrees, + getWheelRotationDegrees, rightPointerClipPath, } from './wheel-layout.js'; import {getContrastingTextColor, getRadialLabelLayout} from './wheel-label-layout.js'; @@ -27,11 +28,6 @@ export const WheelComposition = (props) => { const theme = getTheme(props.theme); const spinFrames = Math.max(1, durationInFrames - Math.round((props.holdMs / 1000) * fps)); const holdStartFrame = spinFrames; - const progress = interpolate(frame, [0, spinFrames], [0, 1], { - easing: Easing.out(Easing.cubic), - extrapolateLeft: 'clamp', - extrapolateRight: 'clamp', - }); const settle = interpolate(frame, [holdStartFrame, durationInFrames - 1], [0, 1], { easing: Easing.out(Easing.sin), extrapolateLeft: 'clamp', @@ -47,8 +43,11 @@ export const WheelComposition = (props) => { const sliceDegrees = getSliceDegrees(props.options.length); const finalRotation = getFinalWheelRotationDegrees(props.options.length, props.winnerIndex); const startRotation = -24; - const rotation = interpolate(progress, [0, 1], [startRotation, finalRotation], { - easing: Easing.out(Easing.cubic), + const rotation = getWheelRotationDegrees({ + finalRotationDegrees: finalRotation, + frame, + spinFrames, + startRotationDegrees: startRotation, }); return ( diff --git a/renderer/src/remotion/wheel-layout.js b/renderer/src/remotion/wheel-layout.js index d641bcf..949f302 100644 --- a/renderer/src/remotion/wheel-layout.js +++ b/renderer/src/remotion/wheel-layout.js @@ -35,3 +35,35 @@ export const getFinalWheelRotationDegrees = (optionCount, winnerIndex, fullTurns * @returns {number} */ export const cssDegreesToPieRadians = (cssDegrees) => ((cssDegrees - 270) * Math.PI) / 180; + +/** + * @param {number} value + * @param {number} min + * @param {number} max + * @returns {number} + */ +const clamp = (value, min, max) => Math.min(Math.max(value, min), max); + +/** + * @param {number} frame + * @param {number} spinFrames + * @returns {number} + */ +export const getSpinProgress = (frame, spinFrames) => { + const safeSpinFrames = Math.max(1, spinFrames); + const progress = clamp(frame / safeSpinFrames, 0, 1); + + return 1 - (1 - progress) ** 3; +}; + +/** + * @param {object} input + * @param {number} input.frame + * @param {number} input.spinFrames + * @param {number} input.startRotationDegrees + * @param {number} input.finalRotationDegrees + * @returns {number} + */ +export const getWheelRotationDegrees = ({frame, spinFrames, startRotationDegrees, finalRotationDegrees}) => + startRotationDegrees + + (finalRotationDegrees - startRotationDegrees) * getSpinProgress(frame, spinFrames); diff --git a/renderer/test/wheel-layout.test.js b/renderer/test/wheel-layout.test.js index 4f067d0..484e199 100644 --- a/renderer/test/wheel-layout.test.js +++ b/renderer/test/wheel-layout.test.js @@ -4,6 +4,8 @@ import { getFinalWheelRotationDegrees, getSliceCenterDegrees, getSliceDegrees, + getSpinProgress, + getWheelRotationDegrees, rightPointerClipPath, } from '../src/remotion/wheel-layout.js'; @@ -43,6 +45,25 @@ describe('wheel layout', () => { expect(getRenderedStartDegrees(270)).toBe(270); }); + test('eases spin progress once across the spin duration', () => { + expect(getSpinProgress(0, 100)).toBe(0); + expect(getSpinProgress(25, 100)).toBeCloseTo(0.578125); + expect(getSpinProgress(50, 100)).toBeCloseTo(0.875); + expect(getSpinProgress(100, 100)).toBe(1); + expect(getSpinProgress(125, 100)).toBe(1); + }); + + test('maps eased spin progress to wheel rotation', () => { + const rotation = getWheelRotationDegrees({ + finalRotationDegrees: 696, + frame: 25, + spinFrames: 100, + startRotationDegrees: -24, + }); + + expect(rotation).toBeCloseTo(392.25); + }); + test('defines a right-side pointer that points into the wheel', () => { expect(rightPointerClipPath).toBe('polygon(0 50%, 100% 0, 100% 100%)'); });