170 lines
4.8 KiB
TypeScript
170 lines
4.8 KiB
TypeScript
import * as THREE from "three";
|
|
import { PlayerObject } from "./model";
|
|
import { invokeAnimation } from "./animation";
|
|
import { loadSkinToCanvas, loadCapeToCanvas, isSlimSkin } from "./utils";
|
|
|
|
class SkinViewer {
|
|
|
|
public domElement: HTMLElement;
|
|
public animation: Animation;
|
|
public detectModel: boolean = false;
|
|
public animationPaused: boolean = false;
|
|
public animationTime: number = 0;
|
|
public disposed: boolean = false;
|
|
|
|
public skinImg: HTMLImageElement;
|
|
public skinCanvas: HTMLCanvasElement;
|
|
public skinTexture: THREE.Texture;
|
|
|
|
public capeImg: HTMLImageElement;
|
|
public capeCanvas: HTMLCanvasElement;
|
|
public capeTexture: THREE.Texture;
|
|
|
|
public layer1Material: THREE.MeshBasicMaterial;
|
|
public layer2Material: THREE.MeshBasicMaterial;
|
|
|
|
public scene: THREE.Scene;
|
|
public camera: THREE.PerspectiveCamera;
|
|
|
|
public capeMaterial: THREE.MeshBasicMaterial;
|
|
public renderer: THREE.WebGLRenderer;
|
|
|
|
public playerObject: PlayerObject;
|
|
|
|
constructor(options) {
|
|
this.domElement = options.domElement;
|
|
this.animation = options.animation || null;
|
|
this.detectModel = options.animation !== false; // true by default
|
|
this.animationPaused = false;
|
|
this.animationTime = 0;
|
|
this.disposed = false;
|
|
|
|
// texture
|
|
this.skinImg = new Image();
|
|
this.skinCanvas = document.createElement("canvas");
|
|
this.skinTexture = new THREE.Texture(this.skinCanvas);
|
|
this.skinTexture.magFilter = THREE.NearestFilter;
|
|
this.skinTexture.minFilter = THREE.NearestFilter;
|
|
|
|
this.capeImg = new Image();
|
|
this.capeCanvas = document.createElement("canvas");
|
|
this.capeTexture = new THREE.Texture(this.capeCanvas);
|
|
this.capeTexture.magFilter = THREE.NearestFilter;
|
|
this.capeTexture.minFilter = THREE.NearestFilter;
|
|
|
|
this.layer1Material = new THREE.MeshBasicMaterial({ map: this.skinTexture, side: THREE.FrontSide });
|
|
this.layer2Material = new THREE.MeshBasicMaterial({ map: this.skinTexture, transparent: true, opacity: 1, side: THREE.DoubleSide, alphaTest: 0.5 });
|
|
this.capeMaterial = new THREE.MeshBasicMaterial({ map: this.capeTexture, transparent: true, opacity: 1, side: THREE.DoubleSide, alphaTest: 0.5 });
|
|
|
|
// scene
|
|
this.scene = new THREE.Scene();
|
|
|
|
// Use smaller fov to avoid distortion
|
|
this.camera = new THREE.PerspectiveCamera(40);
|
|
this.camera.position.y = -12;
|
|
this.camera.position.z = 60;
|
|
|
|
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: false });
|
|
this.renderer.setSize(300, 300); // default size
|
|
this.renderer.context.getShaderInfoLog = () => ""; // shut firefox up
|
|
this.domElement.appendChild(this.renderer.domElement);
|
|
|
|
this.playerObject = new PlayerObject(this.layer1Material, this.layer2Material, this.capeMaterial);
|
|
this.scene.add(this.playerObject);
|
|
|
|
// texture loading
|
|
this.skinImg.crossOrigin = "anonymous";
|
|
this.skinImg.onerror = () => console.error("Failed loading " + this.skinImg.src);
|
|
this.skinImg.onload = () => {
|
|
loadSkinToCanvas(this.skinCanvas, this.skinImg);
|
|
|
|
if (this.detectModel) {
|
|
this.playerObject.skin.slim = isSlimSkin(this.skinCanvas);
|
|
}
|
|
|
|
this.skinTexture.needsUpdate = true;
|
|
this.layer1Material.needsUpdate = true;
|
|
this.layer2Material.needsUpdate = true;
|
|
|
|
this.playerObject.skin.visible = true;
|
|
};
|
|
|
|
this.capeImg.crossOrigin = "anonymous";
|
|
this.capeImg.onerror = () => console.error("Failed loading " + this.capeImg.src);
|
|
this.capeImg.onload = () => {
|
|
loadCapeToCanvas(this.capeCanvas, this.capeImg);
|
|
|
|
this.capeTexture.needsUpdate = true;
|
|
this.capeMaterial.needsUpdate = true;
|
|
|
|
this.playerObject.cape.visible = true;
|
|
};
|
|
|
|
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;
|
|
|
|
const draw = () => {
|
|
if (this.disposed) return;
|
|
window.requestAnimationFrame(draw);
|
|
if (!this.animationPaused) {
|
|
this.animationTime++;
|
|
if (this.animation) {
|
|
invokeAnimation(this.animation, this.playerObject, this.animationTime / 100.0);
|
|
}
|
|
}
|
|
this.renderer.render(this.scene, this.camera);
|
|
};
|
|
draw();
|
|
}
|
|
|
|
private setSize(width, height) {
|
|
this.camera.aspect = width / height;
|
|
this.camera.updateProjectionMatrix();
|
|
this.renderer.setSize(width, height);
|
|
}
|
|
|
|
private dispose() {
|
|
this.disposed = true;
|
|
this.domElement.removeChild(this.renderer.domElement);
|
|
this.renderer.dispose();
|
|
this.skinTexture.dispose();
|
|
this.capeTexture.dispose();
|
|
}
|
|
|
|
get skinUrl() {
|
|
return this.skinImg.src;
|
|
}
|
|
|
|
set skinUrl(url) {
|
|
this.skinImg.src = url;
|
|
}
|
|
|
|
get capeUrl() {
|
|
return this.capeImg.src;
|
|
}
|
|
|
|
set capeUrl(url) {
|
|
this.capeImg.src = url;
|
|
}
|
|
|
|
get width() {
|
|
return this.renderer.getSize().width;
|
|
}
|
|
|
|
set width(newWidth) {
|
|
this.setSize(newWidth, this.height);
|
|
}
|
|
|
|
get height() {
|
|
return this.renderer.getSize().height;
|
|
}
|
|
|
|
set height(newHeight) {
|
|
this.setSize(this.width, newHeight);
|
|
}
|
|
}
|
|
|
|
export { SkinViewer };
|