diff --git a/examples/1_8_texturemap_redux.png b/examples/1_8_texturemap_redux.png
deleted file mode 100644
index a206b32..0000000
Binary files a/examples/1_8_texturemap_redux.png and /dev/null differ
diff --git a/examples/cape.png b/examples/cape.png
new file mode 100644
index 0000000..598ad68
Binary files /dev/null and b/examples/cape.png differ
diff --git a/examples/ears.png b/examples/ears.png
new file mode 100644
index 0000000..10c101f
Binary files /dev/null and b/examples/ears.png differ
diff --git a/examples/index.html b/examples/index.html
index 27a723c..7b14016 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -24,19 +24,14 @@
diff --git a/examples/skin.png b/examples/skin.png
new file mode 100644
index 0000000..879ee75
Binary files /dev/null and b/examples/skin.png differ
diff --git a/src/model.ts b/src/model.ts
index 377541b..79e1b46 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -17,6 +17,10 @@ function toCapeVertices(x1: number, y1: number, x2: number, y2: number): Array {
+ return toFaceVertices(x1, y1, x2, y2, 14.0, 7.0);
+}
+
function setVertices(box: BoxGeometry, top: Array, bottom: Array, left: Array, front: Array, right: Array, back: Array): void {
box.faceVertexUvs[0] = [];
@@ -416,12 +420,47 @@ export class CapeObject extends Group {
}
}
+export class EarsObject extends Group {
+
+ readonly leftEar: Mesh;
+ readonly rightEar: Mesh;
+
+ constructor(texture: Texture) {
+ super();
+
+ const earMaterial = new MeshBasicMaterial({ map: texture, transparent: true, opacity: 1, side: DoubleSide, alphaTest: 0.5 });
+
+ // back = outside
+ // front = inside
+ const earBox = new BoxGeometry(6, 6, 1, 0, 0, 0);
+ //x1: number, y1: number, x2: number, y2: number
+ setVertices(earBox,
+ //from look at back
+ toEarVertices(1, 0, 7, 1), //top
+ toEarVertices(7, 0, 13, 1), //bottom
+ toEarVertices(0, 1, 1, 7), //right
+ toEarVertices(1, 1, 7, 7), //front
+ toEarVertices(0, 1, 1, 7), //left
+ toEarVertices(8, 1, 14, 7) //back
+ );
+
+ this.leftEar = new Mesh(earBox, earMaterial);
+ this.leftEar.position.x = -5.5;
+ this.add(this.leftEar);
+
+ this.rightEar = new Mesh(earBox, earMaterial);
+ this.rightEar.position.x = 5.5;
+ this.add(this.rightEar);
+ }
+}
+
export class PlayerObject extends Group {
readonly skin: SkinObject;
readonly cape: CapeObject;
+ readonly ears: EarsObject
- constructor(skinTexture: Texture, capeTexture: Texture) {
+ constructor(skinTexture: Texture, capeTexture: Texture, earTexture: Texture) {
super();
this.skin = new SkinObject(skinTexture);
@@ -432,7 +471,12 @@ export class PlayerObject extends Group {
this.cape.name = "cape";
this.cape.position.z = -2;
this.cape.position.y = -4;
- this.cape.rotation.x = 25 * Math.PI / 180;
+ this.cape.rotation.x = 10 * Math.PI / 180;
this.add(this.cape);
+
+ this.ears = new EarsObject(earTexture);
+ this.ears.name = "ears";
+ this.ears.position.y = 5.5;
+ this.add(this.ears);
}
}
diff --git a/src/viewer.ts b/src/viewer.ts
index 9771647..9e0d06b 100644
--- a/src/viewer.ts
+++ b/src/viewer.ts
@@ -1,12 +1,13 @@
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";
+import { TextureCanvas, TextureSource, isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "skinview-utils";
export interface SkinViewerOptions {
domElement: Node;
skinUrl?: string;
capeUrl?: string;
+ earUrl?: string;
width?: number;
height?: number;
detectModel?: boolean;
@@ -26,6 +27,10 @@ export class SkinViewer {
public readonly capeCanvas: HTMLCanvasElement;
public readonly capeTexture: Texture;
+ public readonly earImg: HTMLImageElement;
+ public readonly earCanvas: HTMLCanvasElement;
+ public readonly earTexture: Texture;
+
public readonly scene: Scene;
public readonly camera: PerspectiveCamera;
public readonly renderer: WebGLRenderer;
@@ -36,6 +41,7 @@ export class SkinViewer {
private _renderPaused: boolean = false;
private _skinSet: boolean = false;
private _capeSet: boolean = false;
+ private _earSet: boolean = false;
constructor(options: SkinViewerOptions) {
this.domElement = options.domElement;
@@ -56,21 +62,28 @@ export class SkinViewer {
this.capeTexture.magFilter = NearestFilter;
this.capeTexture.minFilter = NearestFilter;
+ this.earImg = new Image();
+ this.earCanvas = document.createElement("canvas");
+ this.earTexture = new Texture(this.earCanvas);
+ this.earTexture.magFilter = NearestFilter;
+ this.earTexture.minFilter = NearestFilter;
+
// scene
this.scene = new Scene();
// Use smaller fov to avoid distortion
this.camera = new PerspectiveCamera(40);
- this.camera.position.y = -12;
- this.camera.position.z = 60;
+ this.camera.position.y = 0;
+ this.camera.position.z = 65;
this.renderer = new WebGLRenderer({ alpha: true });
this.domElement.appendChild(this.renderer.domElement);
- this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture);
+ this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture, this.earTexture);
this.playerObject.name = "player";
this.playerObject.skin.visible = false;
this.playerObject.cape.visible = false;
+ this.playerObject.ears.visible = false;
this.scene.add(this.playerObject);
// texture loading
@@ -96,12 +109,24 @@ export class SkinViewer {
this.playerObject.cape.visible = true;
};
+ this.earImg.crossOrigin = "anonymous";
+ this.earImg.onerror = (): void => console.error("Failed loading " + this.earImg.src);
+ this.earImg.onload = (): void => {
+ loadEarsToCanvas(this.earCanvas, this.earImg);
+
+ this.earTexture.needsUpdate = true;
+ this.playerObject.ears.visible = true;
+ };
+
if (options.skinUrl !== undefined) {
this.skinUrl = options.skinUrl;
}
if (options.capeUrl !== undefined) {
this.capeUrl = options.capeUrl;
}
+ if (options.earUrl !== undefined) {
+ this.earUrl = options.earUrl;
+ }
this.width = options.width === undefined ? 300 : options.width;
this.height = options.height === undefined ? 300 : options.height;
@@ -179,6 +204,20 @@ export class SkinViewer {
}
}
+ get earUrl(): string | null {
+ return this._earSet ? this.earImg.src : null;
+ }
+
+ set earUrl(url: string | null) {
+ if (url === null) {
+ this._earSet = false;
+ this.playerObject.ears.visible = false;
+ } else {
+ this._earSet = true;
+ this.earImg.src = url;
+ }
+ }
+
get width(): number {
return this.renderer.getSize(new Vector2()).width;
}
@@ -195,3 +234,12 @@ export class SkinViewer {
this.setSize(this.width, newHeight);
}
}
+
+function loadEarsToCanvas(canvas: TextureCanvas, image: TextureSource): void {
+ canvas.width = 14;
+ canvas.height = 7;
+
+ const context = canvas.getContext("2d")!;
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ context.drawImage(image, 0, 0, image.width, image.height);
+}