UNPKG

skin3d

Version:

A fast, customizable Minecraft skin viewer powered by Three.js. Easily render and preview Minecraft skins in 3D for your projects.

349 lines 13.6 kB
import { PlayerObject } from "./model.js"; /** * Abstract base class for animations that can be played on a PlayerObject. */ export class PlayerAnimation { constructor() { /** Animation speed multiplier. @defaultValue 1.0 */ Object.defineProperty(this, "speed", { enumerable: true, configurable: true, writable: true, value: 1.0 }); /** Whether the animation is paused. @defaultValue false */ Object.defineProperty(this, "paused", { enumerable: true, configurable: true, writable: true, value: false }); /** Current animation progress. */ Object.defineProperty(this, "progress", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "currentId", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "progress0", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "animationObjects", { enumerable: true, configurable: true, writable: true, value: new Map() }); } /** * Update the animation state. * @param player - The player object. * @param deltaTime - Time elapsed since last call. */ update(player, deltaTime) { if (this.paused) return; const delta = deltaTime * this.speed; this.animate(player, delta); this.animationObjects.forEach((animation, id) => { const progress0 = this.progress0.get(id); animation(player, this.progress - progress0, id); }); this.progress += delta; } /** * Add a new animation function and return its id. * @param fn - Animation function (player, progress, id). * @returns The id of the newly added animation. */ addAnimation(fn) { const id = this.currentId++; this.progress0.set(id, this.progress); this.animationObjects.set(id, fn); return id; } /** * Remove an animation by its id. * @param id - The id of the animation to remove. */ removeAnimation(id) { if (id !== undefined) { this.animationObjects.delete(id); this.progress0.delete(id); } } } /** * Animation from a function. */ export class FunctionAnimation extends PlayerAnimation { constructor(fn) { super(); Object.defineProperty(this, "fn", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.fn = fn; } animate(player, delta) { this.fn(player, this.progress, delta); } } /** * Idle animation (arms and cape sway gently). */ export class IdleAnimation extends PlayerAnimation { animate(player) { const t = this.progress * 2; const basicArmRotationZ = Math.PI * 0.02; player.skin.leftArm.rotation.z = Math.cos(t) * 0.03 + basicArmRotationZ; player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ; const basicCapeRotationX = Math.PI * 0.06; player.cape.rotation.x = Math.sin(t) * 0.01 + basicCapeRotationX; } } /** * Walking animation (arms and legs swing, head bobs). */ export class WalkingAnimation extends PlayerAnimation { constructor() { super(...arguments); /** Whether to shake head when walking. @defaultValue true */ Object.defineProperty(this, "headBobbing", { enumerable: true, configurable: true, writable: true, value: true }); } animate(player) { const t = this.progress * 8; player.skin.leftLeg.rotation.x = Math.sin(t) * 0.5; player.skin.rightLeg.rotation.x = Math.sin(t + Math.PI) * 0.5; player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.5; player.skin.rightArm.rotation.x = Math.sin(t) * 0.5; const basicArmRotationZ = Math.PI * 0.02; player.skin.leftArm.rotation.z = Math.cos(t) * 0.03 + basicArmRotationZ; player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.03 - basicArmRotationZ; if (this.headBobbing) { player.skin.head.rotation.y = Math.sin(t / 4) * 0.2; player.skin.head.rotation.x = Math.sin(t / 5) * 0.1; } else { player.skin.head.rotation.y = 0; player.skin.head.rotation.x = 0; } const basicCapeRotationX = Math.PI * 0.06; player.cape.rotation.x = Math.sin(t / 1.5) * 0.06 + basicCapeRotationX; } } /** * Running animation (faster, more exaggerated swing). */ export class RunningAnimation extends PlayerAnimation { animate(player) { const t = this.progress * 15 + Math.PI * 0.5; player.skin.leftLeg.rotation.x = Math.cos(t + Math.PI) * 1.3; player.skin.rightLeg.rotation.x = Math.cos(t) * 1.3; player.skin.leftArm.rotation.x = Math.cos(t) * 1.5; player.skin.rightArm.rotation.x = Math.cos(t + Math.PI) * 1.5; const basicArmRotationZ = Math.PI * 0.1; player.skin.leftArm.rotation.z = Math.cos(t) * 0.1 + basicArmRotationZ; player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.1 - basicArmRotationZ; player.position.y = Math.cos(t * 2); player.position.x = Math.cos(t) * 0.15; player.rotation.z = Math.cos(t + Math.PI) * 0.01; const basicCapeRotationX = Math.PI * 0.3; player.cape.rotation.x = Math.sin(t * 2) * 0.1 + basicCapeRotationX; } } function clamp(num, min, max) { return num <= min ? min : num >= max ? max : num; } /** * Flying animation (body rotates, elytra wings expand). */ export class FlyingAnimation extends PlayerAnimation { animate(player) { const t = this.progress > 0 ? this.progress * 20 : 0; const startProgress = clamp((t * t) / 100, 0, 1); player.rotation.x = (startProgress * Math.PI) / 2; player.skin.head.rotation.x = startProgress > 0.5 ? Math.PI / 4 - player.rotation.x : 0; const basicArmRotationZ = Math.PI * 0.25 * startProgress; player.skin.leftArm.rotation.z = basicArmRotationZ; player.skin.rightArm.rotation.z = -basicArmRotationZ; const elytraRotationX = 0.34906584; const elytraRotationZ = Math.PI / 2; const interpolation = Math.pow(0.9, t); player.elytra.leftWing.rotation.x = elytraRotationX + interpolation * (0.2617994 - elytraRotationX); player.elytra.leftWing.rotation.z = elytraRotationZ + interpolation * (0.2617994 - elytraRotationZ); player.elytra.updateRightWing(); } } /** * Waving animation (one arm waves). */ export class WaveAnimation extends PlayerAnimation { constructor(whichArm = "left") { super(); Object.defineProperty(this, "whichArm", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.whichArm = whichArm; } animate(player) { const t = this.progress * Math.PI; const targetArm = this.whichArm === "left" ? player.skin.leftArm : player.skin.rightArm; targetArm.rotation.x = 180; targetArm.rotation.z = Math.sin(t) * 0.5; } } /** * Crouch animation (body and limbs move to crouch pose). */ export class CrouchAnimation extends PlayerAnimation { constructor() { super(...arguments); /** Show progress of animation. @defaultValue false */ Object.defineProperty(this, "showProgress", { enumerable: true, configurable: true, writable: true, value: false }); /** Run this animation once. @defaultValue false */ Object.defineProperty(this, "runOnce", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "isRunningHitAnimation", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "hitAnimationSpeed", { enumerable: true, configurable: true, writable: true, value: 1 }); Object.defineProperty(this, "erp", { enumerable: true, configurable: true, writable: true, value: 0 }); // Elytra rotate progress Object.defineProperty(this, "isCrouched", { enumerable: true, configurable: true, writable: true, value: void 0 }); } /** * Add the hit animation. * @param speed - Speed of hit animation (default: same as crouch speed). */ addHitAnimation(speed = this.speed) { this.isRunningHitAnimation = true; this.hitAnimationSpeed = speed; } animate(player) { let pr = this.progress * 8; if (pr === 0) this.isCrouched = undefined; if (this.runOnce) pr = clamp(pr, -1, 1); if (!this.showProgress) pr = Math.floor(pr); const sinVal = Math.abs(Math.sin((pr * Math.PI) / 2)); player.skin.body.rotation.x = 0.4537860552 * sinVal; player.skin.body.position.z = 1.3256181 * sinVal - 3.4500310377 * sinVal; player.skin.body.position.y = -6 - 2.103677462 * sinVal; player.cape.position.y = 8 - 1.851236166577372 * sinVal; player.cape.rotation.x = (10.8 * Math.PI) / 180 + 0.294220265771 * sinVal; player.cape.position.z = -2 + 3.786619432 * sinVal - 3.4500310377 * sinVal; player.elytra.position.x = player.cape.position.x; player.elytra.position.y = player.cape.position.y; player.elytra.position.z = player.cape.position.z; player.elytra.rotation.x = player.cape.rotation.x - (10.8 * Math.PI) / 180; const pr1 = this.progress / this.speed; if (sinVal === 1) { this.erp = !this.isCrouched ? pr1 : this.erp; this.isCrouched = true; player.elytra.leftWing.rotation.z = 0.26179944 + 0.4582006 * Math.abs(Math.sin((Math.min(pr1 - this.erp, 1) * Math.PI) / 2)); player.elytra.updateRightWing(); } else if (this.isCrouched !== undefined) { this.erp = this.isCrouched ? pr1 : this.erp; player.elytra.leftWing.rotation.z = 0.72 - 0.4582006 * Math.abs(Math.sin((Math.min(pr1 - this.erp, 1) * Math.PI) / 2)); player.elytra.updateRightWing(); this.isCrouched = false; } player.skin.head.position.y = -3.618325234674 * sinVal; player.skin.leftArm.position.z = 3.618325234674 * sinVal - 3.4500310377 * sinVal; player.skin.rightArm.position.z = player.skin.leftArm.position.z; player.skin.leftArm.rotation.x = 0.410367746202 * sinVal; player.skin.rightArm.rotation.x = player.skin.leftArm.rotation.x; player.skin.leftArm.rotation.z = 0.1; player.skin.rightArm.rotation.z = -player.skin.leftArm.rotation.z; player.skin.leftArm.position.y = -2 - 2.53943318 * sinVal; player.skin.rightArm.position.y = player.skin.leftArm.position.y; player.skin.rightLeg.position.z = -3.4500310377 * sinVal; player.skin.leftLeg.position.z = player.skin.rightLeg.position.z; if (this.isRunningHitAnimation) { const pr2 = this.progress; let t = (this.progress * 18 * this.hitAnimationSpeed) / this.speed; if (this.speed === 0) t = 0; const isCrouching = Math.abs(Math.sin((pr2 * Math.PI) / 2)) === 1; player.skin.rightArm.rotation.x = -0.4537860552 + 2 * Math.sin(t + Math.PI) * 0.3 - (isCrouching ? 0.4537860552 : 0); const basicArmRotationZ = 0.01 * Math.PI + 0.06; player.skin.rightArm.rotation.z = -Math.cos(t) * 0.403 + basicArmRotationZ; player.skin.body.rotation.y = -Math.cos(t) * 0.06; player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.077 + (isCrouching ? 0.47 : 0); player.skin.leftArm.rotation.z = -Math.cos(t) * 0.015 + 0.13 - (!isCrouching ? 0.05 : 0); if (!isCrouching) { player.skin.leftArm.position.z = Math.cos(t) * 0.3; player.skin.leftArm.position.x = 5 - Math.cos(t) * 0.05; } } } } /** * Hit animation (right arm swings). */ export class HitAnimation extends PlayerAnimation { animate(player) { const t = this.progress * 18; player.skin.rightArm.rotation.x = -0.4537860552 * 2 + 2 * Math.sin(t + Math.PI) * 0.3; const basicArmRotationZ = 0.01 * Math.PI + 0.06; player.skin.rightArm.rotation.z = -Math.cos(t) * 0.403 + basicArmRotationZ; player.skin.body.rotation.y = -Math.cos(t) * 0.06; player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.077; player.skin.leftArm.rotation.z = -Math.cos(t) * 0.015 + 0.13 - 0.05; player.skin.leftArm.position.z = Math.cos(t) * 0.3; player.skin.leftArm.position.x = 5 - Math.cos(t) * 0.05; } } //# sourceMappingURL=animation.js.map