minetest-skin-converter/uvcharacter.js

248 lines
10 KiB
JavaScript
Raw Permalink Normal View History

2017-05-27 23:33:59 -07:00
// requires uvareas.js, threee.js
/*
Usage:
Make sure to load uvareas.js before uvcharacter.js:
<script src="three.min.js" type="text/javascript"></script>
<script src="uvareas.js" type="text/javascript"></script>
<script src="uvcharacter.js" type="text/javascript"></script>
Add an element to the HTML page to render the 3D character:
<div id="characterElementId"><!-- Placeholder for the 3d Character --></div>
Create a new instance and supply the element ID of the 3D character:
var minecraftCharacter = new MinecraftCharacter('characterElementId');
Get references to some variables you may want to modify:
var localScene = minecraftCharacter.scene; // The THREE.js scene
var localCharacterCanvas = minecraftCharacter.canvas; // The canvas which contains the UV map of the skin
Create or load a UV map and assign it to the canvas:
var characterImage = new Image(); // now do something
localCharacterCanvas.getContext("2d").drawImage(characterImage, 0, 0);
Finally call render to show the character:
minecraftCharacter.render;
*/
class MinecraftCharacter {
constructor(characterRenderElementIdName) {
console.log("MinecraftCharacter(" + characterRenderElementIdName + ")");
characterElementId = characterRenderElementIdName;
createCharacter();
}
get canvas() {
return characterCanvas;
}
get scene() {
return scene;
}
get render() {
characterImageLoaded();
return null;
}
get reRender() {
reSetupScene();
return null;
}
}
// ---------------------------------------------------------------------------
// Everything below is private even though Javascript does not support this.
// Using these vars in other loaded JS files may break this class
// ---------------------------------------------------------------------------
// canvas to store the skin image
var characterCanvas = document.createElement('canvas');
// element name to display the 3d character in
var characterElementId = 'characterElementId';
// All meshes are stored here so one can access them later
var characterMeshes = {};
// TODO: Move to constructor instead of const
const constCharacterScale = 4;
var isInitialized = false;
var radius = 32;
var alpha = 0;
var renderer; // THREE.WebGLRenderer
var camera; // THREE.PerspectiveCamera
var scene = new THREE.Scene();
var skinTexture = new THREE.Texture(characterCanvas);
skinTexture.magFilter = THREE.NearestFilter;
skinTexture.minFilter = THREE.NearestMipMapNearestFilter;
// Set the material for character and cloth
var materialFigure = new THREE.MeshBasicMaterial({map: skinTexture, side: THREE.FrontSide});
var materialCloth = new THREE.MeshBasicMaterial({map: skinTexture, transparent: true, opacity: 1, alphaTest: 0.5, side: THREE.DoubleSide});
function createCharacter() {
// Head Parts
// UV Map: https://solutiondesign.com/blog/-/blogs/webgl-and-three-js-texture-mappi-1/
/* We have 16x16 areas in the range (0/0)-(1/1). Each area is 0.0625×0.0625.
0/0 is bottom left and 1/1 top right */
var uvFaceWidth = 1/constUvWidth;
var i = 0;
// scale the character a little lit larger
var areaPartScale = 4;
for (var part in constBodyParts) {
var bodyPart = constBodyParts[part];
var areaPart = new UvArea(eval("const" + bodyPart));
//console.log(bodyPart, areaPart);
// Define the geometry
// Set the size (Character / Clothes)
if ( (bodyPart == constHead) || (bodyPart == constBody) || (bodyPart == constArmLeft) || (bodyPart == constArmRight) || (bodyPart == constLegRight) || (bodyPart == constLegLeft) ) {
areaPartScale = constCharacterScale;
} else {
// The Hat, Jacket, Sleeves and Trousers are a little bit larger
areaPartScale = constCharacterScale * 1.125;
};
var partBox = new THREE.BoxGeometry(areaPartScale * areaPart.width, areaPartScale * areaPart.height, areaPartScale * areaPart.depth, 1, 1, 1);
// Purge exisiting UV definitions
partBox.faceVertexUvs[0] = [];
i = 0;
// Create new UV definitions for all 6 faces
for (var face in constBoxFaces) {
var boxFace = constBoxFaces[face];
// locate the area on the loaded image with 4 points: Bottom/Left, Bottom/Right, Top/Right, Top/Left
// to create a face
var partFace = [
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y") - eval("areaPart."+boxFace+".h"))),
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x") + eval("areaPart."+boxFace+".w")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y") - eval("areaPart."+boxFace+".h"))),
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x") + eval("areaPart."+boxFace+".w")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y"))),
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y")))
];
// Map the face to the cube. 2 triangles for each rectangle / square
partBox.faceVertexUvs[0][i] = [partFace[3], partFace[0], partFace[2]]; i++;
partBox.faceVertexUvs[0][i] = [partFace[0], partFace[1], partFace[2]]; i++;
}
// Select the material (Character or Cloth)
var partMesh = null;
if ( (bodyPart == constHead) || (bodyPart == constArmLeft) || (bodyPart == constArmRight) || (bodyPart == constLegRight) || (bodyPart == constLegLeft) ) {
partMesh = new THREE.Mesh(partBox, materialFigure);
} else {
partMesh = new THREE.Mesh(partBox, materialCloth);
}
partMesh.name = String(bodyPart);
// Move the cube(bodyPart) to the right position to create the figure
if ( (bodyPart == constHead) || (bodyPart == constHat) ) {
// don't move the Head
} else if ( (bodyPart == constBody) || (bodyPart == constJacket) ) {
// place the Body under the Head
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
} else if ( (bodyPart == constArmLeft) || (bodyPart == constSleeveLeft) ) {
// place the Arm next to the Body
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
partMesh.position.x = constCharacterScale * (areaBody.width/2 + areaArmLeft.width/2);
} else if ( (bodyPart == constArmRight) || (bodyPart == constSleeveRight) ) {
// place the Arm next to the Body
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
partMesh.position.x = -constCharacterScale * (areaBody.width/2 + areaArmRight.width/2);
} else if ( (bodyPart == constLegRight) || (bodyPart == constTrouserRight) ) {
// place the Leg under the Body + Head
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height + areaLegRight.height/2);
partMesh.position.x = -constCharacterScale * (areaLegRight.width/2);
} else if ( (bodyPart == constLegLeft) || (bodyPart == constTrouserLeft) ) {
// place the Leg under the Body + Head
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height + areaLegLeft.height/2);
partMesh.position.x = constCharacterScale * (areaLegLeft.width/2);
}
scene.add(partMesh);
characterMeshes[bodyPart] = partMesh;
}
}
function characterImageLoaded() {
skinTexture.needsUpdate = true;
materialFigure.needsUpdate = true;
materialCloth.needsUpdate = true;
if (isInitialized == false) {
isInitialized = true;
//createCharacter();
setupScene();
Animate();
}
}
function setupScene() {
var sceneElement = document.getElementById(characterElementId);
var containerRect = sceneElement.getBoundingClientRect();
var width = containerRect.width;
var height = containerRect.height;
/*
PerspectiveCamera( fov, aspect, near, far )
fov Camera frustum vertical field of view.
aspect Camera frustum aspect ratio.
near Camera frustum near plane.
far Camera frustum far plane.
*/
camera = new THREE.PerspectiveCamera(75, 1, 1, 10000);
camera.position.y = -constCharacterScale * 3;
renderer = new THREE.WebGLRenderer({alpha: true});
renderer.setSize(width, width);
sceneElement.addEventListener('resize', reSetupScene, false);
sceneElement.appendChild(renderer.domElement);
}
function reSetupScene() {
if (isInitialized == true) {
renderer.setSize(2, 2);
var sceneElement = document.getElementById(characterElementId);
var containerRect = sceneElement.getBoundingClientRect();
var width = containerRect.width;
renderer.setSize(width, width);
};
}
function Animate() {
requestAnimationFrame(Animate);
camera.rotation.y = alpha;
alpha += Math.PI / 320;
camera.position.z = radius*Math.cos(alpha);
camera.position.x = radius*Math.sin(alpha);
var meshLegLeft = characterMeshes[constLegLeft];
var meshTrouserLeft = characterMeshes[constTrouserLeft];
var meshLegRight = characterMeshes[constLegRight];
var meshTrouserRight = characterMeshes[constTrouserRight];
var meshArmLeft = characterMeshes[constArmLeft];
var meshSleeveLeft = characterMeshes[constSleeveLeft];
var meshArmRight = characterMeshes[constArmRight];
var meshSleeveRight = characterMeshes[constSleeveRight];
var zOffsetLeg = constCharacterScale * areaLegRight.height/2;
var yOffsetLeg = constCharacterScale * (areaHead.height/2 + areaBody.height);
var zOffsetArm = constCharacterScale * areaArmRight.height/2;
var yOffsetArm = constCharacterScale * areaHead.height/2;
//Leg Swing
meshTrouserLeft.rotation.x = meshLegLeft.rotation.x = Math.cos(alpha*4);
meshTrouserLeft.position.z = meshLegLeft.position.z = -zOffsetLeg*Math.sin(meshLegLeft.rotation.x);
meshTrouserLeft.position.y = meshLegLeft.position.y = -yOffsetLeg - zOffsetLeg*Math.abs(Math.cos(meshLegLeft.rotation.x));
meshTrouserRight.rotation.x = meshLegRight.rotation.x = Math.cos(alpha*4 + (Math.PI));
meshTrouserRight.position.z = meshLegRight.position.z = -zOffsetLeg*Math.sin(meshLegRight.rotation.x);
meshTrouserRight.position.y = meshLegRight.position.y = -yOffsetLeg - zOffsetLeg*Math.abs(Math.cos(meshLegRight.rotation.x));
//Arm Swing
meshSleeveLeft.rotation.x = meshArmLeft.rotation.x = Math.cos(alpha*4 + (Math.PI));
meshSleeveLeft.position.z = meshArmLeft.position.z = -zOffsetArm*Math.sin(meshArmLeft.rotation.x);
meshSleeveLeft.position.y = meshArmLeft.position.y = -yOffsetArm - zOffsetArm*Math.abs(Math.cos(meshArmLeft.rotation.x));
meshSleeveRight.rotation.x = meshArmRight.rotation.x = Math.cos(alpha*4);
meshSleeveRight.position.z = meshArmRight.position.z = -zOffsetArm*Math.sin(meshArmRight.rotation.x);
meshSleeveRight.position.y = meshArmRight.position.y = -yOffsetArm - zOffsetArm*Math.abs(Math.cos(meshArmRight.rotation.x));
renderer.render(scene, camera);
}