From 940f0a70837a8ae96b9094fe18b7ea218e865ac0 Mon Sep 17 00:00:00 2001 From: James Harrison Date: Sun, 26 Apr 2020 00:00:10 +0100 Subject: [PATCH] Added ears --- examples/1_8_texturemap_redux.png | Bin 3684 -> 0 bytes examples/cape.png | Bin 0 -> 15814 bytes examples/ears.png | Bin 0 -> 233 bytes examples/index.html | 15 +++----- examples/skin.png | Bin 0 -> 1333 bytes src/model.ts | 48 +++++++++++++++++++++++-- src/viewer.ts | 56 +++++++++++++++++++++++++++--- 7 files changed, 103 insertions(+), 16 deletions(-) delete mode 100644 examples/1_8_texturemap_redux.png create mode 100644 examples/cape.png create mode 100644 examples/ears.png create mode 100644 examples/skin.png diff --git a/examples/1_8_texturemap_redux.png b/examples/1_8_texturemap_redux.png deleted file mode 100644 index a206b32da79d2a1be3de8e29425fc1a2f38ea7b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3684 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..879ee759535cecd1e1e74d0a5c0a4ea392262f7f GIT binary patch literal 1333 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=3dtTpz6=aistgPb%?u1b{{!il z3=E|P3=FRl7#OT(FffScPl`Y422{&g;1OBOz`%D1gc(IOyc&Rl5+$w?CBgY=CFO}l zsSE{)nRz98d8s7|CVB>XmN~|CehdsO(>+}rLn`9l&bZqvV=mCb|Lu0`3V|mQGLwY^ z{nEwmnr2)RIjLA~YO!W}qkqcR`z4?K_%9tl^Y7R5U`mblq;@i&5 z_VQ=d#vgx{vcJ%E{@=0kWx?trOs9`LZ|9%z`L5H=`^i~(D@7Nb-L_M8v;O0xH7`F- ziT~(cz`VM@CuwHfywCO1*$*W2{cn3C@%Y5iqKyv*RMfNEu#xtaD9AdgF2V z2jz!Y=O~JGhX$|ZpO9>Dta{s5h957j|LaLGaa(jpls8AzMc7w0NEJKZJ(sj#)j=Ue|a{=83u7^DNsrd~?&c z@EBjW&!u)xQ`TjwUE;gk22>{-b@pnC(%TJpP6x(o&d#3Ix#jSdg{vbUeq%52jpK}s zsI+g;u2L;YeEOGR-HWLgTBm+z-mxg$Nj&Tx|AQ_x1zAt!m4C2%G7CM;o6Yzv);0Ua za`*LmiLa~X_y}Jxx&N{8qI=Q4WRdw-nVvlO?N}Ike_`RXKa%Rn!4uw}3=gy~G-cTs z8B^=^#UjmM*U9;XMkX!_Qsyo7KLjV=|MKGJ%fpsVuZw(N1fS)&Euiq?-Rd2;+<&cY z%I|%kVDUM?d)CK~`S_xi5Q z2RNSbw}{$cNcz4{gVKcIJhY7p;lL* za$m}w$2KOle%iZ#w&wO0v%u{Py*&MWq_))lS@AcnS-5Xl5eYU$dFS$*K3g}<1`C>a z=6ujuYh(St>)GDpfhNo+&)rX+X5V!_EB#5;9e0I5OH&pPzvqp!e*1UwyfWS?USKMD z*1RO0f7bi#LgNsvCF^(WJI`EFC1WXQJ?&h1kgoL%kq7rodgfGF6+5TjXkM{%md}#V z%W+(3{hu#X@?YQ(@O)~uNB(D4-V2`BWv`XjX3r5UNL%>Vj(MlT{q!A*?~k0_!X$ol zCI1BZg{$udlrQ?+V^Y7tf^%{k>y;ht?=R%7us+CBDVO!HbOE2=taHH!L*KUT5}wN* z64+y(dgSNfEAk0{nVH(Gg&h2|_esCmUqNu;V9~ds$9_iyv$_KhSWgkIQWvkhK4QFSlU(hF8DZ nS*8~_-{pLMbP0l+XkKe8X`& literal 0 HcmV?d00001 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); +}