Factor out creature-animator.tsx.
rodzic
0c947a7c72
commit
d3e01a4865
|
@ -0,0 +1,84 @@
|
||||||
|
import { getBoundingBoxCenter } from "./bounding-box";
|
||||||
|
import { SvgSymbolData } from "./svg-symbol";
|
||||||
|
import {
|
||||||
|
svgRotate,
|
||||||
|
SvgTransform,
|
||||||
|
svgTransformOrigin,
|
||||||
|
svgTranslate,
|
||||||
|
} from "./svg-transform";
|
||||||
|
|
||||||
|
type AnimationTransformer = (
|
||||||
|
animPct: number,
|
||||||
|
animScale: number,
|
||||||
|
symbol: SvgSymbolData
|
||||||
|
) => SvgTransform[];
|
||||||
|
|
||||||
|
export interface CreatureAnimator {
|
||||||
|
getSvgTransforms: AnimationTransformer;
|
||||||
|
getChildAnimator(): CreatureAnimator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any function that takes a number in the range [0, 1] and
|
||||||
|
* transforms it to a number in the same range, for the
|
||||||
|
* purposes of animation easing.
|
||||||
|
*/
|
||||||
|
type EasingFunction = (t: number) => number;
|
||||||
|
|
||||||
|
// https://gist.github.com/gre/1650294
|
||||||
|
const easeInOutQuad: EasingFunction = (t) =>
|
||||||
|
t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease from 0, get to 1 by the time t=0.5, and then
|
||||||
|
* ease back to 0.
|
||||||
|
*/
|
||||||
|
const easeInOutQuadPingPong: EasingFunction = (t) => {
|
||||||
|
if (t < 0.5) {
|
||||||
|
return easeInOutQuad(t * 2);
|
||||||
|
}
|
||||||
|
return 1 - easeInOutQuad((t - 0.5) * 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a percentage (number in the range [0, 1]) to
|
||||||
|
* a number in the range [-1, 1].
|
||||||
|
*/
|
||||||
|
function pctToNegativeOneToOne(pct: number) {
|
||||||
|
return (pct - 0.5) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Y_HOVER_AMPLITUDE = 25.0;
|
||||||
|
|
||||||
|
const hoverTransformer: AnimationTransformer = (animPct, animScale) => {
|
||||||
|
const yHover =
|
||||||
|
pctToNegativeOneToOne(easeInOutQuadPingPong(animPct)) *
|
||||||
|
Y_HOVER_AMPLITUDE *
|
||||||
|
animScale;
|
||||||
|
return [svgTranslate({ x: 0, y: yHover })];
|
||||||
|
};
|
||||||
|
|
||||||
|
const spinTransformer: AnimationTransformer = (animPct, animScale, symbol) => {
|
||||||
|
const origin = getBoundingBoxCenter(symbol.bbox);
|
||||||
|
return [svgTransformOrigin(origin, [svgRotate(animPct * 360)])];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hoverAnimator: CreatureAnimator = {
|
||||||
|
getSvgTransforms: hoverTransformer,
|
||||||
|
getChildAnimator: () => hoverAnimator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const spinAnimator: CreatureAnimator = {
|
||||||
|
getSvgTransforms: spinTransformer,
|
||||||
|
getChildAnimator: () => spinAnimator,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hoverAndSpinAnimator: CreatureAnimator = {
|
||||||
|
getSvgTransforms: hoverTransformer,
|
||||||
|
getChildAnimator: () => spinAnimator,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nullAnimator: CreatureAnimator = {
|
||||||
|
getSvgTransforms: () => [],
|
||||||
|
getChildAnimator: () => nullAnimator,
|
||||||
|
};
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useContext } from "react";
|
import React, { useContext, useMemo } from "react";
|
||||||
import { BBox, Point } from "../vendor/bezier-js";
|
import { BBox, Point } from "../vendor/bezier-js";
|
||||||
import { getAttachmentTransforms } from "./attach";
|
import { getAttachmentTransforms } from "./attach";
|
||||||
import { getBoundingBoxCenter, uniformlyScaleToFit } from "./bounding-box";
|
import { getBoundingBoxCenter, uniformlyScaleToFit } from "./bounding-box";
|
||||||
|
import { CreatureAnimator, nullAnimator } from "./creature-animator";
|
||||||
import { scalePointXY, subtractPoints } from "./point";
|
import { scalePointXY, subtractPoints } from "./point";
|
||||||
import { AttachmentPointType } from "./specs";
|
import { AttachmentPointType } from "./specs";
|
||||||
import {
|
import {
|
||||||
|
@ -49,24 +50,22 @@ export type CreatureSymbol = {
|
||||||
nests: NestedCreatureSymbol[];
|
nests: NestedCreatureSymbol[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type AnimationType = "hover" | "rotate";
|
|
||||||
|
|
||||||
export type CreatureSymbolProps = CreatureSymbol & {
|
export type CreatureSymbolProps = CreatureSymbol & {
|
||||||
animType?: AnimationType;
|
animator?: CreatureAnimator;
|
||||||
animPct?: number;
|
animPct?: number;
|
||||||
animScale?: number;
|
animScale?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NestedCreatureSymbolProps = NestedCreatureSymbol & {
|
type NestedCreatureSymbolProps = NestedCreatureSymbol & {
|
||||||
parent: SvgSymbolData;
|
parent: SvgSymbolData;
|
||||||
animType: AnimationType;
|
animator: CreatureAnimator;
|
||||||
animPct: number;
|
animPct: number;
|
||||||
animScale: number;
|
animScale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AttachedCreatureSymbolProps = AttachedCreatureSymbol & {
|
type AttachedCreatureSymbolProps = AttachedCreatureSymbol & {
|
||||||
parent: SvgSymbolData;
|
parent: SvgSymbolData;
|
||||||
animType: AnimationType;
|
animator: CreatureAnimator;
|
||||||
animPct: number;
|
animPct: number;
|
||||||
animScale: number;
|
animScale: number;
|
||||||
};
|
};
|
||||||
|
@ -218,55 +217,20 @@ const NestedCreatureSymbol: React.FC<NestedCreatureSymbolProps> = ({
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Any function that takes a number in the range [0, 1] and
|
|
||||||
* transforms it to a number in the same range, for the
|
|
||||||
* purposes of animation easing.
|
|
||||||
*/
|
|
||||||
type EasingFunction = (t: number) => number;
|
|
||||||
|
|
||||||
// https://gist.github.com/gre/1650294
|
|
||||||
const easeInOutQuad: EasingFunction = (t) =>
|
|
||||||
t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ease from 0, get to 1 by the time t=0.5, and then
|
|
||||||
* ease back to 0.
|
|
||||||
*/
|
|
||||||
const easeInOutQuadPingPong: EasingFunction = (t) => {
|
|
||||||
if (t < 0.5) {
|
|
||||||
return easeInOutQuad(t * 2);
|
|
||||||
}
|
|
||||||
return 1 - easeInOutQuad((t - 0.5) * 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a percentage (number in the range [0, 1]) to
|
|
||||||
* a number in the range [-1, 1].
|
|
||||||
*/
|
|
||||||
function pctToNegativeOneToOne(pct: number) {
|
|
||||||
return (pct - 0.5) * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Y_HOVER_AMPLITUDE = 25.0;
|
|
||||||
const CHILD_ANIM_SCALE_MULTIPLIER = 0.5;
|
const CHILD_ANIM_SCALE_MULTIPLIER = 0.5;
|
||||||
|
|
||||||
export const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
export const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
||||||
let ctx = useContext(CreatureContext);
|
let ctx = useContext(CreatureContext);
|
||||||
const { data, attachments, nests } = props;
|
const { data, attachments, nests } = props;
|
||||||
const attachmentCtx: CreatureContextType = { ...ctx, parent: data };
|
const attachmentCtx: CreatureContextType = { ...ctx, parent: data };
|
||||||
const animType = props.animType ?? "hover";
|
const animator = props.animator ?? nullAnimator;
|
||||||
const animPct = props.animPct ?? 0;
|
const animPct = props.animPct ?? 0;
|
||||||
const animScale = props.animScale ?? 1;
|
const animScale = props.animScale ?? 1;
|
||||||
const yHover =
|
const svgTransforms = useMemo(
|
||||||
pctToNegativeOneToOne(easeInOutQuadPingPong(animPct)) *
|
() => animator.getSvgTransforms(animPct, animScale, data),
|
||||||
Y_HOVER_AMPLITUDE *
|
[animator, animPct, animScale, data]
|
||||||
animScale;
|
);
|
||||||
const origin = getBoundingBoxCenter(data.bbox);
|
const childAnimator = useMemo(() => animator.getChildAnimator(), [animator]);
|
||||||
const svgTransforms =
|
|
||||||
animType === "hover"
|
|
||||||
? [svgTranslate({ x: 0, y: yHover })]
|
|
||||||
: [svgRotate(animPct * 360)];
|
|
||||||
|
|
||||||
if (props.invertColors) {
|
if (props.invertColors) {
|
||||||
ctx = swapColors(ctx);
|
ctx = swapColors(ctx);
|
||||||
|
@ -280,7 +244,7 @@ export const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
||||||
// appear behind our symbol, while anything nested within our symbol
|
// appear behind our symbol, while anything nested within our symbol
|
||||||
// should be after our symbol so they appear in front of it.
|
// should be after our symbol so they appear in front of it.
|
||||||
return (
|
return (
|
||||||
<SvgTransform transform={[svgTransformOrigin(origin, svgTransforms)]}>
|
<SvgTransform transform={svgTransforms}>
|
||||||
{attachments.length && (
|
{attachments.length && (
|
||||||
<CreatureContext.Provider value={attachmentCtx}>
|
<CreatureContext.Provider value={attachmentCtx}>
|
||||||
{attachments.map((a, i) => (
|
{attachments.map((a, i) => (
|
||||||
|
@ -290,7 +254,7 @@ export const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
||||||
parent={data}
|
parent={data}
|
||||||
animPct={animPct}
|
animPct={animPct}
|
||||||
animScale={animScale * CHILD_ANIM_SCALE_MULTIPLIER}
|
animScale={animScale * CHILD_ANIM_SCALE_MULTIPLIER}
|
||||||
animType="rotate"
|
animator={childAnimator}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</CreatureContext.Provider>
|
</CreatureContext.Provider>
|
||||||
|
@ -305,7 +269,7 @@ export const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
||||||
parent={data}
|
parent={data}
|
||||||
animPct={animPct}
|
animPct={animPct}
|
||||||
animScale={animScale * CHILD_ANIM_SCALE_MULTIPLIER}
|
animScale={animScale * CHILD_ANIM_SCALE_MULTIPLIER}
|
||||||
animType="rotate"
|
animator={childAnimator}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</CreatureContext.Provider>
|
</CreatureContext.Provider>
|
||||||
|
|
|
@ -47,6 +47,7 @@ import { GalleryWidget } from "../../gallery-widget";
|
||||||
import { serializeCreatureDesign } from "./serialization";
|
import { serializeCreatureDesign } from "./serialization";
|
||||||
import { CreatureEditorWidget } from "./creature-editor";
|
import { CreatureEditorWidget } from "./creature-editor";
|
||||||
import { useAnimationPct } from "../../animation";
|
import { useAnimationPct } from "../../animation";
|
||||||
|
import { hoverAndSpinAnimator } from "../../creature-animator";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum number of attachment points that any symbol used as the main body
|
* The minimum number of attachment points that any symbol used as the main body
|
||||||
|
@ -380,7 +381,11 @@ function createCreatureAnimationRenderer(
|
||||||
return (
|
return (
|
||||||
<SvgTransform transform={svgScale(scale)}>
|
<SvgTransform transform={svgScale(scale)}>
|
||||||
<CreatureContext.Provider value={ctx}>
|
<CreatureContext.Provider value={ctx}>
|
||||||
<CreatureSymbol {...creature} animPct={animPct} />
|
<CreatureSymbol
|
||||||
|
{...creature}
|
||||||
|
animPct={animPct}
|
||||||
|
animator={hoverAndSpinAnimator}
|
||||||
|
/>
|
||||||
</CreatureContext.Provider>
|
</CreatureContext.Provider>
|
||||||
</SvgTransform>
|
</SvgTransform>
|
||||||
);
|
);
|
||||||
|
|
Ładowanie…
Reference in New Issue