skinview3d/src/viewer.ts

184 lines
4.8 KiB
TypeScript
Raw Normal View History

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";
2020-01-17 12:04:59 +01:00
import { isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "skinview-utils";
2018-07-02 13:52:36 +02:00
2018-08-17 06:05:17 +02:00
export interface SkinViewerOptions {
domElement: Node;
skinUrl?: string;
capeUrl?: string;
width?: number;
height?: number;
detectModel?: boolean;
}
export class SkinViewer {
2018-07-17 20:49:00 +02:00
2018-08-17 06:05:17 +02:00
public readonly domElement: Node;
2019-12-22 13:42:04 +01:00
public readonly animations: RootAnimation = new RootAnimation();
2018-08-16 13:35:16 +02:00
public detectModel: boolean = true;
2018-07-17 20:49:00 +02:00
2018-08-17 06:05:17 +02:00
public readonly skinImg: HTMLImageElement;
public readonly skinCanvas: HTMLCanvasElement;
public readonly skinTexture: Texture;
2018-07-17 20:49:00 +02:00
2018-08-17 06:05:17 +02:00
public readonly capeImg: HTMLImageElement;
public readonly capeCanvas: HTMLCanvasElement;
public readonly capeTexture: Texture;
2018-07-17 20:49:00 +02:00
public readonly scene: Scene;
public readonly camera: PerspectiveCamera;
public readonly renderer: WebGLRenderer;
2018-07-17 20:49:00 +02:00
2018-08-17 06:05:17 +02:00
public readonly playerObject: PlayerObject;
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;
2018-08-17 06:05:17 +02:00
constructor(options: SkinViewerOptions) {
2017-10-01 14:00:45 +02:00
this.domElement = options.domElement;
2018-08-16 13:35:16 +02:00
if (options.detectModel === false) {
this.detectModel = false;
}
2017-10-01 14:00:45 +02:00
// texture
this.skinImg = new Image();
this.skinCanvas = document.createElement("canvas");
this.skinTexture = new Texture(this.skinCanvas);
this.skinTexture.magFilter = NearestFilter;
this.skinTexture.minFilter = NearestFilter;
2017-10-01 14:00:45 +02:00
this.capeImg = new Image();
this.capeCanvas = document.createElement("canvas");
this.capeTexture = new Texture(this.capeCanvas);
this.capeTexture.magFilter = NearestFilter;
this.capeTexture.minFilter = NearestFilter;
2017-10-01 14:00:45 +02:00
// scene
this.scene = new Scene();
2017-10-01 14:00:45 +02:00
// Use smaller fov to avoid distortion
this.camera = new PerspectiveCamera(40);
2017-10-01 14:00:45 +02:00
this.camera.position.y = -12;
this.camera.position.z = 60;
2017-10-01 14:00:45 +02:00
this.renderer = new WebGLRenderer({ alpha: true });
2017-10-01 14:00:45 +02:00
this.domElement.appendChild(this.renderer.domElement);
this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture);
2019-04-20 16:08:49 +02:00
this.playerObject.name = "player";
this.playerObject.skin.visible = false;
this.playerObject.cape.visible = false;
2017-10-01 14:00:45 +02:00
this.scene.add(this.playerObject);
// texture loading
this.skinImg.crossOrigin = "anonymous";
2020-02-01 12:13:46 +01:00
this.skinImg.onerror = (): void => console.error("Failed loading " + this.skinImg.src);
this.skinImg.onload = (): void => {
2018-07-05 04:56:21 +02:00
loadSkinToCanvas(this.skinCanvas, this.skinImg);
2017-10-01 14:00:45 +02:00
if (this.detectModel) {
2018-07-05 04:56:21 +02:00
this.playerObject.skin.slim = isSlimSkin(this.skinCanvas);
}
2018-07-02 13:52:36 +02:00
2017-10-01 14:00:45 +02:00
this.skinTexture.needsUpdate = true;
this.playerObject.skin.visible = true;
};
this.capeImg.crossOrigin = "anonymous";
2020-02-01 12:13:46 +01:00
this.capeImg.onerror = (): void => console.error("Failed loading " + this.capeImg.src);
this.capeImg.onload = (): void => {
2018-07-05 04:56:21 +02:00
loadCapeToCanvas(this.capeCanvas, this.capeImg);
2017-10-01 14:00:45 +02:00
this.capeTexture.needsUpdate = true;
this.playerObject.cape.visible = true;
};
2020-01-30 18:35:02 +01:00
if (options.skinUrl !== undefined) {
this.skinUrl = options.skinUrl;
}
if (options.capeUrl !== undefined) {
this.capeUrl = options.capeUrl;
}
this.width = options.width === undefined ? 300 : options.width;
this.height = options.height === undefined ? 300 : options.height;
2017-10-01 14:00:45 +02:00
2019-12-27 12:04:32 +01:00
window.requestAnimationFrame(() => this.draw());
}
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);
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 {
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 skinUrl(): string {
return this.skinImg.src;
}
2017-10-01 14:00:45 +02:00
2020-02-01 12:13:46 +01:00
set skinUrl(url: string) {
this.skinImg.src = url;
}
2017-10-01 14:00:45 +02:00
2020-02-01 12:13:46 +01:00
get capeUrl(): string {
return this.capeImg.src;
}
2017-10-01 14:00:45 +02:00
2020-02-01 12:13:46 +01:00
set capeUrl(url: string) {
this.capeImg.src = url;
}
2017-10-01 14:00:45 +02:00
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;
}
2017-10-01 14:00:45 +02:00
2020-02-01 12:13:46 +01:00
set width(newWidth: number) {
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;
}
2017-10-01 14:00:45 +02:00
2020-02-01 12:13:46 +01:00
set height(newHeight: number) {
this.setSize(this.width, newHeight);
}
2017-10-02 15:29:41 +02:00
}