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); +}