diff --git a/README.md b/README.md index 5f0a0b2..cffd952 100644 --- a/README.md +++ b/README.md @@ -42,23 +42,23 @@ Three.js powered Minecraft skin viewer. control.enableZoom = false; control.enablePan = false; - skinViewer.animation = new skinview3d.CompositeAnimation(); - // Add an animation - let walk = skinViewer.animation.add(skinview3d.WalkingAnimation); + let walk = skinViewer.animations.add(skinview3d.WalkingAnimation); // Add another animation - let rotate = skinViewer.animation.add(skinview3d.RotatingAnimation); + let rotate = skinViewer.animations.add(skinview3d.RotatingAnimation); // Remove an animation, stop walking dude walk.remove(); + // Remove the rotating animation, and make the player face forward + rotate.resetAndRemove(); // And run for now! - let run = skinViewer.animation.add(skinview3d.RunningAnimation); + let run = skinViewer.animations.add(skinview3d.RunningAnimation); // Set the speed of an animation run.speed = 3; // Pause single animation run.paused = true; // Pause all animations! - skinViewer.animationPaused = true; + skinViewer.animations.paused = true; ``` diff --git a/examples/index.html b/examples/index.html index f8e0e7a..c0800a9 100644 --- a/examples/index.html +++ b/examples/index.html @@ -36,10 +36,8 @@ // skinViewer.playerObject.skin.slim = true; let control = new skinview3d.createOrbitControls(skinViewer); - skinViewer.animation = new skinview3d.CompositeAnimation(); - - let walk = skinViewer.animation.add(skinview3d.WalkingAnimation); - walk.speed = 1.5; + skinViewer.animations.add(skinview3d.WalkingAnimation); + skinViewer.animations.speed = 1.5; diff --git a/src/animation.ts b/src/animation.ts index 2e89024..973b1ca 100644 --- a/src/animation.ts +++ b/src/animation.ts @@ -1,4 +1,5 @@ import { PlayerObject } from "./model"; +import * as THREE from "three"; export interface IAnimation { play(player: PlayerObject, time: number): void; @@ -19,55 +20,65 @@ export function invokeAnimation(animation: Animation, player: PlayerObject, time // This interface is used to control animations export interface AnimationHandle { - paused: boolean; speed: number; + paused: boolean; + progress: number; readonly animation: Animation; reset(): void; - remove(): void; } -class AnimationWrapper implements AnimationHandle, IAnimation { - public paused = false; - public speed: number = 1.0; - public readonly animation: Animation; +export interface SubAnimationHandle extends AnimationHandle { + remove(): void; + resetAndRemove(): void; +} - private _paused = false; - private _lastChange: number | null = null; - private _speed: number = 1.0; - private _lastChangeX: number | null = null; +class AnimationWrapper implements SubAnimationHandle, IAnimation { + speed: number = 1.0; + paused: boolean = false; + progress: number = 0; + readonly animation: Animation; + + private lastTime: number = 0; + private started: boolean = false; + private toResetAndRemove: boolean = false; constructor(animation: Animation) { this.animation = animation; } play(player: PlayerObject, time: number) { - if (this._lastChange === null) { - this._lastChange = time; - this._lastChangeX = 0; - } else if (this.paused !== this._paused || this.speed !== this._speed) { - const dt = time - this._lastChange; - if (this._paused === false) { - this._lastChangeX! += dt * this._speed; - } - this._paused = this.paused; - this._speed = this.speed; - this._lastChange = time; + if (this.toResetAndRemove) { + invokeAnimation(this.animation, player, 0); + this.remove(); + return; } - if (this.paused === false) { - const dt = time - this._lastChange; - const x = this._lastChangeX! + this.speed * dt; - invokeAnimation(this.animation, player, x); + + let delta: number; + if (this.started) { + delta = time - this.lastTime; + } else { + delta = 0; + this.started = true; } + this.lastTime = time; + if (!this.paused) { + this.progress += delta * this.speed; + } + invokeAnimation(this.animation, player, this.progress); } reset() { - this._lastChange = null; + this.progress = 0; } remove() { // stub get's overriden } + + resetAndRemove() { + this.toResetAndRemove = true; + } } export class CompositeAnimation implements IAnimation { @@ -76,7 +87,9 @@ export class CompositeAnimation implements IAnimation { add(animation: Animation): AnimationHandle { const handle = new AnimationWrapper(animation); - handle.remove = () => this.handles.delete(handle); + handle.remove = () => { + this.handles.delete(handle); + }; this.handles.add(handle); return handle; } @@ -86,6 +99,40 @@ export class CompositeAnimation implements IAnimation { } } +export class RootAnimation extends CompositeAnimation implements AnimationHandle { + speed: number = 1.0; + progress: number = 0.0; + readonly clock: THREE.Clock = new THREE.Clock(true); + + get animation() { + return this; + } + + get paused() { + return !this.clock.running; + } + + set paused(value) { + if (value) { + this.clock.stop(); + } else { + this.clock.start(); + } + } + + runAnimationLoop(player: PlayerObject): void { + if (this.handles.size === 0) { + return; + } + this.progress += this.clock.getDelta() * this.speed; + this.play(player, this.progress); + } + + reset() { + this.progress = 0; + } +} + export const WalkingAnimation: Animation = (player, time) => { const skin = player.skin; diff --git a/src/skinview3d.ts b/src/skinview3d.ts index 8080fde..f12cc7f 100644 --- a/src/skinview3d.ts +++ b/src/skinview3d.ts @@ -20,8 +20,11 @@ export { AnimationFn, Animation, invokeAnimation, + + SubAnimationHandle, AnimationHandle, CompositeAnimation, + RootAnimation, WalkingAnimation, RunningAnimation, diff --git a/src/viewer.ts b/src/viewer.ts index d9f0aa1..cca75fb 100644 --- a/src/viewer.ts +++ b/src/viewer.ts @@ -1,6 +1,6 @@ import * as THREE from "three"; import { PlayerObject } from "./model"; -import { invokeAnimation } from "./animation"; +import { RootAnimation } from "./animation"; import { loadSkinToCanvas, loadCapeToCanvas, isSlimSkin } from "./utils"; export interface SkinViewerOptions { @@ -16,10 +16,8 @@ export interface SkinViewerOptions { export class SkinViewer { public readonly domElement: Node; - public animation: Animation | null; + public readonly animations: RootAnimation = new RootAnimation(); public detectModel: boolean = true; - public animationPaused: boolean = false; - public animationTime: number = 0; public disposed: boolean = false; public readonly skinImg: HTMLImageElement; @@ -44,7 +42,6 @@ export class SkinViewer { constructor(options: SkinViewerOptions) { this.domElement = options.domElement; - this.animation = options.animation || null; if (options.detectModel === false) { this.detectModel = false; } @@ -123,12 +120,7 @@ export class SkinViewer { if (this.disposed || this._renderPaused) { return; } - if (!this.animationPaused) { - this.animationTime++; - if (this.animation) { - invokeAnimation(this.animation, this.playerObject, this.animationTime / 100.0); - } - } + this.animations.runAnimationLoop(this.playerObject); this.renderer.render(this.scene, this.camera); window.requestAnimationFrame(() => this.draw()); }