Merge branch 'master' of github.com:bs-community/skinview3d into add-storybook

This commit is contained in:
Sean Boult 2020-07-10 08:52:02 -05:00
commit e885a68731
9 changed files with 2977 additions and 2952 deletions

View File

@ -9,3 +9,4 @@ extends:
rules: rules:
'@typescript-eslint/no-inferrable-types': off '@typescript-eslint/no-inferrable-types': off
'@typescript-eslint/interface-name-prefix': 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 ```html
<div id="skin_container"></div> <div id="skin_container"></div>
<script> <script>
let skinViewer = new skinview3d.SkinViewer({ let skinViewer = new skinview3d.SkinViewer(document.getElementById("skin_container"), {
domElement: document.getElementById("skin_container"), width: 300,
width: 600, height: 400,
height: 600, skin: "img/skin.png"
skinUrl: "img/skin.png",
capeUrl: "img/cape.png"
}); });
// Change the textures // Change viewer size
skinViewer.skinUrl = "img/skin2.png"; skinViewer.width = 600;
skinViewer.capeUrl = "img/cape2.png"; skinViewer.height = 800;
// Resize the skin viewer // Load another skin
skinViewer.width = 300; skinViewer.loadSkin("img/skin2.png");
skinViewer.height = 400;
// Load a cape
skinViewer.loadCape("img/cape.png");
// Unload(hide) the cape
skinViewer.loadCape(null);
// Control objects with your mouse! // Control objects with your mouse!
let control = skinview3d.createOrbitControls(skinViewer); let control = skinview3d.createOrbitControls(skinViewer);

40
examples/index.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>skinview3d</title>
<link href="https://fonts.googleapis.com/css?family=Archivo+Black" rel="stylesheet">
<style>
html,
body {
margin: 0;
padding: 0;
background-color: #1e1e1e;
}
</style>
</head>
<body>
<div id="skin_container"></div>
<script type="text/javascript" src="../bundles/skinview3d.bundle.js"></script>
<script>
let skinViewer = new skinview3d.SkinViewer(document.getElementById("skin_container"), {
width: 400,
height: 300,
skin: "./1_8_texturemap_redux.png"
});
let control = new skinview3d.createOrbitControls(skinViewer);
skinViewer.animations.add(skinview3d.WalkingAnimation);
skinViewer.animations.speed = 1.5;
</script>
</body>
</html>

5553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,63 @@
{ {
"name": "skinview3d", "name": "skinview3d",
"version": "2.0.0-alpha.1", "version": "2.0.0-alpha.5",
"description": "Three.js powered Minecraft skin viewer", "description": "Three.js powered Minecraft skin viewer",
"main": "libs/skinview3d.js", "main": "libs/skinview3d.js",
"type": "module", "scripts": {
"scripts": { "clean": "rimraf libs bundles",
"clean": "rimraf libs bundles", "build:modules": "tsc --declaration --sourceMap --outDir libs -p .",
"build:modules": "tsc -p .", "build:bundles": "rollup -c",
"build:bundles": "rollup -c", "build": "npm run build:modules && npm run build:bundles",
"build": "npm run build:modules && npm run build:bundles", "test:lint": "eslint --ext .ts src",
"test:lint": "eslint --ext .ts src", "test": "npm run test:lint",
"test": "npm run test:lint", "dev:watch:modules": "tsc -w --declaration --sourceMap --outDir libs -p .",
"dev:watch:modules": "tsc -w -p .", "dev:watch:bundles": "rollup -w -c",
"dev:watch:bundles": "rollup -w -c", "dev:serve": "ws",
"dev:serve": "ws", "storybook": "start-storybook -s ./public -p 6006",
"dev": "npm-run-all --parallel dev:watch:modules dev:watch:bundles dev:serve", "dev": "npm-run-all --parallel dev:watch:modules dev:watch:bundles dev:serve",
"prepublishOnly": "npm run clean && npm run build", "prepublishOnly": "npm run clean && npm run build",
"storybook": "start-storybook -s ./public -p 6006", "build-storybook": "build-storybook"
"build-storybook": "build-storybook" },
}, "repository": {
"repository": { "type": "git",
"type": "git", "url": "git+https://github.com/bs-community/skinview3d.git"
"url": "git+https://github.com/bs-community/skinview3d.git" },
}, "author": "Haowei Wen <yushijinhun@gmail.com> (https://github.com/yushijinhun)",
"author": "Haowei Wen <yushijinhun@gmail.com> (https://github.com/yushijinhun)", "contributors": [
"contributors": [ "Sean Boult <hacksore@mcskinsearch.com> (https://github.com/Hacksore)",
"Sean Boult <hacksore@mcskinsearch.com> (https://github.com/Hacksore)", "Pig Fang <g-plane@hotmail.com> (https://github.com/g-plane)",
"Pig Fang <g-plane@hotmail.com> (https://github.com/g-plane)", "printempw <h@prinzeugen.net> (https://github.com/printempw)",
"printempw <h@prinzeugen.net> (https://github.com/printempw)", "Kent Rasmussen <hyprkookeez@gmail.com> (https://github.com/earthiverse)"
"Kent Rasmussen <hyprkookeez@gmail.com> (https://github.com/earthiverse)" ],
], "license": "MIT",
"license": "MIT", "bugs": {
"bugs": { "url": "https://github.com/bs-community/skinview3d/issues"
"url": "https://github.com/bs-community/skinview3d/issues" },
}, "homepage": "https://github.com/bs-community/skinview3d",
"homepage": "https://github.com/bs-community/skinview3d", "files": [
"files": [ "libs",
"libs", "bundles"
"bundles" ],
], "dependencies": {
"dependencies": { "@storybook/addon-knobs": "^5.3.19",
"skinview-utils": "^0.2.0", "skinview-utils": "^0.5.5",
"three": "^0.112.1" "three": "^0.117.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.7", "@babel/core": "^7.10.4",
"@rollup/plugin-node-resolve": "^7.0.0", "@rollup/plugin-node-resolve": "^8.1.0",
"@rollup/plugin-typescript": "^2.1.0", "@rollup/plugin-typescript": "^5.0.0",
"@storybook/html": "^5.3.14", "@storybook/html": "^5.3.19",
"@typescript-eslint/eslint-plugin": "^2.17.0", "@typescript-eslint/eslint-plugin": "^3.4.0",
"@typescript-eslint/parser": "^2.17.0", "@typescript-eslint/parser": "^3.4.0",
"@yushijinhun/three-minifier-rollup": "^0.1.4", "@yushijinhun/three-minifier-rollup": "^0.1.7",
"babel-loader": "^8.0.6", "babel-loader": "^8.1.0",
"eslint": "^6.8.0", "eslint": "^7.3.1",
"local-web-server": "^3.0.7", "local-web-server": "^4.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rimraf": "^3.0.0", "rimraf": "^3.0.2",
"rollup": "^1.29.1", "rollup": "^2.18.0",
"rollup-plugin-terser": "^5.2.0", "rollup-plugin-terser": "^6.1.0",
"typescript": "^3.7.5", "typescript": "^3.9.5"
"@storybook/addon-knobs": "^5.3.14" }
}
} }

View File

@ -102,7 +102,7 @@ export class CompositeAnimation implements IAnimation {
export class RootAnimation extends CompositeAnimation implements AnimationHandle { export class RootAnimation extends CompositeAnimation implements AnimationHandle {
speed: number = 1.0; speed: number = 1.0;
progress: number = 0.0; progress: number = 0.0;
readonly clock: Clock = new Clock(true); private readonly clock: Clock = new Clock(true);
get animation(): RootAnimation { get animation(): RootAnimation {
return this; 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"; 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> { 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 { 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] = [];
box.faceVertexUvs[0][0] = [right[3], right[0], right[2]]; box.faceVertexUvs[0][0] = [right[3], right[0], right[2]];
box.faceVertexUvs[0][1] = [right[0], right[1], right[2]]; box.faceVertexUvs[0][1] = [right[0], right[1], right[2]];
@ -59,7 +59,7 @@ export class SkinObject extends Group {
readonly leftLeg: BodyPart; readonly leftLeg: BodyPart;
private modelListeners: Array<() => void> = []; // called when model(slim property) is changed private modelListeners: Array<() => void> = []; // called when model(slim property) is changed
private _slim = false; private slim = false;
constructor(texture: Texture) { constructor(texture: Texture) {
super(); super();
@ -210,12 +210,12 @@ export class SkinObject extends Group {
const rightArmPivot = new Group(); const rightArmPivot = new Group();
rightArmPivot.add(rightArmMesh, rightArm2Mesh); rightArmPivot.add(rightArmMesh, rightArm2Mesh);
rightArmPivot.position.y = -6; rightArmPivot.position.y = -4;
this.rightArm = new BodyPart(rightArmMesh, rightArm2Mesh); this.rightArm = new BodyPart(rightArmMesh, rightArm2Mesh);
this.rightArm.name = "rightArm"; this.rightArm.name = "rightArm";
this.rightArm.add(rightArmPivot); this.rightArm.add(rightArmPivot);
this.rightArm.position.y = -4; this.rightArm.position.y = -6;
this.modelListeners.push(() => { this.modelListeners.push(() => {
this.rightArm.position.x = this.slim ? -5.5 : -6; this.rightArm.position.x = this.slim ? -5.5 : -6;
}); });
@ -283,12 +283,12 @@ export class SkinObject extends Group {
const leftArmPivot = new Group(); const leftArmPivot = new Group();
leftArmPivot.add(leftArmMesh, leftArm2Mesh); leftArmPivot.add(leftArmMesh, leftArm2Mesh);
leftArmPivot.position.y = -6; leftArmPivot.position.y = -4;
this.leftArm = new BodyPart(leftArmMesh, leftArm2Mesh); this.leftArm = new BodyPart(leftArmMesh, leftArm2Mesh);
this.leftArm.name = "leftArm"; this.leftArm.name = "leftArm";
this.leftArm.add(leftArmPivot); this.leftArm.add(leftArmPivot);
this.leftArm.position.y = -4; this.leftArm.position.y = -6;
this.modelListeners.push(() => { this.modelListeners.push(() => {
this.leftArm.position.x = this.slim ? 5.5 : 6; this.leftArm.position.x = this.slim ? 5.5 : 6;
}); });
@ -364,15 +364,15 @@ export class SkinObject extends Group {
this.leftLeg.position.x = 2; this.leftLeg.position.x = 2;
this.add(this.leftLeg); this.add(this.leftLeg);
this.slim = false; this.modelType = "default";
} }
get slim(): boolean { get modelType(): ModelType {
return this._slim; return this.slim ? "slim" : "default";
} }
set slim(value) { set modelType(value: ModelType) {
this._slim = value; this.slim = value === "slim";
this.modelListeners.forEach(listener => listener()); this.modelListeners.forEach(listener => listener());
} }

View File

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

View File

@ -2,16 +2,11 @@
"compilerOptions": { "compilerOptions": {
"module": "es2015", "module": "es2015",
"moduleResolution": "node", "moduleResolution": "node",
"target": "es2017",
"lib": [ "lib": [
"dom", "dom"
"es2015"
], ],
"target": "es2015", "strict": true
"strict": true,
"declaration": true,
"sourceMap": true,
"outDir": "libs",
"types": []
}, },
"include": [ "include": [
"src" "src"