Skip to content

Instantly share code, notes, and snippets.

@mraliscoder
Created October 22, 2022 12:35
Show Gist options
  • Save mraliscoder/88265482b11c566c0641940f9de645d9 to your computer and use it in GitHub Desktop.
Save mraliscoder/88265482b11c566c0641940f9de645d9 to your computer and use it in GitHub Desktop.
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