Skip to content

Instantly share code, notes, and snippets.

@fpigerre
Created May 17, 2014 01:54
Show Gist options
  • Save fpigerre/eb6908067ed254a2fa2f to your computer and use it in GitHub Desktop.
Save fpigerre/eb6908067ed254a2fa2f to your computer and use it in GitHub Desktop.
Minecraftt JavaScript Skin Animation
/**
* Code by djazz as part of his Minecraft Skin Viewer
* http://djazz.mine.nu/apps/MinecraftSkin/
*/
var MSP = (function (global, undefined) {
'use strict';
// shim layer with setTimeout fallback
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (/* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
var supportWebGL = !!global.WebGLRenderingContext && (!!global.document.createElement('canvas').getContext('experimental-webgl') || !!global.document.createElement('canvas').getContext('webgl'));
var container = global.document.querySelector('#skinpreview');
var cw = 400, ch = 550;
var tileUvWidth = 1 / 64;
var tileUvHeight = 1 / 32;
var skinBig = global.document.createElement('canvas');
var sbc = skinBig.getContext('2d');
var sizeRatio = 8;
skinBig.width = 64 * sizeRatio;
skinBig.height = 32 * sizeRatio;
var skincanvas = global.document.createElement('canvas');
var skinc = skincanvas.getContext('2d');
skincanvas.width = 64;
skincanvas.height = 32;
var capecanvas = global.document.createElement('canvas');
var capec = capecanvas.getContext('2d');
capecanvas.width = 64;
capecanvas.height = 32;
var isRotating = true;
var isPaused = false;
var isYfreezed = false;
var isFunnyRunning = false;
var getMaterial = function (img, trans) {
var material = new THREE.MeshBasicMaterial({
map: new THREE.Texture(
THREE.ImageUtils.loadTexture('images/minecraftskinpurple.png'),
new THREE.UVMapping(),
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter,
(trans ? THREE.RGBAFormat : THREE.RGBFormat)
),
transparent: trans
});
material.map.needsUpdate = true;
return material;
};
var uvmap = function (mesh, face, x, y, w, h, rotateBy) {
if (!rotateBy) rotateBy = 0;
var uvs = mesh.geometry.faceVertexUvs[0][face];
var tileU = x;
var tileV = y;
/*uvs[ (0 + rotateBy) % 4 ].u = tileU * tileUvWidth;
uvs[ (0 + rotateBy) % 4 ].v = tileV * tileUvHeight;
uvs[ (1 + rotateBy) % 4 ].u = tileU * tileUvWidth;
uvs[ (1 + rotateBy) % 4 ].v = tileV * tileUvHeight + h * tileUvHeight;
uvs[ (2 + rotateBy) % 4 ].u = tileU * tileUvWidth + w * tileUvWidth;
uvs[ (2 + rotateBy) % 4 ].v = tileV * tileUvHeight + h * tileUvHeight;
uvs[ (3 + rotateBy) % 4 ].u = tileU * tileUvWidth + w * tileUvWidth;
uvs[ (3 + rotateBy) % 4 ].v = tileV * tileUvHeight;*/
};
var cubeFromPlanes = function (size, mat) {
var cube = new THREE.Object3D();
var meshes = [];
for (var i = 0; i < 6; i++) {
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(size, size), mat);
mesh.doubleSided = true;
cube.add(mesh);
meshes.push(mesh);
}
// Front
meshes[0].rotation.x = Math.PI / 2;
meshes[0].rotation.z = -Math.PI / 2;
meshes[0].position.x = size / 2;
// Back
meshes[1].rotation.x = Math.PI / 2;
meshes[1].rotation.z = Math.PI / 2;
meshes[1].position.x = -size / 2;
// Top
meshes[2].position.y = size / 2;
// Bottom
meshes[3].rotation.y = Math.PI;
meshes[3].rotation.z = Math.PI;
meshes[3].position.y = -size / 2;
// Left
meshes[4].rotation.x = Math.PI / 2;
meshes[4].position.z = size / 2;
// Right
meshes[5].rotation.x = -Math.PI / 2;
meshes[5].rotation.y = Math.PI;
meshes[5].position.z = -size / 2;
return cube;
};
var charMaterial = getMaterial(skincanvas, false);
var charMaterialTrans = getMaterial(skincanvas, true);
var capeMaterial = getMaterial(capecanvas, false);
var camera = new THREE.PerspectiveCamera(35, cw / ch, 1, 1000);
camera.position.z = 50;
//camera.target.position.y = -2;
var scene = new THREE.Scene();
scene.add(camera);
var headgroup = new THREE.Object3D();
var upperbody = new THREE.Object3D();
// Left leg
var leftleggeo = new THREE.CubeGeometry(4, 12, 4);
for (var i = 0; i < 8; i += 1) {
leftleggeo.vertices[i].y -= 6;
}
var leftleg = new THREE.Mesh(leftleggeo, charMaterial);
leftleg.position.z = -2;
leftleg.position.y = -6;
uvmap(leftleg, 0, 8, 20, -4, 12);
uvmap(leftleg, 1, 16, 20, -4, 12);
uvmap(leftleg, 2, 4, 16, 4, 4, 3);
uvmap(leftleg, 3, 8, 20, 4, -4, 1);
uvmap(leftleg, 4, 12, 20, -4, 12);
uvmap(leftleg, 5, 4, 20, -4, 12);
// Right leg
var rightleggeo = new THREE.CubeGeometry(4, 12, 4);
for (var i = 0; i < 8; i += 1) {
rightleggeo.vertices[i].y -= 6;
}
var rightleg = new THREE.Mesh(rightleggeo, charMaterial);
rightleg.position.z = 2;
rightleg.position.y = -6;
uvmap(rightleg, 0, 4, 20, 4, 12);
uvmap(rightleg, 1, 12, 20, 4, 12);
uvmap(rightleg, 2, 8, 16, -4, 4, 3);
uvmap(rightleg, 3, 12, 20, -4, -4, 1);
uvmap(rightleg, 4, 0, 20, 4, 12);
uvmap(rightleg, 5, 8, 20, 4, 12);
// Body
var bodygeo = new THREE.CubeGeometry(4, 12, 8);
var bodymesh = new THREE.Mesh(bodygeo, charMaterial);
uvmap(bodymesh, 0, 20, 20, 8, 12);
uvmap(bodymesh, 1, 32, 20, 8, 12);
uvmap(bodymesh, 2, 20, 16, 8, 4, 1);
uvmap(bodymesh, 3, 28, 16, 8, 4, 3);
uvmap(bodymesh, 4, 16, 20, 4, 12);
uvmap(bodymesh, 5, 28, 20, 4, 12);
upperbody.add(bodymesh);
// Left arm
var leftarmgeo = new THREE.CubeGeometry(4, 12, 4);
for (var i = 0; i < 8; i += 1) {
leftarmgeo.vertices[i].y -= 4;
}
var leftarm = new THREE.Mesh(leftarmgeo, charMaterial);
leftarm.position.z = -6;
leftarm.position.y = 4;
leftarm.rotation.x = Math.PI / 32;
uvmap(leftarm, 0, 48, 20, -4, 12);
uvmap(leftarm, 1, 56, 20, -4, 12);
uvmap(leftarm, 2, 48, 16, -4, 4, 1);
uvmap(leftarm, 3, 52, 16, -4, 4, 3);
uvmap(leftarm, 4, 52, 20, -4, 12);
uvmap(leftarm, 5, 44, 20, -4, 12);
upperbody.add(leftarm);
// Right arm
var rightarmgeo = new THREE.CubeGeometry(4, 12, 4);
for (var i = 0; i < 8; i += 1) {
rightarmgeo.vertices[i].y -= 4;
}
var rightarm = new THREE.Mesh(rightarmgeo, charMaterial);
rightarm.position.z = 6;
rightarm.position.y = 4;
rightarm.rotation.x = -Math.PI / 32;
uvmap(rightarm, 0, 44, 20, 4, 12);
uvmap(rightarm, 1, 52, 20, 4, 12);
uvmap(rightarm, 2, 44, 16, 4, 4, 1);
uvmap(rightarm, 3, 48, 16, 4, 4, 3);
uvmap(rightarm, 4, 40, 20, 4, 12);
uvmap(rightarm, 5, 48, 20, 4, 12);
upperbody.add(rightarm);
//Head
var headgeo = new THREE.CubeGeometry(8, 8, 8);
var headmesh = new THREE.Mesh(headgeo, charMaterial);
headmesh.position.y = 2;
uvmap(headmesh, 0, 8, 8, 8, 8);
uvmap(headmesh, 1, 24, 8, 8, 8);
uvmap(headmesh, 2, 8, 0, 8, 8, 1);
uvmap(headmesh, 3, 16, 0, 8, 8, 3);
uvmap(headmesh, 4, 0, 8, 8, 8);
uvmap(headmesh, 5, 16, 8, 8, 8);
headgroup.add(headmesh);
// Helmet/hat
/*var helmetgeo = new THREE.CubeGeometry(9, 9, 9);
var helmetmesh = new THREE.Mesh(helmetgeo, charMaterialTrans);
helmetmesh.doubleSided = true;
helmetmesh.position.y = 2;
uvmap(helmetmesh, 0, 32+8, 8, 8, 8);
uvmap(helmetmesh, 1, 32+24, 8, 8, 8);
uvmap(helmetmesh, 2, 32+8, 0, 8, 8, 1);
uvmap(helmetmesh, 3, 32+16, 0, 8, 8, 3);
uvmap(helmetmesh, 4, 32+0, 8, 8, 8);
uvmap(helmetmesh, 5, 32+16, 8, 8, 8);*/
//headgroup.add(helmetmesh);
var helmet = cubeFromPlanes(9, charMaterialTrans);
helmet.position.y = 2;
uvmap(helmet.children[0], 0, 32 + 8, 8, 8, 8);
uvmap(helmet.children[1], 0, 32 + 24, 8, 8, 8);
uvmap(helmet.children[2], 0, 32 + 8, 0, 8, 8, 1);
uvmap(helmet.children[3], 0, 32 + 16, 0, 8, 8, 3);
uvmap(helmet.children[4], 0, 32 + 0, 8, 8, 8);
uvmap(helmet.children[5], 0, 32 + 16, 8, 8, 8);
headgroup.add(helmet);
var ears = new THREE.Object3D();
var eargeo = new THREE.CubeGeometry(1, (9 / 8) * 6, (9 / 8) * 6);
var leftear = new THREE.Mesh(eargeo, charMaterial);
var rightear = new THREE.Mesh(eargeo, charMaterial);
leftear.position.y = 2 + (9 / 8) * 5;
rightear.position.y = 2 + (9 / 8) * 5;
leftear.position.z = -(9 / 8) * 5;
rightear.position.z = (9 / 8) * 5;
// Right ear share same geometry, same uv-maps
uvmap(leftear, 0, 25, 1, 6, 6); // Front side
uvmap(leftear, 1, 32, 1, 6, 6); // Back side
uvmap(leftear, 2, 25, 0, 6, 1, 1); // Top edge
uvmap(leftear, 3, 31, 0, 6, 1, 1); // Bottom edge
uvmap(leftear, 4, 24, 1, 1, 6); // Left edge
uvmap(leftear, 5, 31, 1, 1, 6); // Right edge
ears.add(leftear);
ears.add(rightear);
leftear.visible = rightear.visible = false;
headgroup.add(ears);
headgroup.position.y = 8;
var capeOrigo = new THREE.Object3D();
var capegeo = new THREE.CubeGeometry(1, 16, 10);
var capemesh = new THREE.Mesh(capegeo, capeMaterial);
capemesh.position.y = -8;
capemesh.visible = false;
uvmap(capemesh, 0, 1, 1, 10, 16); // Front side
uvmap(capemesh, 1, 12, 1, 10, 16); // Back side
uvmap(capemesh, 2, 1, 0, 10, 1); // Top edge
uvmap(capemesh, 3, 11, 0, 10, 1, 1); // Bottom edge
uvmap(capemesh, 4, 0, 1, 1, 16); // Left edge
uvmap(capemesh, 5, 11, 1, 1, 16); // Right edge
capeOrigo.rotation.y = Math.PI;
capeOrigo.position.x = -2;
capeOrigo.position.y = 6;
capeOrigo.add(capemesh);
var playerModel = new THREE.Object3D();
playerModel.add(leftleg);
playerModel.add(rightleg);
playerModel.add(upperbody);
playerModel.add(headgroup);
playerModel.add(capeOrigo);
playerModel.position.y = 6;
var playerGroup = new THREE.Object3D();
playerGroup.add(playerModel);
scene.add(playerGroup);
var mouseX = 0;
var mouseY = 0.1;
var originMouseX = 0;
var originMouseY = 0;
var rad = 0;
var isMouseOver = false;
var isMouseDown = false;
var counter = 0;
var firstRender = true;
var startTime = Date.now();
var pausedTime = 0;
var render = function () {
requestAnimFrame(render, renderer.domElement);
var oldRad = rad;
var time = (Date.now() - startTime) / 1000;
if (!isMouseDown) {
//mouseX*=0.95;
if (!isYfreezed) {
mouseY *= 0.97;
}
if (isRotating) {
rad += 2;
}
}
else {
rad = mouseX;
}
if (mouseY > 500) {
mouseY = 500;
}
else if (mouseY < -500) {
mouseY = -500;
}
camera.position.x = -Math.cos(rad / (cw / 2) + (Math.PI / 0.9));
camera.position.z = -Math.sin(rad / (cw / 2) + (Math.PI / 0.9));
camera.position.y = (mouseY / (ch / 2)) * 1.5 + 0.2;
camera.position.setLength(70);
camera.lookAt(new THREE.Vector3(0, 1.5, 0));
if (!isPaused) {
counter += 0.01;
headgroup.rotation.y = Math.sin(time * 1.5) / 5;
headgroup.rotation.z = Math.sin(time) / 6;
if (isFunnyRunning) {
rightarm.rotation.z = 2 * Math.cos(0.6662 * time * 10 + Math.PI);
rightarm.rotation.x = 1 * (Math.cos(0.2812 * time * 10) - 1);
leftarm.rotation.z = 2 * Math.cos(0.6662 * time * 10);
leftarm.rotation.x = 1 * (Math.cos(0.2312 * time * 10) + 1);
rightleg.rotation.z = 1.4 * Math.cos(0.6662 * time * 10);
leftleg.rotation.z = 1.4 * Math.cos(0.6662 * time * 10 + Math.PI);
playerGroup.position.y = -6 + 1 * Math.cos(0.6662 * time * 10 * 2); // Jumping
playerGroup.position.z = 0.15 * Math.cos(0.6662 * time * 10); // Dodging when running
playerGroup.rotation.x = 0.01 * Math.cos(0.6662 * time * 10 + Math.PI); // Slightly tilting when running
capeOrigo.rotation.z = 0.1 * Math.sin(0.6662 * time * 10 * 2) + Math.PI / 2.5;
}
else {
leftarm.rotation.z = -Math.sin(time * 3) / 2;
leftarm.rotation.x = (Math.cos(time * 3) + Math.PI / 2) / 30;
rightarm.rotation.z = Math.sin(time * 3) / 2;
rightarm.rotation.x = -(Math.cos(time * 3) + Math.PI / 2) / 30;
leftleg.rotation.z = Math.sin(time * 3) / 3;
rightleg.rotation.z = -Math.sin(time * 3) / 3;
capeOrigo.rotation.z = Math.sin(time * 2) / 15 + Math.PI / 15;
playerGroup.position.y = -6; // Not jumping
}
}
renderer.render(scene, camera);
};
if (supportWebGL) {
var renderer = new THREE.WebGLRenderer({antialias: true});
}
else {
renderer = new THREE.CanvasRenderer({antialias: true});
}
var threecanvas = renderer.domElement;
renderer.setSize(cw, ch);
//renderer.setClearColorHex(0x000000, 0.25);
container.appendChild(threecanvas);
var onMouseMove = function (e) {
if (isMouseDown) {
mouseX = (e.pageX - threecanvas.offsetLeft - originMouseX);
mouseY = (e.pageY - threecanvas.offsetTop - originMouseY);
}
};
threecanvas.addEventListener('mousedown', function (e) {
e.preventDefault();
originMouseX = (e.pageX - threecanvas.offsetLeft) - rad;
originMouseY = (e.pageY - threecanvas.offsetTop) - mouseY;
isMouseDown = true;
isMouseOver = true;
onMouseMove(e);
}, false);
global.addEventListener('mouseup', function (e) {
isMouseDown = false;
}, false);
global.addEventListener('mousemove', onMouseMove, false);
threecanvas.addEventListener('mouseout', function (e) {
isMouseOver = false;
}, false);
container.appendChild(global.document.createElement('br'));
var spinBox = global.document.createElement('input');
spinBox.type = 'checkbox';
spinBox.checked = false;
spinBox.id = 'msp_spinbox';
var spinBoxLabel = global.document.createElement('label');
spinBoxLabel.textContent = "Freeze rotating";
spinBoxLabel.setAttribute('for', spinBox.id);
container.appendChild(spinBox);
container.appendChild(spinBoxLabel);
spinBox.addEventListener('change', function () {
isRotating = !spinBox.checked;
}, false);
container.appendChild(global.document.createElement('br'));
var moveBox = global.document.createElement('input');
moveBox.type = 'checkbox';
moveBox.checked = false;
moveBox.id = 'msp_movebox';
var moveBoxLabel = global.document.createElement('label');
moveBoxLabel.textContent = "Freeze movements";
moveBoxLabel.setAttribute('for', moveBox.id);
container.appendChild(moveBox);
container.appendChild(moveBoxLabel);
moveBox.addEventListener('change', function () {
isPaused = moveBox.checked;
// \o/
if (isPaused) {
pausedTime = Date.now() - startTime;
}
else {
startTime = Date.now() - pausedTime;
}
}, false);
container.appendChild(global.document.createElement('br'));
var yFreezeBox = global.document.createElement('input');
yFreezeBox.type = 'checkbox';
yFreezeBox.checked = false;
yFreezeBox.id = 'msp_yfreezebox';
var yFreezeBoxLabel = global.document.createElement('label');
yFreezeBoxLabel.textContent = "Freeze camera";
yFreezeBoxLabel.setAttribute('for', yFreezeBox.id);
container.appendChild(yFreezeBox);
container.appendChild(yFreezeBoxLabel);
yFreezeBox.addEventListener('change', function () {
isYfreezed = yFreezeBox.checked;
}, false);
container.appendChild(global.document.createElement('br'));
var capeBox = global.document.createElement('input');
capeBox.type = 'checkbox';
capeBox.checked = true;
capeBox.id = 'msp_capebox';
var capeBoxLabel = global.document.createElement('label');
capeBoxLabel.textContent = "Show capes";
capeBoxLabel.setAttribute('for', capeBox.id);
container.appendChild(capeBox);
container.appendChild(capeBoxLabel);
capeBox.addEventListener('change', function () {
if (capeBox.checked) {
capeOrigo.add(capemesh);
}
else {
capeOrigo.remove(capemesh);
}
}, false);
container.appendChild(global.document.createElement('br'));
var runBox = global.document.createElement('input');
runBox.type = 'checkbox';
runBox.checked = false;
runBox.id = 'msp_runbox';
var runBoxLabel = global.document.createElement('label');
runBoxLabel.textContent = "Classic running";
runBoxLabel.setAttribute('for', runBox.id);
container.appendChild(runBox);
container.appendChild(runBoxLabel);
runBox.addEventListener('change', function () {
isFunnyRunning = runBox.checked;
}, false);
container.appendChild(global.document.createElement('br'));
container.appendChild(skinBig);
render();
var skin = new Image();
skin.onload = function () {
skinc.clearRect(0, 0, 64, 32);
skinc.drawImage(skin, 0, 0);
var imgdata = skinc.getImageData(0, 0, 64, 32);
var pixels = imgdata.data;
sbc.clearRect(0, 0, skinBig.width, skinBig.height);
sbc.save();
var isOnecolor = true;
var colorCheckAgainst = [40, 0];
var colorIndex = (colorCheckAgainst[0] + colorCheckAgainst[1] * 64) * 4;
var isPixelDifferent = function (x, y) {
if (pixels[(x + y * 64) * 4 + 0] !== pixels[colorIndex + 0] || pixels[(x + y * 64) * 4 + 1] !== pixels[colorIndex + 1] || pixels[(x + y * 64) * 4 + 2] !== pixels[colorIndex + 2] || pixels[(x + y * 64) * 4 + 3] !== pixels[colorIndex + 3]) {
return true;
}
return false;
};
// Check if helmet/hat is a solid color
// Bottom row
for (var i = 32; i < 64; i += 1) {
for (var j = 8; j < 16; j += 1) {
if (isPixelDifferent(i, j)) {
isOnecolor = false;
break;
}
}
if (!isOnecolor) {
break;
}
}
if (!isOnecolor) {
// Top row
for (var i = 40; i < 56; i += 1) {
for (var j = 0; j < 8; j += 1) {
if (isPixelDifferent(i, j)) {
isOnecolor = false;
break;
}
}
if (!isOnecolor) {
break;
}
}
}
for (var i = 0; i < 64; i += 1) {
for (var j = 0; j < 32; j += 1) {
if (isOnecolor && ((i >= 32 && i < 64 && j >= 8 && j < 16) || (i >= 40 && i < 56 && j >= 0 && j < 8))) {
pixels[(i + j * 64) * 4 + 3] = 0;
}
sbc.fillStyle = 'rgba(' + pixels[(i + j * 64) * 4 + 0] + ', ' + pixels[(i + j * 64) * 4 + 1] + ', ' + pixels[(i + j * 64) * 4 + 2] + ', ' + pixels[(i + j * 64) * 4 + 3] / 255 + ')';
sbc.fillRect(i * sizeRatio, j * sizeRatio, sizeRatio, sizeRatio);
}
}
sbc.restore();
skinc.putImageData(imgdata, 0, 0);
charMaterial.map.needsUpdate = true;
charMaterialTrans.map.needsUpdate = true;
};
var cape = new Image();
cape.onload = function () {
capec.clearRect(0, 0, 64, 32);
capec.drawImage(cape, 0, 0);
capeMaterial.map.needsUpdate = true;
capemesh.visible = true;
};
cape.onerror = function () {
capemesh.visible = false;
};
var defaultImages = [
'simon.png',
'char.png',
'link.png',
'DefMenge.png',
'cod.png'
];
skin.src = defaultImages[Math.floor(Math.random() * defaultImages.length)];
var handleFiles = function (files) {
if (files.length > 0) {
var file = files[0];
if (file.type === 'image/png') {
var fr = new FileReader();
fr.onload = function (e) {
var img = new Image();
img.onload = function () {
if (this.width === 64 && this.height === 32) {
skin.src = img.src;
}
else {
alert("Error: The image must be 64x32 pixels!");
}
};
img.onerror = function () {
alert("Error: Not an image or unknown file format");
};
img.src = this.result;
};
fr.readAsDataURL(file);
}
else {
alert("Error: This is not a PNG image!");
}
}
};
threecanvas.addEventListener('dragenter', function (e) {
e.stopPropagation();
e.preventDefault();
threecanvas.className = "dragenter";
}, false);
threecanvas.addEventListener('dragleave', function (e) {
e.stopPropagation();
e.preventDefault();
threecanvas.className = "";
}, false);
threecanvas.addEventListener('dragover', function (e) {
e.stopPropagation();
e.preventDefault();
}, false);
threecanvas.addEventListener('drop', function (e) {
e.stopPropagation();
e.preventDefault();
threecanvas.className = "";
var dt = e.dataTransfer;
var files = dt.files;
handleFiles(files);
}, false);
global.document.forms.imageform.fileinput.addEventListener('change', function () {
var files = this.files;
handleFiles(files);
}, false);
return {
changeSkin: function (url) {
skin.src = url;
},
changeCape: function (url) {
cape.src = url;
},
setEars: function (val) {
leftear.visible = rightear.visible = val;
}
};
}(this));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment