2019-12-31 17:47:04 +01:00
|
|
|
import { PerspectiveCamera } from "three/src/cameras/PerspectiveCamera";
|
|
|
|
import { DoubleSide, FrontSide, NearestFilter } from "three/src/constants";
|
|
|
|
import { MeshBasicMaterial } from "three/src/materials/MeshBasicMaterial";
|
|
|
|
import { Vector2 } from "three/src/math/Vector2";
|
|
|
|
import { WebGLRenderer } from "three/src/renderers/WebGLRenderer";
|
|
|
|
import { Scene } from "three/src/scenes/Scene";
|
|
|
|
import { Texture } from "three/src/textures/Texture";
|
2019-12-22 13:19:33 +01:00
|
|
|
import { RootAnimation } from "./animation";
|
2019-12-31 17:47:04 +01:00
|
|
|
import { PlayerObject } from "./model";
|
|
|
|
import { isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "./utils";
|
2018-07-02 13:52:36 +02:00
|
|
|
|
2018-08-17 06:05:17 +02:00
|
|
|
export interface SkinViewerOptions {
|
|
|
|
domElement: Node;
|
|
|
|
animation?: Animation;
|
|
|
|
skinUrl?: string;
|
|
|
|
capeUrl?: string;
|
|
|
|
width?: number;
|
|
|
|
height?: number;
|
|
|
|
detectModel?: boolean;
|
|
|
|
}
|
|
|
|
|
2018-08-16 13:07:46 +02:00
|
|
|
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-21 14:52:02 +02:00
|
|
|
public disposed: boolean = false;
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2018-08-17 06:05:17 +02:00
|
|
|
public readonly skinImg: HTMLImageElement;
|
|
|
|
public readonly skinCanvas: HTMLCanvasElement;
|
2019-12-31 17:47:04 +01:00
|
|
|
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;
|
2019-12-31 17:47:04 +01:00
|
|
|
public readonly capeTexture: Texture;
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2019-12-31 17:47:04 +01:00
|
|
|
public readonly layer1Material: MeshBasicMaterial;
|
|
|
|
public readonly layer2Material: MeshBasicMaterial;
|
|
|
|
public readonly capeMaterial: MeshBasicMaterial;
|
2018-07-17 20:49:00 +02:00
|
|
|
|
2019-12-31 17:47:04 +01: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
|
|
|
|
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");
|
2019-12-31 17:47:04 +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.capeImg = new Image();
|
|
|
|
this.capeCanvas = document.createElement("canvas");
|
2019-12-31 17:47:04 +01:00
|
|
|
this.capeTexture = new Texture(this.capeCanvas);
|
|
|
|
this.capeTexture.magFilter = NearestFilter;
|
|
|
|
this.capeTexture.minFilter = NearestFilter;
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2019-12-31 17:47:04 +01:00
|
|
|
this.layer1Material = new MeshBasicMaterial({ map: this.skinTexture, side: FrontSide });
|
|
|
|
this.layer2Material = new MeshBasicMaterial({ map: this.skinTexture, transparent: true, opacity: 1, side: DoubleSide, alphaTest: 0.5 });
|
|
|
|
this.capeMaterial = new MeshBasicMaterial({ map: this.capeTexture, transparent: true, opacity: 1, side: DoubleSide, alphaTest: 0.5 });
|
2017-10-01 14:00:45 +02:00
|
|
|
|
|
|
|
// scene
|
2019-12-31 17:47:04 +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
|
2019-12-31 17:47:04 +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
|
|
|
|
2019-12-31 17:47:04 +01:00
|
|
|
this.renderer = new WebGLRenderer({ alpha: true, antialias: false });
|
2017-10-01 14:00:45 +02:00
|
|
|
this.renderer.setSize(300, 300); // default size
|
|
|
|
this.domElement.appendChild(this.renderer.domElement);
|
|
|
|
|
2018-07-02 09:46:48 +02:00
|
|
|
this.playerObject = new PlayerObject(this.layer1Material, this.layer2Material, this.capeMaterial);
|
2019-04-20 16:08:49 +02:00
|
|
|
this.playerObject.name = "player";
|
2017-10-01 14:00:45 +02:00
|
|
|
this.scene.add(this.playerObject);
|
|
|
|
|
|
|
|
// texture loading
|
2018-03-03 15:04:10 +01:00
|
|
|
this.skinImg.crossOrigin = "anonymous";
|
2018-01-06 11:47:07 +01:00
|
|
|
this.skinImg.onerror = () => console.error("Failed loading " + this.skinImg.src);
|
2017-10-01 14:00:45 +02:00
|
|
|
this.skinImg.onload = () => {
|
2018-07-05 04:56:21 +02:00
|
|
|
loadSkinToCanvas(this.skinCanvas, this.skinImg);
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-02 14:13:01 +02:00
|
|
|
if (this.detectModel) {
|
2018-07-05 04:56:21 +02:00
|
|
|
this.playerObject.skin.slim = isSlimSkin(this.skinCanvas);
|
2018-07-02 14:13:01 +02:00
|
|
|
}
|
2018-07-02 13:52:36 +02:00
|
|
|
|
2017-10-01 14:00:45 +02:00
|
|
|
this.skinTexture.needsUpdate = true;
|
|
|
|
this.layer1Material.needsUpdate = true;
|
|
|
|
this.layer2Material.needsUpdate = true;
|
|
|
|
|
|
|
|
this.playerObject.skin.visible = true;
|
|
|
|
};
|
|
|
|
|
2018-03-03 15:04:10 +01:00
|
|
|
this.capeImg.crossOrigin = "anonymous";
|
2018-01-06 11:47:07 +01:00
|
|
|
this.capeImg.onerror = () => console.error("Failed loading " + this.capeImg.src);
|
2017-10-01 14:00:45 +02:00
|
|
|
this.capeImg.onload = () => {
|
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.capeMaterial.needsUpdate = true;
|
|
|
|
|
|
|
|
this.playerObject.cape.visible = true;
|
|
|
|
};
|
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
if (options.skinUrl) this.skinUrl = options.skinUrl;
|
|
|
|
if (options.capeUrl) this.capeUrl = options.capeUrl;
|
|
|
|
if (options.width) this.width = options.width;
|
|
|
|
if (options.height) this.height = options.height;
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2019-12-27 12:04:32 +01:00
|
|
|
window.requestAnimationFrame(() => this.draw());
|
|
|
|
}
|
|
|
|
|
|
|
|
private draw() {
|
|
|
|
if (this.disposed || this._renderPaused) {
|
|
|
|
return;
|
|
|
|
}
|
2019-12-31 17:34:00 +01:00
|
|
|
this.animations.runAnimationLoop(this.playerObject);
|
2019-12-27 12:04:32 +01:00
|
|
|
this.renderer.render(this.scene, this.camera);
|
|
|
|
window.requestAnimationFrame(() => this.draw());
|
2017-10-01 14:00:45 +02:00
|
|
|
}
|
|
|
|
|
2019-12-28 14:14:45 +01:00
|
|
|
setSize(width: number, height: number) {
|
2017-10-01 14:00:45 +02:00
|
|
|
this.camera.aspect = width / height;
|
|
|
|
this.camera.updateProjectionMatrix();
|
|
|
|
this.renderer.setSize(width, height);
|
|
|
|
}
|
|
|
|
|
2018-08-17 06:05:17 +02:00
|
|
|
dispose() {
|
2017-10-01 14:00:45 +02:00
|
|
|
this.disposed = true;
|
|
|
|
this.domElement.removeChild(this.renderer.domElement);
|
|
|
|
this.renderer.dispose();
|
|
|
|
this.skinTexture.dispose();
|
|
|
|
this.capeTexture.dispose();
|
|
|
|
}
|
|
|
|
|
2019-12-27 12:04:32 +01:00
|
|
|
get renderPaused() {
|
|
|
|
return this._renderPaused;
|
|
|
|
}
|
|
|
|
|
|
|
|
set renderPaused(value: boolean) {
|
|
|
|
const toResume = !this.disposed && !value && this._renderPaused;
|
|
|
|
this._renderPaused = value;
|
|
|
|
if (toResume) {
|
|
|
|
window.requestAnimationFrame(() => this.draw());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
get skinUrl() {
|
|
|
|
return this.skinImg.src;
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
set skinUrl(url) {
|
|
|
|
this.skinImg.src = url;
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
get capeUrl() {
|
|
|
|
return this.capeImg.src;
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
set capeUrl(url) {
|
|
|
|
this.capeImg.src = url;
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
get width() {
|
2019-12-31 17:47:04 +01:00
|
|
|
const target = new Vector2();
|
2019-12-26 03:57:27 +01:00
|
|
|
return this.renderer.getSize(target).width;
|
2018-07-21 05:07:52 +02:00
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
set width(newWidth) {
|
|
|
|
this.setSize(newWidth, this.height);
|
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
get height() {
|
2019-12-31 17:47:04 +01:00
|
|
|
const target = new Vector2();
|
2019-12-26 03:57:27 +01:00
|
|
|
return this.renderer.getSize(target).height;
|
2018-07-21 05:07:52 +02:00
|
|
|
}
|
2017-10-01 14:00:45 +02:00
|
|
|
|
2018-07-21 05:07:52 +02:00
|
|
|
set height(newHeight) {
|
|
|
|
this.setSize(this.width, newHeight);
|
|
|
|
}
|
2017-10-02 15:29:41 +02:00
|
|
|
}
|