Full refactor of animation control
This commit is contained in:
parent
9f7b94a1a2
commit
127d2ee7a3
14
README.md
14
README.md
|
@ -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.speed = 0;
|
run.paused = true;
|
||||||
// Pause all animations!
|
// Pause all animations!
|
||||||
skinViewer.animationSpeed = 0;
|
skinViewer.animations.paused = true;
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -36,8 +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 = skinview3d.WalkingAnimation;
|
skinViewer.animations.add(skinview3d.WalkingAnimation);
|
||||||
skinViewer.animationSpeed = 1.5;
|
skinViewer.animations.speed = 1.5;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -20,26 +21,50 @@ 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 {
|
||||||
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 speed: number = 1.0;
|
remove(): void;
|
||||||
public readonly animation: Animation;
|
resetAndRemove(): void;
|
||||||
|
}
|
||||||
|
|
||||||
private lastTime = 0;
|
class AnimationWrapper implements SubAnimationHandle, IAnimation {
|
||||||
private progress = 0;
|
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) {
|
constructor(animation: Animation) {
|
||||||
this.animation = animation;
|
this.animation = animation;
|
||||||
}
|
}
|
||||||
|
|
||||||
play(player: PlayerObject, time: number) {
|
play(player: PlayerObject, time: number) {
|
||||||
this.progress += (time - this.lastTime) * this.speed;
|
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;
|
this.lastTime = time;
|
||||||
|
if (!this.paused) {
|
||||||
|
this.progress += delta * this.speed;
|
||||||
|
}
|
||||||
invokeAnimation(this.animation, player, this.progress);
|
invokeAnimation(this.animation, player, this.progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +75,10 @@ class AnimationWrapper implements AnimationHandle, IAnimation {
|
||||||
remove() {
|
remove() {
|
||||||
// stub get's overriden
|
// stub get's overriden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetAndRemove() {
|
||||||
|
this.toResetAndRemove = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompositeAnimation implements IAnimation {
|
export class CompositeAnimation implements IAnimation {
|
||||||
|
@ -58,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;
|
||||||
}
|
}
|
||||||
|
@ -68,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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
public detectModel: boolean = true;
|
public detectModel: boolean = true;
|
||||||
public animationSpeed: number = 1;
|
|
||||||
public animationTime: number = 0;
|
|
||||||
public disposed: boolean = false;
|
public disposed: boolean = false;
|
||||||
|
|
||||||
public readonly skinImg: HTMLImageElement;
|
public readonly skinImg: HTMLImageElement;
|
||||||
|
@ -40,11 +38,8 @@ export class SkinViewer {
|
||||||
|
|
||||||
public readonly playerObject: PlayerObject;
|
public readonly playerObject: PlayerObject;
|
||||||
|
|
||||||
private readonly clock: THREE.Clock;
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -116,14 +111,11 @@ export class SkinViewer {
|
||||||
if (options.width) this.width = options.width;
|
if (options.width) this.width = options.width;
|
||||||
if (options.height) this.height = options.height;
|
if (options.height) this.height = options.height;
|
||||||
|
|
||||||
this.clock = new THREE.Clock(true);
|
this.animations = new RootAnimation();
|
||||||
const draw = () => {
|
const draw = () => {
|
||||||
if (this.disposed) return;
|
if (this.disposed) return;
|
||||||
window.requestAnimationFrame(draw);
|
window.requestAnimationFrame(draw);
|
||||||
this.animationTime += this.clock.getDelta() * this.animationSpeed;
|
this.animations.runAnimationLoop(this.playerObject);
|
||||||
if (this.animation) {
|
|
||||||
invokeAnimation(this.animation, this.playerObject, this.animationTime);
|
|
||||||
}
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera);
|
||||||
};
|
};
|
||||||
draw();
|
draw();
|
||||||
|
|
Loading…
Reference in New Issue