skinview3d/src/animation.ts

198 lines
4.9 KiB
TypeScript
Raw Normal View History

import { Clock } from "three";
2020-01-24 12:34:50 +01:00
import { PlayerObject } from "./model.js";
2018-07-17 20:49:00 +02:00
2018-08-17 06:46:04 +02:00
export interface IAnimation {
play(player: PlayerObject, time: number): void;
}
export type AnimationFn = (player: PlayerObject, time: number) => void;
export type Animation = AnimationFn | IAnimation;
2020-02-01 12:13:46 +01:00
export function invokeAnimation(animation: Animation, player: PlayerObject, time: number): void {
2018-08-17 06:15:02 +02:00
if (animation instanceof Function) {
2018-01-06 15:01:12 +01:00
animation(player, time);
} else {
2018-08-17 06:15:02 +02:00
// must be IAnimation here
animation.play(player, time);
2018-01-06 15:01:12 +01:00
}
}
2018-08-17 06:46:04 +02:00
// This interface is used to control animations
export interface AnimationHandle {
speed: number;
2019-12-22 13:19:33 +01:00
paused: boolean;
progress: number;
2018-08-17 06:46:04 +02:00
readonly animation: Animation;
2018-07-17 20:49:00 +02:00
2018-08-17 06:46:04 +02:00
reset(): void;
2019-12-22 13:19:33 +01:00
}
export interface SubAnimationHandle extends AnimationHandle {
2018-08-17 06:46:04 +02:00
remove(): void;
2019-12-22 13:19:33 +01:00
resetAndRemove(): void;
2018-08-17 06:46:04 +02:00
}
2019-12-22 13:19:33 +01:00
class AnimationWrapper implements SubAnimationHandle, IAnimation {
speed: number = 1.0;
paused: boolean = false;
progress: number = 0;
readonly animation: Animation;
2018-07-17 20:49:00 +02:00
2019-12-22 13:19:33 +01:00
private lastTime: number = 0;
private started: boolean = false;
private toResetAndRemove: boolean = false;
2018-07-17 20:49:00 +02:00
constructor(animation: Animation) {
this.animation = animation;
2018-01-06 15:01:12 +01:00
}
2018-07-17 20:49:00 +02:00
2020-02-01 12:13:46 +01:00
play(player: PlayerObject, time: number): void {
2019-12-22 13:19:33 +01:00
if (this.toResetAndRemove) {
invokeAnimation(this.animation, player, 0);
this.remove();
return;
}
let delta: number;
if (this.started) {
delta = time - this.lastTime;
} else {
delta = 0;
this.started = true;
}
this.lastTime = time;
2019-12-22 13:19:33 +01:00
if (!this.paused) {
this.progress += delta * this.speed;
}
invokeAnimation(this.animation, player, this.progress);
2018-01-06 15:01:12 +01:00
}
2018-07-17 20:49:00 +02:00
2020-02-01 12:13:46 +01:00
reset(): void {
this.progress = 0;
2018-01-06 15:01:12 +01:00
}
2018-07-17 20:49:00 +02:00
2020-02-01 12:13:46 +01:00
remove(): void {
// stub get's overriden
2018-07-17 20:49:00 +02:00
}
2019-12-22 13:19:33 +01:00
2020-02-01 12:13:46 +01:00
resetAndRemove(): void {
2019-12-22 13:19:33 +01:00
this.toResetAndRemove = true;
}
2018-01-06 15:01:12 +01:00
}
2018-08-17 06:46:04 +02:00
export class CompositeAnimation implements IAnimation {
2018-08-17 06:46:04 +02:00
readonly handles: Set<AnimationHandle & IAnimation> = new Set();
2018-07-17 20:49:00 +02:00
2018-08-17 06:46:04 +02:00
add(animation: Animation): AnimationHandle {
const handle = new AnimationWrapper(animation);
2020-02-01 12:13:46 +01:00
handle.remove = (): void => {
2019-12-22 13:19:33 +01:00
this.handles.delete(handle);
};
this.handles.add(handle);
return handle;
2018-01-06 15:01:12 +01:00
}
2018-08-17 06:46:04 +02:00
2020-02-01 12:13:46 +01:00
play(player: PlayerObject, time: number): void {
2018-01-06 15:01:12 +01:00
this.handles.forEach(handle => handle.play(player, time));
}
}
2019-12-22 13:19:33 +01:00
export class RootAnimation extends CompositeAnimation implements AnimationHandle {
speed: number = 1.0;
progress: number = 0.0;
2020-05-29 10:34:58 +02:00
private readonly clock: Clock = new Clock(true);
2019-12-22 13:19:33 +01:00
2020-02-01 12:13:46 +01:00
get animation(): RootAnimation {
2019-12-22 13:19:33 +01:00
return this;
}
2020-02-01 12:13:46 +01:00
get paused(): boolean {
2019-12-22 13:19:33 +01:00
return !this.clock.running;
}
2020-02-01 12:13:46 +01:00
set paused(value: boolean) {
2019-12-22 13:19:33 +01:00
if (value) {
this.clock.stop();
} else {
this.clock.start();
}
}
runAnimationLoop(player: PlayerObject): void {
2019-12-23 11:15:04 +01:00
if (this.handles.size === 0) {
2019-12-22 13:19:33 +01:00
return;
}
this.progress += this.clock.getDelta() * this.speed;
this.play(player, this.progress);
}
2020-02-01 12:13:46 +01:00
reset(): void {
2019-12-22 13:19:33 +01:00
this.progress = 0;
}
}
2018-08-17 06:15:02 +02:00
export const WalkingAnimation: Animation = (player, time) => {
2018-07-21 14:52:02 +02:00
const skin = player.skin;
2017-10-01 14:00:45 +02:00
// Multiply by animation's natural speed
time *= 8;
// Leg swing
2018-07-17 20:49:00 +02:00
skin.leftLeg.rotation.x = Math.sin(time) * 0.5;
skin.rightLeg.rotation.x = Math.sin(time + Math.PI) * 0.5;
2017-10-01 14:00:45 +02:00
// Arm swing
2018-07-17 20:49:00 +02:00
skin.leftArm.rotation.x = Math.sin(time + Math.PI) * 0.5;
skin.rightArm.rotation.x = Math.sin(time) * 0.5;
2018-07-21 14:52:02 +02:00
const basicArmRotationZ = Math.PI * 0.02;
2018-07-17 20:49:00 +02:00
skin.leftArm.rotation.z = Math.cos(time) * 0.03 + basicArmRotationZ;
skin.rightArm.rotation.z = Math.cos(time + Math.PI) * 0.03 - basicArmRotationZ;
// Head shaking with different frequency & amplitude
skin.head.rotation.y = Math.sin(time / 4) * 0.2;
skin.head.rotation.x = Math.sin(time / 5) * 0.1;
// Always add an angle for cape around the x axis
2018-07-21 14:52:02 +02:00
const basicCapeRotationX = Math.PI * 0.06;
player.cape.rotation.x = Math.sin(time / 1.5) * 0.06 + basicCapeRotationX;
};
2018-08-17 06:15:02 +02:00
export const RunningAnimation: Animation = (player, time) => {
2018-07-21 14:52:02 +02:00
const skin = player.skin;
time *= 15;
// Leg swing with larger amplitude
2018-07-17 20:49:00 +02:00
skin.leftLeg.rotation.x = Math.cos(time + Math.PI) * 1.3;
skin.rightLeg.rotation.x = Math.cos(time) * 1.3;
// Arm swing
2018-07-17 20:49:00 +02:00
skin.leftArm.rotation.x = Math.cos(time) * 1.5;
skin.rightArm.rotation.x = Math.cos(time + Math.PI) * 1.5;
2018-07-21 14:52:02 +02:00
const basicArmRotationZ = Math.PI * 0.1;
2018-07-17 20:49:00 +02:00
skin.leftArm.rotation.z = Math.cos(time) * 0.1 + basicArmRotationZ;
skin.rightArm.rotation.z = Math.cos(time + Math.PI) * 0.1 - basicArmRotationZ;
// Jumping
player.position.y = Math.cos(time * 2);
// Dodging when running
player.position.x = Math.cos(time) * 0.15;
// Slightly tilting when running
player.rotation.z = Math.cos(time + Math.PI) * 0.01;
// Apply higher swing frequency, lower amplitude,
// and greater basic rotation around x axis,
// to cape when running.
2018-07-21 14:52:02 +02:00
const basicCapeRotationX = Math.PI * 0.3;
player.cape.rotation.x = Math.sin(time * 2) * 0.1 + basicCapeRotationX;
// What about head shaking?
// You shouldn't glance right and left when running dude :P
};
2018-08-17 06:15:02 +02:00
export const RotatingAnimation: Animation = (player, time) => {
player.rotation.y = time;
2017-10-01 14:00:45 +02:00
};