Merge pull request #56 from bs-community/mixin

Refactor SkinViewer
This commit is contained in:
Haowei Wen 2020-06-17 13:19:44 +08:00 committed by GitHub
commit f4d4aba706
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 105 additions and 131 deletions

View File

@ -9,3 +9,4 @@ extends:
rules:
'@typescript-eslint/no-inferrable-types': off
'@typescript-eslint/interface-name-prefix': off
'@typescript-eslint/no-empty-interface': off

View File

@ -20,21 +20,24 @@ Three.js powered Minecraft skin viewer.
```html
<div id="skin_container"></div>
<script>
let skinViewer = new skinview3d.SkinViewer({
domElement: document.getElementById("skin_container"),
width: 600,
height: 600,
skinUrl: "img/skin.png",
capeUrl: "img/cape.png"
let skinViewer = new skinview3d.SkinViewer(document.getElementById("skin_container"), {
width: 300,
height: 400,
skin: "img/skin.png"
});
// Change the textures
skinViewer.skinUrl = "img/skin2.png";
skinViewer.capeUrl = "img/cape2.png";
// Change viewer size
skinViewer.width = 600;
skinViewer.height = 800;
// Resize the skin viewer
skinViewer.width = 300;
skinViewer.height = 400;
// Load another skin
skinViewer.loadSkin("img/skin2.png");
// Load a cape
skinViewer.loadCape("img/cape.png");
// Unload(hide) the cape
skinViewer.loadCape(null);
// Control objects with your mouse!
let control = skinview3d.createOrbitControls(skinViewer);

View File

@ -22,17 +22,12 @@
<script type="text/javascript" src="../bundles/skinview3d.bundle.js"></script>
<script>
let skinViewer = new skinview3d.SkinViewer({
domElement: document.getElementById("skin_container"),
width: 900,
height: 900,
skinUrl: "./1_8_texturemap_redux.png"
let skinViewer = new skinview3d.SkinViewer(document.getElementById("skin_container"), {
width: 400,
height: 300,
skin: "./1_8_texturemap_redux.png"
});
// By default, the skin model is automatically detected. You can turn it off in this way:
// skinViewer.detectModel = false;
// skinViewer.playerObject.skin.slim = true;
let control = new skinview3d.createOrbitControls(skinViewer);
skinViewer.animations.add(skinview3d.WalkingAnimation);
skinViewer.animations.speed = 1.5;

6
package-lock.json generated
View File

@ -2637,9 +2637,9 @@
"dev": true
},
"skinview-utils": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.2.0.tgz",
"integrity": "sha512-MRcZf/u9Ksn1VbbBzjG50XAeKLMCPpQ2Onm+31nUcOw97XtS2sVBc4nNPwNGvUL5I7QTqfyr885gMxMkfp1klQ=="
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.5.3.tgz",
"integrity": "sha512-abvsUEiHjeUWGPmrTpVv4a2J6EhxTpdZAWg7kGHncKJyH91JKPqxnt8JvuehfO7d5J92pBLMiDV6F2IZaHZOdg=="
},
"slice-ansi": {
"version": "2.1.0",

View File

@ -11,7 +11,7 @@
"build": "npm run build:modules && npm run build:bundles",
"test:lint": "eslint --ext .ts src",
"test": "npm run test:lint",
"dev:watch:modules": "tsc -w -p .",
"dev:watch:modules": "tsc -w --declaration --sourceMap --outDir libs -p .",
"dev:watch:bundles": "rollup -w -c",
"dev:serve": "ws",
"dev": "npm-run-all --parallel dev:watch:modules dev:watch:bundles dev:serve",
@ -38,7 +38,7 @@
"bundles"
],
"dependencies": {
"skinview-utils": "^0.2.0",
"skinview-utils": "^0.5.3",
"three": "^0.117.1"
},
"devDependencies": {

View File

@ -102,7 +102,7 @@ export class CompositeAnimation implements IAnimation {
export class RootAnimation extends CompositeAnimation implements AnimationHandle {
speed: number = 1.0;
progress: number = 0.0;
readonly clock: Clock = new Clock(true);
private readonly clock: Clock = new Clock(true);
get animation(): RootAnimation {
return this;

View File

@ -1,3 +1,4 @@
import { ModelType } from "skinview-utils";
import { BoxGeometry, DoubleSide, FrontSide, Group, Mesh, MeshBasicMaterial, Object3D, Texture, Vector2 } from "three";
function toFaceVertices(x1: number, y1: number, x2: number, y2: number, w: number, h: number): Array<Vector2> {
@ -18,7 +19,6 @@ function toCapeVertices(x1: number, y1: number, x2: number, y2: number): Array<V
}
function setVertices(box: BoxGeometry, top: Array<Vector2>, bottom: Array<Vector2>, left: Array<Vector2>, front: Array<Vector2>, right: Array<Vector2>, back: Array<Vector2>): void {
box.faceVertexUvs[0] = [];
box.faceVertexUvs[0][0] = [right[3], right[0], right[2]];
box.faceVertexUvs[0][1] = [right[0], right[1], right[2]];
@ -59,7 +59,7 @@ export class SkinObject extends Group {
readonly leftLeg: BodyPart;
private modelListeners: Array<() => void> = []; // called when model(slim property) is changed
private _slim = false;
private slim = false;
constructor(texture: Texture) {
super();
@ -364,15 +364,15 @@ export class SkinObject extends Group {
this.leftLeg.position.x = 2;
this.add(this.leftLeg);
this.slim = false;
this.modelType = "default";
}
get slim(): boolean {
return this._slim;
get modelType(): ModelType {
return this.slim ? "slim" : "default";
}
set slim(value) {
this._slim = value;
set modelType(value: ModelType) {
this.slim = value === "slim";
this.modelListeners.forEach(listener => listener());
}

View File

@ -1,62 +1,59 @@
import { applyMixins, CapeContainer, ModelType, SkinContainer, RemoteImage, TextureSource } from "skinview-utils";
import { NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
import { RootAnimation } from "./animation.js";
import { PlayerObject } from "./model.js";
import { isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "skinview-utils";
export interface SkinViewerOptions {
domElement: Node;
skinUrl?: string;
capeUrl?: string;
width?: number;
height?: number;
detectModel?: boolean;
export type LoadOptions = {
/**
* Whether to make the object visible after the texture is loaded. (default: true)
*/
makeVisible?: boolean;
}
export class SkinViewer {
export type SkinViewerOptions = {
width?: number;
height?: number;
skin?: RemoteImage | TextureSource;
cape?: RemoteImage | TextureSource;
}
public readonly domElement: Node;
public readonly animations: RootAnimation = new RootAnimation();
public detectModel: boolean = true;
function toMakeVisible(options?: LoadOptions): boolean {
if (options && options.makeVisible === false) {
return false;
}
return true;
}
public readonly skinImg: HTMLImageElement;
public readonly skinCanvas: HTMLCanvasElement;
public readonly skinTexture: Texture;
class SkinViewer {
readonly domElement: Node;
readonly scene: Scene;
readonly camera: PerspectiveCamera;
readonly renderer: WebGLRenderer;
readonly playerObject: PlayerObject;
readonly animations: RootAnimation = new RootAnimation();
public readonly capeImg: HTMLImageElement;
public readonly capeCanvas: HTMLCanvasElement;
public readonly capeTexture: Texture;
public readonly scene: Scene;
public readonly camera: PerspectiveCamera;
public readonly renderer: WebGLRenderer;
public readonly playerObject: PlayerObject;
protected readonly skinCanvas: HTMLCanvasElement;
protected readonly capeCanvas: HTMLCanvasElement;
private readonly skinTexture: Texture;
private readonly capeTexture: Texture;
private _disposed: boolean = false;
private _renderPaused: boolean = false;
private _skinSet: boolean = false;
private _capeSet: boolean = false;
constructor(options: SkinViewerOptions) {
this.domElement = options.domElement;
if (options.detectModel === false) {
this.detectModel = false;
}
constructor(domElement: Node, options: SkinViewerOptions = {}) {
this.domElement = domElement;
// texture
this.skinImg = new Image();
this.skinCanvas = document.createElement("canvas");
this.skinTexture = new Texture(this.skinCanvas);
this.skinTexture.magFilter = NearestFilter;
this.skinTexture.minFilter = NearestFilter;
this.capeImg = new Image();
this.capeCanvas = document.createElement("canvas");
this.capeTexture = new Texture(this.capeCanvas);
this.capeTexture.magFilter = NearestFilter;
this.capeTexture.minFilter = NearestFilter;
// scene
this.scene = new Scene();
// Use smaller fov to avoid distortion
@ -73,39 +70,43 @@ export class SkinViewer {
this.playerObject.cape.visible = false;
this.scene.add(this.playerObject);
// texture loading
this.skinImg.crossOrigin = "anonymous";
this.skinImg.onerror = (): void => console.error("Failed loading " + this.skinImg.src);
this.skinImg.onload = (): void => {
loadSkinToCanvas(this.skinCanvas, this.skinImg);
if (this.detectModel) {
this.playerObject.skin.slim = isSlimSkin(this.skinCanvas);
}
this.skinTexture.needsUpdate = true;
this.playerObject.skin.visible = true;
};
this.capeImg.crossOrigin = "anonymous";
this.capeImg.onerror = (): void => console.error("Failed loading " + this.capeImg.src);
this.capeImg.onload = (): void => {
loadCapeToCanvas(this.capeCanvas, this.capeImg);
this.capeTexture.needsUpdate = true;
this.playerObject.cape.visible = true;
};
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;
window.requestAnimationFrame(() => this.draw());
if (options.skin !== undefined) {
this.loadSkin(options.skin);
}
if (options.cape !== undefined) {
this.loadCape(options.cape);
}
if (options.width !== undefined) {
this.width = options.width;
}
if (options.height !== undefined) {
this.height = options.height;
}
}
protected skinLoaded(model: ModelType, options?: LoadOptions): void {
this.skinTexture.needsUpdate = true;
this.playerObject.skin.modelType = model;
if (toMakeVisible(options)) {
this.playerObject.skin.visible = true;
}
}
protected capeLoaded(options?: LoadOptions): void {
this.capeTexture.needsUpdate = true;
if (toMakeVisible(options)) {
this.playerObject.cape.visible = true;
}
}
protected resetSkin(): void {
this.playerObject.skin.visible = false;
}
protected resetCape(): void {
this.playerObject.cape.visible = false;
}
private draw(): void {
@ -151,34 +152,6 @@ export class SkinViewer {
}
}
get skinUrl(): string | null {
return this._skinSet ? this.skinImg.src : null;
}
set skinUrl(url: string | null) {
if (url === null) {
this._skinSet = false;
this.playerObject.skin.visible = false;
} else {
this._skinSet = true;
this.skinImg.src = url;
}
}
get capeUrl(): string | null {
return this._capeSet ? this.capeImg.src : null;
}
set capeUrl(url: string | null) {
if (url === null) {
this._capeSet = false;
this.playerObject.cape.visible = false;
} else {
this._capeSet = true;
this.capeImg.src = url;
}
}
get width(): number {
return this.renderer.getSize(new Vector2()).width;
}
@ -195,3 +168,6 @@ export class SkinViewer {
this.setSize(this.width, newHeight);
}
}
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<LoadOptions> { }
applyMixins(SkinViewer, [SkinContainer, CapeContainer]);
export { SkinViewer };

View File

@ -2,11 +2,10 @@
"compilerOptions": {
"module": "es2015",
"moduleResolution": "node",
"target": "es2017",
"lib": [
"dom",
"es2015"
"dom"
],
"target": "es2015",
"strict": true
},
"include": [