2020-06-17 05:10:30 +02:00
|
|
|
import { applyMixins, CapeContainer, ModelType, SkinContainer, RemoteImage, TextureSource } from "skinview-utils";
|
2020-01-18 15:54:54 +01:00
|
|
|
import { NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
|
2020-01-24 12:34:50 +01:00
|
|
|
import { RootAnimation } from "./animation.js";
|
|
|
|
|
import { PlayerObject } from "./model.js";
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
export type LoadOptions = {
|
2020-05-29 10:34:58 +02:00
|
|
|
/**
|
|
|
|
|
* Whether to make the object visible after the texture is loaded. (default: true)
|
|
|
|
|
*/
|
2020-01-26 20:39:29 +01:00
|
|
|
makeVisible?: boolean;
|
2020-05-29 10:34:58 +02:00
|
|
|
}
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2020-06-14 09:45:36 +02:00
|
|
|
export type SkinViewerOptions = {
|
|
|
|
|
width?: number;
|
|
|
|
|
height?: number;
|
|
|
|
|
skin?: RemoteImage | TextureSource;
|
|
|
|
|
cape?: RemoteImage | TextureSource;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
function toMakeVisible(options?: LoadOptions): boolean {
|
|
|
|
|
if (options && options.makeVisible === false) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
class SkinViewer {
|
2020-06-14 09:45:36 +02:00
|
|
|
readonly domElement: Node;
|
2020-01-26 20:39:29 +01:00
|
|
|
readonly scene: Scene;
|
|
|
|
|
readonly camera: PerspectiveCamera;
|
|
|
|
|
readonly renderer: WebGLRenderer;
|
|
|
|
|
readonly playerObject: PlayerObject;
|
|
|
|
|
readonly animations: RootAnimation = new RootAnimation();
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
protected readonly skinCanvas: HTMLCanvasElement;
|
|
|
|
|
protected readonly capeCanvas: HTMLCanvasElement;
|
|
|
|
|
private readonly skinTexture: Texture;
|
|
|
|
|
private readonly capeTexture: Texture;
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2020-02-01 13:44:10 +01:00
|
|
|
private _disposed: boolean = false;
|
2019-12-27 12:04:32 +01:00
|
|
|
private _renderPaused: boolean = false;
|
|
|
|
|
|
2020-06-17 03:08:51 +02:00
|
|
|
constructor(domElement: Node, options: SkinViewerOptions = {}) {
|
|
|
|
|
this.domElement = domElement;
|
2020-06-14 09:45:36 +02:00
|
|
|
|
2017-10-01 14:00:45 +02:00
|
|
|
// texture
|
|
|
|
|
this.skinCanvas = document.createElement("canvas");
|
2020-01-01 10:18:06 +01:00
|
|
|
this.skinTexture = new Texture(this.skinCanvas);
|
|
|
|
|
this.skinTexture.magFilter = NearestFilter;
|
|
|
|
|
this.skinTexture.minFilter = NearestFilter;
|
2017-10-01 14:00:45 +02:00
|
|
|
|
|
|
|
|
this.capeCanvas = document.createElement("canvas");
|
2020-01-01 10:18:06 +01:00
|
|
|
this.capeTexture = new Texture(this.capeCanvas);
|
|
|
|
|
this.capeTexture.magFilter = NearestFilter;
|
|
|
|
|
this.capeTexture.minFilter = NearestFilter;
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-01-01 10:18:06 +01:00
|
|
|
this.scene = new Scene();
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-02-11 12:21:33 +01:00
|
|
|
// Use smaller fov to avoid distortion
|
2020-01-01 10:18:06 +01:00
|
|
|
this.camera = new PerspectiveCamera(40);
|
2017-10-01 14:00:45 +02:00
|
|
|
this.camera.position.y = -12;
|
2018-02-11 12:21:33 +01:00
|
|
|
this.camera.position.z = 60;
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-08-13 14:59:36 +02:00
|
|
|
this.renderer = new WebGLRenderer({ alpha: true, preserveDrawingBuffer: true });
|
2017-10-01 14:00:45 +02:00
|
|
|
this.domElement.appendChild(this.renderer.domElement);
|
|
|
|
|
|
2020-01-18 15:54:54 +01:00
|
|
|
this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture);
|
2019-04-20 16:08:49 +02:00
|
|
|
this.playerObject.name = "player";
|
2020-01-18 15:59:07 +01:00
|
|
|
this.playerObject.skin.visible = false;
|
|
|
|
|
this.playerObject.cape.visible = false;
|
2017-10-01 14:00:45 +02:00
|
|
|
this.scene.add(this.playerObject);
|
|
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
window.requestAnimationFrame(() => this.draw());
|
2020-06-14 09:45:36 +02:00
|
|
|
|
2020-06-17 03:08:51 +02:00
|
|
|
if (options.skin !== undefined) {
|
2020-06-17 05:10:30 +02:00
|
|
|
this.loadSkin(options.skin);
|
2020-06-17 03:08:51 +02:00
|
|
|
}
|
|
|
|
|
if (options.cape !== undefined) {
|
2020-06-17 05:10:30 +02:00
|
|
|
this.loadCape(options.cape);
|
2020-06-14 09:45:36 +02:00
|
|
|
}
|
2020-06-17 03:08:51 +02:00
|
|
|
if (options.width !== undefined) {
|
|
|
|
|
this.width = options.width;
|
|
|
|
|
}
|
|
|
|
|
if (options.height !== undefined) {
|
|
|
|
|
this.height = options.height;
|
|
|
|
|
}
|
2020-01-26 20:39:29 +01:00
|
|
|
}
|
2018-07-02 13:52:36 +02:00
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
protected skinLoaded(model: ModelType, options?: LoadOptions): void {
|
|
|
|
|
this.skinTexture.needsUpdate = true;
|
|
|
|
|
this.playerObject.skin.modelType = model;
|
|
|
|
|
if (toMakeVisible(options)) {
|
2017-10-01 14:00:45 +02:00
|
|
|
this.playerObject.skin.visible = true;
|
2020-01-26 20:39:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-01-26 20:39:29 +01:00
|
|
|
protected capeLoaded(options?: LoadOptions): void {
|
|
|
|
|
this.capeTexture.needsUpdate = true;
|
|
|
|
|
if (toMakeVisible(options)) {
|
2017-10-01 14:00:45 +02:00
|
|
|
this.playerObject.cape.visible = true;
|
2020-01-30 18:35:02 +01:00
|
|
|
}
|
2019-12-27 12:04:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-06-14 09:45:36 +02:00
|
|
|
protected resetSkin(): void {
|
|
|
|
|
this.playerObject.skin.visible = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected resetCape(): void {
|
|
|
|
|
this.playerObject.cape.visible = false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
private draw(): void {
|
2019-12-27 12:04:32 +01:00
|
|
|
if (this.disposed || this._renderPaused) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-31 17:34:00 +01:00
|
|
|
this.animations.runAnimationLoop(this.playerObject);
|
2020-01-30 18:36:49 +01:00
|
|
|
this.doRender();
|
2019-12-27 12:04:32 +01:00
|
|
|
window.requestAnimationFrame(() => this.draw());
|
2017-10-01 14:00:45 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
protected doRender(): void {
|
2020-01-30 18:36:49 +01:00
|
|
|
this.renderer.render(this.scene, this.camera);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
setSize(width: number, height: number): void {
|
2017-10-01 14:00:45 +02:00
|
|
|
this.camera.aspect = width / height;
|
|
|
|
|
this.camera.updateProjectionMatrix();
|
|
|
|
|
this.renderer.setSize(width, height);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
dispose(): void {
|
2020-02-01 13:44:10 +01:00
|
|
|
this._disposed = true;
|
2017-10-01 14:00:45 +02:00
|
|
|
this.domElement.removeChild(this.renderer.domElement);
|
|
|
|
|
this.renderer.dispose();
|
|
|
|
|
this.skinTexture.dispose();
|
|
|
|
|
this.capeTexture.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 13:44:10 +01:00
|
|
|
get disposed(): boolean {
|
|
|
|
|
return this._disposed;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
get renderPaused(): boolean {
|
2019-12-27 12:04:32 +01:00
|
|
|
return this._renderPaused;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set renderPaused(value: boolean) {
|
|
|
|
|
const toResume = !this.disposed && !value && this._renderPaused;
|
|
|
|
|
this._renderPaused = value;
|
|
|
|
|
if (toResume) {
|
|
|
|
|
window.requestAnimationFrame(() => this.draw());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
get width(): number {
|
2020-02-01 13:43:02 +01:00
|
|
|
return this.renderer.getSize(new Vector2()).width;
|
2018-07-21 05:07:52 +02:00
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
set width(newWidth: number) {
|
2018-07-21 05:07:52 +02:00
|
|
|
this.setSize(newWidth, this.height);
|
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
get height(): number {
|
2020-02-01 13:43:02 +01:00
|
|
|
return this.renderer.getSize(new Vector2()).height;
|
2018-07-21 05:07:52 +02:00
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2020-02-01 12:13:46 +01:00
|
|
|
set height(newHeight: number) {
|
2018-07-21 05:07:52 +02:00
|
|
|
this.setSize(this.width, newHeight);
|
|
|
|
|
}
|
2017-10-02 15:29:41 +02:00
|
|
|
}
|
2020-01-26 20:39:29 +01:00
|
|
|
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<LoadOptions> { }
|
|
|
|
|
applyMixins(SkinViewer, [SkinContainer, CapeContainer]);
|
|
|
|
|
export { SkinViewer };
|