Created
October 22, 2022 12:35
-
-
Save mraliscoder/88265482b11c566c0641940f9de645d9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const TAU = 2 * Math.PI; | |
| const EPSILON = 1e-3; | |
| const skinLayout = [ | |
| { | |
| head: [ | |
| { | |
| l: { x: 16, y: 8, w: 8, h: 8 }, | |
| r: { x: 0, y: 8, w: 8, h: 8 }, | |
| u: { x: 8, y: 0, w: 8, h: 8 }, | |
| d: { x: 16, y: 7, w: 8, h: -8 }, | |
| f: { x: 8, y: 8, w: 8, h: 8 }, | |
| b: { x: 24, y: 8, w: 8, h: 8 }, | |
| }, | |
| { | |
| l: { x: 48, y: 8, w: 8, h: 8 }, | |
| r: { x: 32, y: 8, w: 8, h: 8 }, | |
| u: { x: 40, y: 0, w: 8, h: 8 }, | |
| d: { x: 48, y: 7, w: 8, h: -8 }, | |
| f: { x: 40, y: 8, w: 8, h: 8 }, | |
| b: { x: 56, y: 8, w: 8, h: 8 }, | |
| }, | |
| ], | |
| torso: [ | |
| { | |
| l: { x: 28, y: 20, w: 4, h: 12 }, | |
| r: { x: 16, y: 20, w: 4, h: 12 }, | |
| u: { x: 20, y: 16, w: 8, h: 4 }, | |
| d: { x: 28, y: 19, w: 8, h: -4 }, | |
| f: { x: 20, y: 20, w: 8, h: 12 }, | |
| b: { x: 32, y: 20, w: 8, h: 12 }, | |
| }, | |
| ], | |
| armR: [ | |
| { | |
| l: { x: 48, y: 20, w: 4, h: 12 }, | |
| r: { x: 40, y: 20, w: 4, h: 12 }, | |
| u: { x: 44, y: 16, w: 4, h: 4 }, | |
| d: { x: 48, y: 19, w: 4, h: -4 }, | |
| f: { x: 44, y: 20, w: 4, h: 12 }, | |
| b: { x: 52, y: 20, w: 4, h: 12 }, | |
| }, | |
| ], | |
| armRS: [ | |
| { | |
| l: { x: 47, y: 20, w: 4, h: 12 }, | |
| r: { x: 40, y: 20, w: 4, h: 12 }, | |
| u: { x: 44, y: 16, w: 3, h: 4 }, | |
| d: { x: 47, y: 19, w: 3, h: -4 }, | |
| f: { x: 44, y: 20, w: 3, h: 12 }, | |
| b: { x: 51, y: 20, w: 3, h: 12 }, | |
| }, | |
| ], | |
| armL: [ | |
| { | |
| l: { x: 43, y: 20, w: -4, h: 12 }, | |
| r: { x: 51, y: 20, w: -4, h: 12 }, | |
| u: { x: 47, y: 16, w: -4, h: 4 }, | |
| d: { x: 51, y: 19, w: -4, h: -4 }, | |
| f: { x: 47, y: 20, w: -4, h: 12 }, | |
| b: { x: 55, y: 20, w: -4, h: 12 }, | |
| }, | |
| ], | |
| armLS: [ | |
| { | |
| l: { x: 43, y: 20, w: -4, h: 12 }, | |
| r: { x: 50, y: 20, w: -4, h: 12 }, | |
| u: { x: 46, y: 16, w: -3, h: 4 }, | |
| d: { x: 49, y: 19, w: -3, h: -4 }, | |
| f: { x: 46, y: 20, w: -3, h: 12 }, | |
| b: { x: 53, y: 20, w: -3, h: 12 }, | |
| }, | |
| ], | |
| legR: [ | |
| { | |
| l: { x: 8, y: 20, w: 4, h: 12 }, | |
| r: { x: 0, y: 20, w: 4, h: 12 }, | |
| u: { x: 4, y: 16, w: 4, h: 4 }, | |
| d: { x: 8, y: 19, w: 4, h: -4 }, | |
| f: { x: 4, y: 20, w: 4, h: 12 }, | |
| b: { x: 12, y: 20, w: 4, h: 12 }, | |
| }, | |
| ], | |
| legL: [ | |
| { | |
| l: { x: 3, y: 20, w: -4, h: 12 }, | |
| r: { x: 11, y: 20, w: -4, h: 12 }, | |
| u: { x: 7, y: 16, w: -4, h: 4 }, | |
| d: { x: 11, y: 19, w: -4, h: -4 }, | |
| f: { x: 7, y: 20, w: -4, h: 12 }, | |
| b: { x: 15, y: 20, w: -4, h: 12 }, | |
| }, | |
| ], | |
| }, | |
| { | |
| head: [ | |
| { | |
| l: { x: 16, y: 8, w: 8, h: 8 }, | |
| r: { x: 0, y: 8, w: 8, h: 8 }, | |
| u: { x: 8, y: 0, w: 8, h: 8 }, | |
| d: { x: 16, y: 7, w: 8, h: -8 }, | |
| f: { x: 8, y: 8, w: 8, h: 8 }, | |
| b: { x: 24, y: 8, w: 8, h: 8 }, | |
| }, | |
| { | |
| l: { x: 48, y: 8, w: 8, h: 8 }, | |
| r: { x: 32, y: 8, w: 8, h: 8 }, | |
| u: { x: 40, y: 0, w: 8, h: 8 }, | |
| d: { x: 48, y: 7, w: 8, h: -8 }, | |
| f: { x: 40, y: 8, w: 8, h: 8 }, | |
| b: { x: 56, y: 8, w: 8, h: 8 }, | |
| }, | |
| ], | |
| torso: [ | |
| { | |
| l: { x: 28, y: 20, w: 4, h: 12 }, | |
| r: { x: 16, y: 20, w: 4, h: 12 }, | |
| u: { x: 20, y: 16, w: 8, h: 4 }, | |
| d: { x: 28, y: 19, w: 8, h: -4 }, | |
| f: { x: 20, y: 20, w: 8, h: 12 }, | |
| b: { x: 32, y: 20, w: 8, h: 12 }, | |
| }, | |
| { | |
| l: { x: 28, y: 36, w: 4, h: 12 }, | |
| r: { x: 16, y: 36, w: 4, h: 12 }, | |
| u: { x: 20, y: 32, w: 8, h: 4 }, | |
| d: { x: 28, y: 35, w: 8, h: -4 }, | |
| f: { x: 20, y: 36, w: 8, h: 12 }, | |
| b: { x: 32, y: 36, w: 8, h: 12 }, | |
| }, | |
| ], | |
| armR: [ | |
| { | |
| l: { x: 48, y: 20, w: 4, h: 12 }, | |
| r: { x: 40, y: 20, w: 4, h: 12 }, | |
| u: { x: 44, y: 16, w: 4, h: 4 }, | |
| d: { x: 48, y: 19, w: 4, h: -4 }, | |
| f: { x: 44, y: 20, w: 4, h: 12 }, | |
| b: { x: 52, y: 20, w: 4, h: 12 }, | |
| }, | |
| { | |
| l: { x: 48, y: 36, w: 4, h: 12 }, | |
| r: { x: 40, y: 36, w: 4, h: 12 }, | |
| u: { x: 44, y: 32, w: 4, h: 4 }, | |
| d: { x: 48, y: 35, w: 4, h: -4 }, | |
| f: { x: 44, y: 36, w: 4, h: 12 }, | |
| b: { x: 52, y: 36, w: 4, h: 12 }, | |
| }, | |
| ], | |
| armRS: [ | |
| { | |
| l: { x: 47, y: 20, w: 4, h: 12 }, | |
| r: { x: 40, y: 20, w: 4, h: 12 }, | |
| u: { x: 44, y: 16, w: 3, h: 4 }, | |
| d: { x: 47, y: 19, w: 3, h: -4 }, | |
| f: { x: 44, y: 20, w: 3, h: 12 }, | |
| b: { x: 51, y: 20, w: 3, h: 12 }, | |
| }, | |
| { | |
| l: { x: 47, y: 36, w: 4, h: 12 }, | |
| r: { x: 40, y: 36, w: 4, h: 12 }, | |
| u: { x: 44, y: 32, w: 3, h: 4 }, | |
| d: { x: 47, y: 35, w: 3, h: -4 }, | |
| f: { x: 44, y: 36, w: 3, h: 12 }, | |
| b: { x: 51, y: 36, w: 3, h: 12 }, | |
| }, | |
| ], | |
| armL: [ | |
| { | |
| l: { x: 40, y: 52, w: 4, h: 12 }, | |
| r: { x: 32, y: 52, w: 4, h: 12 }, | |
| u: { x: 36, y: 48, w: 4, h: 4 }, | |
| d: { x: 40, y: 51, w: 4, h: -4 }, | |
| f: { x: 36, y: 52, w: 4, h: 12 }, | |
| b: { x: 44, y: 52, w: 4, h: 12 }, | |
| }, | |
| { | |
| l: { x: 56, y: 52, w: 4, h: 12 }, | |
| r: { x: 48, y: 52, w: 4, h: 12 }, | |
| u: { x: 52, y: 48, w: 4, h: 4 }, | |
| d: { x: 56, y: 51, w: 4, h: -4 }, | |
| f: { x: 52, y: 52, w: 4, h: 12 }, | |
| b: { x: 60, y: 52, w: 4, h: 12 }, | |
| }, | |
| ], | |
| armLS: [ | |
| { | |
| l: { x: 39, y: 52, w: 4, h: 12 }, | |
| r: { x: 32, y: 52, w: 4, h: 12 }, | |
| u: { x: 36, y: 48, w: 3, h: 4 }, | |
| d: { x: 39, y: 51, w: 3, h: -4 }, | |
| f: { x: 36, y: 52, w: 3, h: 12 }, | |
| b: { x: 43, y: 52, w: 3, h: 12 }, | |
| }, | |
| { | |
| l: { x: 55, y: 52, w: 4, h: 12 }, | |
| r: { x: 48, y: 52, w: 4, h: 12 }, | |
| u: { x: 52, y: 48, w: 3, h: 4 }, | |
| d: { x: 55, y: 51, w: 3, h: -4 }, | |
| f: { x: 52, y: 52, w: 3, h: 12 }, | |
| b: { x: 59, y: 52, w: 3, h: 12 }, | |
| }, | |
| ], | |
| legR: [ | |
| { | |
| l: { x: 8, y: 20, w: 4, h: 12 }, | |
| r: { x: 0, y: 20, w: 4, h: 12 }, | |
| u: { x: 4, y: 16, w: 4, h: 4 }, | |
| d: { x: 8, y: 19, w: 4, h: -4 }, | |
| f: { x: 4, y: 20, w: 4, h: 12 }, | |
| b: { x: 12, y: 20, w: 4, h: 12 }, | |
| }, | |
| { | |
| l: { x: 8, y: 36, w: 4, h: 12 }, | |
| r: { x: 0, y: 36, w: 4, h: 12 }, | |
| u: { x: 4, y: 32, w: 4, h: 4 }, | |
| d: { x: 8, y: 35, w: 4, h: -4 }, | |
| f: { x: 4, y: 36, w: 4, h: 12 }, | |
| b: { x: 12, y: 36, w: 4, h: 12 }, | |
| }, | |
| ], | |
| legL: [ | |
| { | |
| l: { x: 24, y: 52, w: 4, h: 12 }, | |
| r: { x: 16, y: 52, w: 4, h: 12 }, | |
| u: { x: 20, y: 48, w: 4, h: 4 }, | |
| d: { x: 24, y: 51, w: 4, h: -4 }, | |
| f: { x: 20, y: 52, w: 4, h: 12 }, | |
| b: { x: 28, y: 52, w: 4, h: 12 }, | |
| }, | |
| { | |
| l: { x: 8, y: 52, w: 4, h: 12 }, | |
| r: { x: 0, y: 52, w: 4, h: 12 }, | |
| u: { x: 4, y: 48, w: 4, h: 4 }, | |
| d: { x: 8, y: 51, w: 4, h: -4 }, | |
| f: { x: 4, y: 52, w: 4, h: 12 }, | |
| b: { x: 12, y: 52, w: 4, h: 12 }, | |
| }, | |
| ], | |
| }, | |
| ]; | |
| function radians(d) { | |
| return d * (TAU / 360); | |
| } | |
| function toCanvas(image, x, y, w, h) { | |
| x = typeof x === "undefined" ? 0 : x; | |
| y = typeof y === "undefined" ? 0 : y; | |
| w = typeof w === "undefined" ? image.width : w; | |
| h = typeof h === "undefined" ? image.height : h; | |
| let canvas = document.createElement("canvas"); | |
| canvas.width = w; | |
| canvas.height = h; | |
| let ctx = canvas.getContext("2d"); | |
| ctx.drawImage(image, x, y, w, h, 0, 0, w, h); | |
| return canvas; | |
| } | |
| function makeOpaque(image) { | |
| let canvas = toCanvas(image); | |
| let ctx = canvas.getContext("2d"); | |
| let data = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| let pixels = data.data; | |
| for (let p = 3; p < pixels.length; p += 4) { | |
| pixels[p] = 255; | |
| } | |
| ctx.putImageData(data, 0, 0); | |
| return canvas; | |
| } | |
| function hasAlphaLayer(image) { | |
| let canvas = toCanvas(image); | |
| let ctx = canvas.getContext("2d"); | |
| let data = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| let pixels = data.data; | |
| for (let p = 3; p < pixels.length; p += 4) { | |
| if (pixels[p] !== 255) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| function capeScale(height) { | |
| if (height % 22 === 0) { | |
| return height / 22; | |
| } else if (height % 17 === 0) { | |
| return height / 17; | |
| } else if (height >= 32 && (height & (height - 1)) === 0) { | |
| // power of 2 | |
| return height / 32; | |
| } else { | |
| return Math.max(1, Math.floor(height / 22)); | |
| } | |
| } | |
| function drawSkin2D() { | |
| $("canvas.skin-2d").each(function (i, e) { | |
| let url = e.getAttribute("data-skin-hash"); | |
| let flip = e.getAttribute("data-flip") === "true"; | |
| let image = new Image(); | |
| image.crossOrigin = ""; | |
| image.src = url; | |
| image.onload = function () { | |
| let opaque = makeOpaque(image); | |
| let ctx = e.getContext("2d"); | |
| ctx.mozImageSmoothingEnabled = false; | |
| ctx.webkitImageSmoothingEnabled = false; | |
| ctx.msImageSmoothingEnabled = false; | |
| ctx.imageSmoothingEnabled = false; | |
| if (flip) { | |
| ctx.translate(e.width, e.height); | |
| ctx.scale(-1, -1); | |
| } | |
| ctx.drawImage(opaque, 8, 8, 8, 8, 0, 0, e.width, e.height); | |
| if (hasAlphaLayer(image)) { | |
| ctx.drawImage(image, 40, 8, 8, 8, 0, 0, e.width, e.height); | |
| } | |
| }; | |
| image.onerror = function () { | |
| console.error("Error loading " + image.src); | |
| }; | |
| }); | |
| $("canvas.cape-2d").each(function (i, e) { | |
| let url = e.getAttribute("data-cape-hash"); | |
| let flip = e.getAttribute("data-flip") === "true"; | |
| let image = new Image(); | |
| image.crossOrigin = ""; | |
| image.src = url; | |
| image.onload = function () { | |
| let cs = image ? capeScale(image.height) : null; | |
| console.log(image.height); | |
| let opaque = makeOpaque(image); | |
| let ctx = e.getContext("2d"); | |
| ctx.mozImageSmoothingEnabled = false; | |
| ctx.webkitImageSmoothingEnabled = false; | |
| ctx.msImageSmoothingEnabled = false; | |
| ctx.imageSmoothingEnabled = false; | |
| if (flip) { | |
| ctx.translate(e.width, e.height); | |
| ctx.scale(-1, -1); | |
| } | |
| ctx.drawImage(opaque, cs, cs, 10 * cs, 16 * cs, 0, 0, e.width, e.height); | |
| }; | |
| image.onerror = function () { | |
| console.error("Error loading " + image.src); | |
| }; | |
| }); | |
| } | |
| function colorFaces(geometry, canvas, rectangles) { | |
| if (!rectangles) return null; | |
| let pixels = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height).data; | |
| let f = 0; | |
| let faces = []; | |
| let materials = []; | |
| let materialIndexMap = {}; | |
| let side = THREE.FrontSide; | |
| Object.keys(rectangles).forEach(function (k) { | |
| let rect = rectangles[k]; | |
| let width = Math.abs(rect.w); | |
| let height = Math.abs(rect.h); | |
| let dj = Math.sign(rect.w); | |
| let di = Math.sign(rect.h); | |
| for (let y = 0, i = rect.y; y < height; y++, i += di) { | |
| for (let x = 0, j = rect.x; x < width; x++, j += dj, f += 2) { | |
| let p = 4 * (i * canvas.width + j); | |
| let a = pixels[p + 3]; | |
| if (a === 0) { | |
| side = THREE.DoubleSide; | |
| continue; | |
| } | |
| let materialIndex = materialIndexMap[a]; | |
| if (typeof materialIndex === "undefined") { | |
| materials.push( | |
| new THREE.MeshBasicMaterial({ | |
| vertexColors: THREE.FaceColors, | |
| opacity: a / 255, | |
| transparent: a !== 255, | |
| }) | |
| ); | |
| materialIndex = materials.length - 1; | |
| materialIndexMap[a] = materialIndex; | |
| if (a !== 255) { | |
| side = THREE.DoubleSide; | |
| } | |
| } | |
| let face1 = geometry.faces[f]; | |
| let face2 = geometry.faces[f + 1]; | |
| face1.color.r = pixels[p] / 255; | |
| face1.color.g = pixels[p + 1] / 255; | |
| face1.color.b = pixels[p + 2] / 255; | |
| face2.color = face1.color; | |
| face1.materialIndex = materialIndex; | |
| face2.materialIndex = materialIndex; | |
| faces.push(face1); | |
| faces.push(face2); | |
| } | |
| } | |
| }); | |
| if (faces.length === 0) return null; | |
| geometry.faces = faces; | |
| materials.forEach(function (m) { | |
| m.side = side; | |
| }); | |
| return new THREE.Mesh(new THREE.BufferGeometry().fromGeometry(geometry), materials); | |
| } | |
| function buildMinecraftModel(skinImage, capeImage, slim, flip) { | |
| if (skinImage.width < 64 || skinImage.height < 32) { | |
| return null; | |
| } | |
| let version = skinImage.height >= 64 ? 1 : 0; | |
| let cs = capeImage ? capeScale(capeImage.height) : null; | |
| let opaqueSkinCanvas = makeOpaque(skinImage); | |
| let transparentSkinCanvas = toCanvas(skinImage); | |
| let hasAlpha = hasAlphaLayer(skinImage); | |
| let headGroup = new THREE.Object3D(); | |
| headGroup.position.x = 0; | |
| headGroup.position.y = 12; | |
| headGroup.position.z = 0; | |
| let box = new THREE.BoxGeometry(8, 8, 8, 8, 8, 8); | |
| let headMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["head"][0]); | |
| headGroup.add(headMesh); | |
| if (hasAlpha) { | |
| box = new THREE.BoxGeometry(9, 9, 9, 8, 8, 8); | |
| let hatMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]["head"][1]); | |
| hatMesh && headGroup.add(hatMesh); | |
| } | |
| let torsoGroup = new THREE.Object3D(); | |
| torsoGroup.position.x = 0; | |
| torsoGroup.position.y = 2; | |
| torsoGroup.position.z = 0; | |
| box = new THREE.BoxGeometry(8 + EPSILON, 12 + EPSILON, 4 + EPSILON, 8, 12, 4); | |
| let torsoMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["torso"][0]); | |
| torsoGroup.add(torsoMesh); | |
| if (version >= 1 && hasAlpha) { | |
| box = new THREE.BoxGeometry(8.5 + EPSILON, 12.5 + EPSILON, 4.5 + EPSILON, 8, 12, 4); | |
| let jacketMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]["torso"][1]); | |
| jacketMesh && torsoGroup.add(jacketMesh); | |
| } | |
| let rightArmGroup = new THREE.Object3D(); | |
| rightArmGroup.position.x = slim ? -5.5 : -6; | |
| rightArmGroup.position.y = 6; | |
| rightArmGroup.position.z = 0; | |
| let rightArmMesh; | |
| if (slim) { | |
| box = new THREE.BoxGeometry(3, 12, 4, 3, 12, 4).translate(0, -4, 0); | |
| rightArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["armRS"][0]); | |
| } else { | |
| box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -4, 0); | |
| rightArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["armR"][0]); | |
| } | |
| rightArmGroup.add(rightArmMesh); | |
| if (version >= 1 && hasAlpha) { | |
| let rightSleeveMesh; | |
| if (slim) { | |
| box = new THREE.BoxGeometry( | |
| 3.5 + EPSILON * 4, | |
| 12.5 + EPSILON * 4, | |
| 4.5 + EPSILON * 4, | |
| 3, | |
| 12, | |
| 4 | |
| ).translate(0, -4, 0); | |
| rightSleeveMesh = colorFaces( | |
| box, | |
| transparentSkinCanvas, | |
| skinLayout[version]["armRS"][1] | |
| ); | |
| } else { | |
| box = new THREE.BoxGeometry( | |
| 4.5 + EPSILON * 4, | |
| 12.5 + EPSILON * 4, | |
| 4.5 + EPSILON * 4, | |
| 4, | |
| 12, | |
| 4 | |
| ).translate(0, -4, 0); | |
| rightSleeveMesh = colorFaces( | |
| box, | |
| transparentSkinCanvas, | |
| skinLayout[version]["armR"][1] | |
| ); | |
| } | |
| rightSleeveMesh && rightArmGroup.add(rightSleeveMesh); | |
| } | |
| let leftArmGroup = new THREE.Object3D(); | |
| leftArmGroup.position.x = slim ? 5.5 : 6; | |
| leftArmGroup.position.y = 6; | |
| leftArmGroup.position.z = 0; | |
| let leftArmMesh; | |
| if (slim) { | |
| box = new THREE.BoxGeometry(3, 12, 4, 3, 12, 4).translate(0, -4, 0); | |
| leftArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["armLS"][0]); | |
| } else { | |
| box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -4, 0); | |
| leftArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["armL"][0]); | |
| } | |
| leftArmGroup.add(leftArmMesh); | |
| if (version >= 1 && hasAlpha) { | |
| let leftSleeveMesh; | |
| if (slim) { | |
| box = new THREE.BoxGeometry( | |
| 3.5 + EPSILON * 4, | |
| 12.5 + EPSILON * 4, | |
| 4.5 + EPSILON * 4, | |
| 3, | |
| 12, | |
| 4 | |
| ).translate(0, -4, 0); | |
| leftSleeveMesh = colorFaces( | |
| box, | |
| transparentSkinCanvas, | |
| skinLayout[version]["armLS"][1] | |
| ); | |
| } else { | |
| box = new THREE.BoxGeometry( | |
| 4.5 + EPSILON * 4, | |
| 12.5 + EPSILON * 4, | |
| 4.5 + EPSILON * 4, | |
| 4, | |
| 12, | |
| 4 | |
| ).translate(0, -4, 0); | |
| leftSleeveMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]["armL"][1]); | |
| } | |
| leftSleeveMesh && leftArmGroup.add(leftSleeveMesh); | |
| } | |
| let rightLegGroup = new THREE.Object3D(); | |
| rightLegGroup.position.x = -2; | |
| rightLegGroup.position.y = -4; | |
| rightLegGroup.position.z = 0; | |
| box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -6, 0); | |
| let rightLegMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["legR"][0]); | |
| rightLegGroup.add(rightLegMesh); | |
| if (version >= 1 && hasAlpha) { | |
| box = new THREE.BoxGeometry( | |
| 4.5 + EPSILON * 2, | |
| 12.5 + EPSILON * 2, | |
| 4.5 + EPSILON * 2, | |
| 4, | |
| 12, | |
| 4 | |
| ).translate(0, -6, 0); | |
| let rightPantMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]["legR"][1]); | |
| rightPantMesh && rightLegGroup.add(rightPantMesh); | |
| } | |
| let leftLegGroup = new THREE.Object3D(); | |
| leftLegGroup.position.x = 2; | |
| leftLegGroup.position.y = -4; | |
| leftLegGroup.position.z = 0; | |
| box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -6, 0); | |
| let leftLegMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]["legL"][0]); | |
| leftLegGroup.add(leftLegMesh); | |
| if (version >= 1 && hasAlpha) { | |
| box = new THREE.BoxGeometry( | |
| 4.5 + EPSILON * 3, | |
| 12.5 + EPSILON * 3, | |
| 4.5 + EPSILON * 3, | |
| 4, | |
| 12, | |
| 4 | |
| ).translate(0, -6, 0); | |
| let leftPantMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]["legL"][1]); | |
| leftPantMesh && leftLegGroup.add(leftPantMesh); | |
| } | |
| let playerGroup = new THREE.Object3D(); | |
| playerGroup.add(headGroup); | |
| playerGroup.add(torsoGroup); | |
| playerGroup.add(rightArmGroup); | |
| playerGroup.add(leftArmGroup); | |
| playerGroup.add(rightLegGroup); | |
| playerGroup.add(leftLegGroup); | |
| if (capeImage) { | |
| let capeCanvas = makeOpaque(capeImage); | |
| let capeGroup = new THREE.Object3D(); | |
| capeGroup.position.x = 0; | |
| capeGroup.position.y = 8; | |
| capeGroup.position.z = -2; | |
| capeGroup.rotation.y += radians(180); | |
| let capeMesh; | |
| box = new THREE.BoxGeometry(10, 16, 1, 10 * cs, 16 * cs, 1 * cs).translate(0, -8, 0.5); | |
| capeMesh = colorFaces(box, capeCanvas, { | |
| left: { x: 11 * cs, y: 1 * cs, w: 1 * cs, h: 16 * cs }, | |
| right: { x: 0 * cs, y: 1 * cs, w: 1 * cs, h: 16 * cs }, | |
| top: { x: 1 * cs, y: 0 * cs, w: 10 * cs, h: 1 * cs }, | |
| bottom: { x: 11 * cs, y: cs - 1, w: 10 * cs, h: -1 * cs }, | |
| front: { x: 1 * cs, y: 1 * cs, w: 10 * cs, h: 16 * cs }, | |
| back: { x: 12 * cs, y: 1 * cs, w: 10 * cs, h: 16 * cs }, | |
| }); | |
| capeGroup.add(capeMesh); | |
| playerGroup.add(capeGroup); | |
| } | |
| if (flip) { | |
| playerGroup.rotation.z += radians(180); | |
| } | |
| return playerGroup; | |
| } | |
| let renderState; | |
| let renderButton; | |
| let skinButtons; | |
| let canvas3d; | |
| function toggleAnimation(e) { | |
| if (renderState.animate) { | |
| renderState.animate = false; | |
| e.children[0].classList.remove("fa-pause"); | |
| e.children[0].classList.add("fa-play"); | |
| canvas3d.parent().addClass("animation-paused"); | |
| } else { | |
| renderState.animate = true; | |
| e.children[0].classList.remove("fa-play"); | |
| e.children[0].classList.add("fa-pause"); | |
| canvas3d.parent().removeClass("animation-paused"); | |
| window.requestAnimationFrame(renderAnimation); | |
| } | |
| let date = new Date(); | |
| date.setFullYear(date.getFullYear() + 2); | |
| document.cookie = | |
| "animate=" + | |
| renderState.animate + | |
| "; domain=.namemc.com; path=/; expires=" + | |
| date.toUTCString(); | |
| } | |
| function renderAnimation() { | |
| if (renderState?.animate) { | |
| renderState.frame++; | |
| render(); | |
| window.requestAnimationFrame(renderAnimation); | |
| } | |
| } | |
| function render() { | |
| let time = (4 * renderState.frame) % 360; | |
| canvas3d.attr("data-time", time); | |
| renderButton.attr( | |
| "href", | |
| "https://render.namemc.com/skin/3d/body.png?" + | |
| "skin=" + | |
| canvas3d.attr("data-skin-hash") + | |
| "&model=" + | |
| canvas3d.attr("data-model") + | |
| "&theta=" + | |
| canvas3d.attr("data-theta") + | |
| "&phi=" + | |
| canvas3d.attr("data-phi") + | |
| "&time=" + | |
| canvas3d.attr("data-time") + | |
| "&width=600" + | |
| "&height=800" | |
| ); | |
| let angle = Math.sin(radians(time)); | |
| renderState.model.children[2].rotation.x = -radians(18) * angle; | |
| renderState.model.children[3].rotation.x = radians(18) * angle; | |
| renderState.model.children[4].rotation.x = radians(20) * angle; | |
| renderState.model.children[5].rotation.x = -radians(20) * angle; | |
| if (renderState.model.children[6]) { | |
| let capeAngle = Math.sin(radians(renderState.frame)); | |
| renderState.model.children[6].rotation.x = radians(18) - radians(6) * capeAngle; | |
| } | |
| renderState.renderer.render(renderState.scene, renderState.camera); | |
| if (renderState.canvas !== renderState.renderer.domElement) { | |
| renderState.canvas.getContext("2d").drawImage(renderState.renderer.domElement, 0, 0); | |
| } | |
| } | |
| function allowRotation(renderState, positionCamera) { | |
| function startRotation(t, id) { | |
| renderState.dragState[id] = { x: t.screenX, y: t.screenY }; | |
| } | |
| function rotate(t, id) { | |
| if (!renderState.dragState[id]) { | |
| return false; | |
| } | |
| let result = true; | |
| renderState.theta -= t.screenX - renderState.dragState[id].x; | |
| renderState.phi += t.screenY - renderState.dragState[id].y; | |
| renderState.canvas.setAttribute("data-theta", renderState.theta % 360); | |
| renderState.canvas.setAttribute("data-phi", renderState.phi % 360); | |
| renderButton.attr( | |
| "href", | |
| "https://render.namemc.com/skin/3d/body.png?" + | |
| "skin=" + | |
| canvas3d.attr("data-skin-hash") + | |
| "&model=" + | |
| canvas3d.attr("data-model") + | |
| "&theta=" + | |
| canvas3d.attr("data-theta") + | |
| "&phi=" + | |
| canvas3d.attr("data-phi") + | |
| "&time=" + | |
| canvas3d.attr("data-time") + | |
| "&width=600" + | |
| "&height=800" | |
| ); | |
| if (renderState.phi < -90) { | |
| renderState.phi = -90; | |
| result = false; | |
| } else if (renderState.phi > 90) { | |
| renderState.phi = 90; | |
| result = false; | |
| } | |
| positionCamera(renderState.camera, radians(renderState.theta), radians(renderState.phi)); | |
| renderState.renderer.render(renderState.scene, renderState.camera); | |
| renderState.dragState[id].x = t.screenX; | |
| renderState.dragState[id].y = t.screenY; | |
| return result; | |
| } | |
| function endRotation(t, id) { | |
| delete renderState.dragState[id]; | |
| } | |
| renderState.canvas.onmousedown = function (e) { | |
| e.preventDefault(); | |
| startRotation(e, "mouse"); | |
| }; | |
| window.onmousemove = function (e) { | |
| rotate(e, "mouse"); | |
| }; | |
| window.onmouseup = function (e) { | |
| endRotation(e, "mouse"); | |
| }; | |
| renderState.canvas.ontouchstart = function (e) { | |
| for (let i = 0; i < e.changedTouches.length; i++) { | |
| startRotation(e.changedTouches[i], e.changedTouches[i].identifier); | |
| } | |
| }; | |
| renderState.canvas.ontouchmove = function (e) { | |
| let result = false; | |
| for (let i = 0; i < e.changedTouches.length; i++) { | |
| if (rotate(e.changedTouches[i], e.changedTouches[i].identifier)) { | |
| result = true; | |
| } else { | |
| delete renderState.dragState[e.changedTouches[i].identifier]; | |
| } | |
| } | |
| if (result) { | |
| e.preventDefault(); | |
| } | |
| }; | |
| renderState.canvas.ontouchend = renderState.canvas.ontouchcancel = function (e) { | |
| for (let i = 0; i < e.changedTouches.length; i++) { | |
| endRotation(e.changedTouches[i], e.changedTouches[i].identifier); | |
| } | |
| }; | |
| } | |
| let renderer; | |
| function renderSkinHelper(canvas, animate, theta, phi, time, model) { | |
| if (renderState) { | |
| renderState.canvas = canvas; | |
| renderState.scene.remove(renderState.model); | |
| renderState.model = model; | |
| renderState.scene.add(model); | |
| render(); | |
| return; | |
| } | |
| if (!renderer) { | |
| renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true }); | |
| } | |
| renderState = { | |
| canvas: canvas, | |
| animate: animate, | |
| model: model, | |
| theta: theta, | |
| phi: phi, | |
| scene: new THREE.Scene(), | |
| camera: new THREE.PerspectiveCamera(32, canvas.width / canvas.height, 72 - 20, 72 + 20), | |
| //camera: new THREE.OrthographicCamera(-19 * canvas.width / canvas.height, 19 * canvas.width / canvas.height, 19, -19, 1, 100), | |
| renderer: renderer, | |
| dragState: {}, | |
| frame: time / 4, | |
| }; | |
| let origin = new THREE.Vector3(0, 0, 0); | |
| function positionCamera(camera, theta, phi) { | |
| let cosPhi = Math.cos(phi); | |
| camera.position.x = 72 * cosPhi * Math.sin(theta); | |
| camera.position.z = 72 * cosPhi * Math.cos(theta); | |
| camera.position.y = 72 * Math.sin(phi); | |
| camera.lookAt(origin); | |
| } | |
| renderState.scene.add(model); | |
| positionCamera(renderState.camera, radians(renderState.theta), radians(renderState.phi)); | |
| allowRotation(renderState, positionCamera); | |
| render(); | |
| if (renderState.animate) { | |
| window.requestAnimationFrame(renderAnimation); | |
| } | |
| } | |
| let modelCache = {}; | |
| function renderSkin(canvas, slim, flip, animate, theta, phi, time, skinHash, capeHash, callback) { | |
| let hash = [capeHash, skinHash, slim, flip].join(":"); | |
| function handleModel() { | |
| try { | |
| renderSkinHelper(canvas, animate, theta, phi, time, modelCache[hash]); | |
| callback(); | |
| } catch (e) { | |
| callback(e); | |
| } | |
| } | |
| if (modelCache[hash]) { | |
| handleModel(); | |
| } else { | |
| function handleImages(skinImage, capeImage) { | |
| let model = buildMinecraftModel(skinImage, capeImage, slim, flip); | |
| if (model) { | |
| modelCache[hash] = model; | |
| handleModel(); | |
| } else { | |
| callback(); | |
| } | |
| } | |
| let skinImage = new Image(); | |
| skinImage.crossOrigin = ""; | |
| skinImage.src = skinHash; | |
| skinImage.onload = function () { | |
| if (capeHash) { | |
| let capeImage = new Image(); | |
| capeImage.crossOrigin = ""; | |
| capeImage.src = capeHash; | |
| capeImage.onload = function () { | |
| handleImages(skinImage, capeImage); | |
| }; | |
| capeImage.onerror = function () { | |
| handleImages(skinImage, null); | |
| console.error("Error loading " + capeImage.src); | |
| }; | |
| } else { | |
| handleImages(skinImage, null); | |
| } | |
| }; | |
| skinImage.onerror = function () { | |
| console.error("Error loading " + skinImage.src); | |
| }; | |
| } | |
| } | |
| function drawFullSkin2D(e) { | |
| let isCape = e.classList.contains("cape-3d"); | |
| let skinHash = e.getAttribute("data-skin-hash"); | |
| let capeHash = e.getAttribute("data-cape-hash"); | |
| let url = isCape ? capeHash : skinHash; | |
| let flip = e.getAttribute("data-flip") === "true"; | |
| let image = new Image(); | |
| image.crossOrigin = ""; | |
| image.src = url; | |
| image.onload = function () { | |
| let opaque = makeOpaque(image); | |
| let ctx = e.getContext("2d"); | |
| ctx.save(); | |
| ctx.mozImageSmoothingEnabled = false; | |
| ctx.webkitImageSmoothingEnabled = false; | |
| ctx.msImageSmoothingEnabled = false; | |
| ctx.imageSmoothingEnabled = false; | |
| if (flip) { | |
| ctx.translate(e.width, e.height); | |
| ctx.scale(-1, -1); | |
| } | |
| ctx.translate(e.width / 2, e.height / 2); | |
| let scale; | |
| if (isCape) { | |
| scale = Math.min(Math.floor(e.width / 10), Math.floor(e.height / 16)) - 1; | |
| ctx.scale(scale, scale); | |
| ctx.drawImage(opaque, 1, 1, 10, 16, -5, -8, 10, 16); | |
| } else { | |
| scale = Math.min(Math.floor(e.width / 16), Math.floor(e.height / 32)) - 1; | |
| ctx.scale(scale, scale); | |
| ctx.drawImage(opaque, 8, 8, 8, 8, -4, -16, 8, 8); // face | |
| ctx.drawImage(opaque, 20, 20, 8, 12, -4, -8, 8, 12); // chest | |
| ctx.drawImage(opaque, 44, 20, 4, 12, -8, -8, 4, 12); // right arm | |
| let version = image.height >= 64 ? 1 : 0; | |
| if (version === 0) { | |
| ctx.save(); | |
| ctx.scale(-1, 1); | |
| ctx.drawImage(opaque, 44, 20, 4, 12, -8, -8, 4, 12); // left arm | |
| ctx.restore(); | |
| } else { | |
| ctx.drawImage(opaque, 36, 52, 4, 12, 4, -8, 4, 12); // left arm | |
| } | |
| ctx.drawImage(opaque, 4, 20, 4, 12, -4, 4, 4, 12); // right leg | |
| if (version === 0) { | |
| ctx.save(); | |
| ctx.scale(-1, 1); | |
| ctx.drawImage(opaque, 4, 20, 4, 12, -4, 4, 4, 12); // left leg | |
| ctx.restore(); | |
| } else { | |
| ctx.drawImage(opaque, 20, 52, 4, 12, 0, 4, 4, 12); // left leg | |
| } | |
| if (hasAlphaLayer(image)) { | |
| ctx.drawImage(image, 40, 8, 8, 8, -4, -16, 8, 8); // mask | |
| if (version >= 1) { | |
| ctx.drawImage(image, 20, 36, 8, 12, -4, -8, 8, 12); // jacket | |
| ctx.drawImage(image, 44, 36, 4, 12, -8, -8, 4, 12); // right sleeve | |
| ctx.drawImage(image, 52, 52, 4, 12, 4, -8, 4, 12); // left sleeve | |
| ctx.drawImage(image, 4, 36, 4, 12, -4, 4, 4, 12); // right pant | |
| ctx.drawImage(image, 4, 52, 4, 12, 0, 4, 4, 12); // left pant | |
| } | |
| } | |
| } | |
| ctx.restore(); | |
| }; | |
| image.onerror = function () { | |
| console.error("Error loading " + image.src); | |
| }; | |
| } | |
| function drawSkin3D() { | |
| if (!canvas3d.get(0)) return; | |
| let slim = canvas3d.attr("data-model") === "slim"; | |
| let skinHash = canvas3d.attr("data-skin-hash"); | |
| let capeHash = canvas3d.attr("data-cape-hash"); | |
| let flip = canvas3d.attr("data-flip") === "true"; | |
| let animate = canvas3d.attr("data-animate") === "true"; | |
| let theta = canvas3d.attr("data-theta") ? parseFloat(canvas3d.attr("data-theta")) : -30; | |
| let phi = canvas3d.attr("data-phi") ? parseFloat(canvas3d.attr("data-phi")) : 20; | |
| let time = canvas3d.attr("data-time") ? parseFloat(canvas3d.attr("data-time")) : 90; | |
| canvas3d.attr("data-model", slim ? "slim" : "classic"); | |
| canvas3d.attr("data-theta", theta); | |
| canvas3d.attr("data-phi", phi); | |
| canvas3d.attr("data-time", time); | |
| renderSkin( | |
| canvas3d.get(0), | |
| slim, | |
| flip, | |
| animate, | |
| theta, | |
| phi, | |
| time, | |
| skinHash, | |
| capeHash, | |
| function (err) { | |
| if (err) { | |
| $(".play-pause-btn").remove(); | |
| drawFullSkin2D(canvas3d.get(0)); | |
| } | |
| } | |
| ); | |
| } | |
| function updateSkin(element) { | |
| let skinHash = element.getAttribute("data-skin-hash"); | |
| let capeHash = element.getAttribute("data-cape-hash"); | |
| let model = element.getAttribute("data-model"); | |
| let redraw = false; | |
| if (skinHash && canvas3d.attr("data-skin-hash") !== skinHash) { | |
| canvas3d.attr("data-skin-hash", skinHash); | |
| redraw = true; | |
| } | |
| if (capeHash && canvas3d.attr("data-cape-hash") !== capeHash) { | |
| canvas3d.attr("data-cape-hash", capeHash); | |
| redraw = true; | |
| } | |
| if (model && canvas3d.attr("data-model") !== model) { | |
| canvas3d.attr("data-model", model); | |
| redraw = true; | |
| } | |
| if (redraw) { | |
| drawSkin3D(); | |
| skinButtons.each(function (i, e) { | |
| $(e).toggleClass( | |
| "skin-button-selected", | |
| $(e).attr("data-skin-hash") === canvas3d.attr("data-skin-hash") || | |
| $(e).attr("data-cape-hash") === canvas3d.attr("data-cape-hash") | |
| ); | |
| }); | |
| } | |
| return false; | |
| } | |
| function stopSkin3D() { | |
| renderer = undefined; | |
| renderState = undefined; | |
| } | |
| function initSkin3D() { | |
| renderer = undefined; | |
| renderState = undefined; | |
| renderButton = $("#render-button"); | |
| skinButtons = $(".skin-button"); | |
| canvas3d = $("canvas.skin-3d"); | |
| drawSkin3D(); | |
| drawSkin2D(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment