248 lines
10 KiB
JavaScript
248 lines
10 KiB
JavaScript
// 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);
|
||
} |