Merge branch 'animation-refactor'

This commit is contained in:
yushijinhun 2020-01-01 00:34:00 +08:00
commit db1884e782
No known key found for this signature in database
GPG Key ID: 5BC167F73EA558E4
5 changed files with 88 additions and 48 deletions

View File

@ -42,23 +42,23 @@ Three.js powered Minecraft skin viewer.
control.enableZoom = false; control.enableZoom = false;
control.enablePan = false; control.enablePan = false;
skinViewer.animation = new skinview3d.CompositeAnimation();
// Add an animation // Add an animation
let walk = skinViewer.animation.add(skinview3d.WalkingAnimation); let walk = skinViewer.animations.add(skinview3d.WalkingAnimation);
// Add another animation // Add another animation
let rotate = skinViewer.animation.add(skinview3d.RotatingAnimation); let rotate = skinViewer.animations.add(skinview3d.RotatingAnimation);
// Remove an animation, stop walking dude // Remove an animation, stop walking dude
walk.remove(); walk.remove();
// Remove the rotating animation, and make the player face forward
rotate.resetAndRemove();
// And run for now! // And run for now!
let run = skinViewer.animation.add(skinview3d.RunningAnimation); let run = skinViewer.animations.add(skinview3d.RunningAnimation);
// Set the speed of an animation // Set the speed of an animation
run.speed = 3; run.speed = 3;
// Pause single animation // Pause single animation
run.paused = true; run.paused = true;
// Pause all animations! // Pause all animations!
skinViewer.animationPaused = true; skinViewer.animations.paused = true;
</script> </script>
``` ```

View File

@ -36,10 +36,8 @@
// skinViewer.playerObject.skin.slim = true; // skinViewer.playerObject.skin.slim = true;
let control = new skinview3d.createOrbitControls(skinViewer); let control = new skinview3d.createOrbitControls(skinViewer);
skinViewer.animation = new skinview3d.CompositeAnimation(); skinViewer.animations.add(skinview3d.WalkingAnimation);
skinViewer.animations.speed = 1.5;
let walk = skinViewer.animation.add(skinview3d.WalkingAnimation);
walk.speed = 1.5;
</script> </script>

View File

@ -1,4 +1,5 @@
import { PlayerObject } from "./model"; import { PlayerObject } from "./model";
import * as THREE from "three";
export interface IAnimation { export interface IAnimation {
play(player: PlayerObject, time: number): void; 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 // This interface is used to control animations
export interface AnimationHandle { export interface AnimationHandle {
paused: boolean;
speed: number; speed: number;
paused: boolean;
progress: number;
readonly animation: Animation; readonly animation: Animation;
reset(): void; reset(): void;
remove(): void;
} }
class AnimationWrapper implements AnimationHandle, IAnimation { export interface SubAnimationHandle extends AnimationHandle {
public paused = false; remove(): void;
public speed: number = 1.0; resetAndRemove(): void;
public readonly animation: Animation; }
private _paused = false; class AnimationWrapper implements SubAnimationHandle, IAnimation {
private _lastChange: number | null = null; speed: number = 1.0;
private _speed: number = 1.0; paused: boolean = false;
private _lastChangeX: number | null = null; progress: number = 0;
readonly animation: Animation;
private lastTime: number = 0;
private started: boolean = false;
private toResetAndRemove: boolean = false;
constructor(animation: Animation) { constructor(animation: Animation) {
this.animation = animation; this.animation = animation;
} }
play(player: PlayerObject, time: number) { play(player: PlayerObject, time: number) {
if (this._lastChange === null) { if (this.toResetAndRemove) {
this._lastChange = time; invokeAnimation(this.animation, player, 0);
this._lastChangeX = 0; this.remove();
} else if (this.paused !== this._paused || this.speed !== this._speed) { return;
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.paused === false) {
const dt = time - this._lastChange; let delta: number;
const x = this._lastChangeX! + this.speed * dt; if (this.started) {
invokeAnimation(this.animation, player, x); 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() { reset() {
this._lastChange = null; this.progress = 0;
} }
remove() { remove() {
// stub get's overriden // stub get's overriden
} }
resetAndRemove() {
this.toResetAndRemove = true;
}
} }
export class CompositeAnimation implements IAnimation { export class CompositeAnimation implements IAnimation {
@ -76,7 +87,9 @@ export class CompositeAnimation implements IAnimation {
add(animation: Animation): AnimationHandle { add(animation: Animation): AnimationHandle {
const handle = new AnimationWrapper(animation); const handle = new AnimationWrapper(animation);
handle.remove = () => this.handles.delete(handle); handle.remove = () => {
this.handles.delete(handle);
};
this.handles.add(handle); this.handles.add(handle);
return 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) => { export const WalkingAnimation: Animation = (player, time) => {
const skin = player.skin; const skin = player.skin;

View File

@ -20,8 +20,11 @@ export {
AnimationFn, AnimationFn,
Animation, Animation,
invokeAnimation, invokeAnimation,
SubAnimationHandle,
AnimationHandle, AnimationHandle,
CompositeAnimation, CompositeAnimation,
RootAnimation,
WalkingAnimation, WalkingAnimation,
RunningAnimation, RunningAnimation,

View File

@ -1,6 +1,6 @@
import * as THREE from "three"; import * as THREE from "three";
import { PlayerObject } from "./model"; import { PlayerObject } from "./model";
import { invokeAnimation } from "./animation"; import { RootAnimation } from "./animation";
import { loadSkinToCanvas, loadCapeToCanvas, isSlimSkin } from "./utils"; import { loadSkinToCanvas, loadCapeToCanvas, isSlimSkin } from "./utils";
export interface SkinViewerOptions { export interface SkinViewerOptions {
@ -16,10 +16,8 @@ export interface SkinViewerOptions {
export class SkinViewer { export class SkinViewer {
public readonly domElement: Node; public readonly domElement: Node;
public animation: Animation | null; public readonly animations: RootAnimation = new RootAnimation();
public detectModel: boolean = true; public detectModel: boolean = true;
public animationPaused: boolean = false;
public animationTime: number = 0;
public disposed: boolean = false; public disposed: boolean = false;
public readonly skinImg: HTMLImageElement; public readonly skinImg: HTMLImageElement;
@ -44,7 +42,6 @@ export class SkinViewer {
constructor(options: SkinViewerOptions) { constructor(options: SkinViewerOptions) {
this.domElement = options.domElement; this.domElement = options.domElement;
this.animation = options.animation || null;
if (options.detectModel === false) { if (options.detectModel === false) {
this.detectModel = false; this.detectModel = false;
} }
@ -123,12 +120,7 @@ export class SkinViewer {
if (this.disposed || this._renderPaused) { if (this.disposed || this._renderPaused) {
return; return;
} }
if (!this.animationPaused) { this.animations.runAnimationLoop(this.playerObject);
this.animationTime++;
if (this.animation) {
invokeAnimation(this.animation, this.playerObject, this.animationTime / 100.0);
}
}
this.renderer.render(this.scene, this.camera); this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(() => this.draw()); window.requestAnimationFrame(() => this.draw());
} }