-
Notifications
You must be signed in to change notification settings - Fork 4
The basic functions
Now that we know the how the bones work we can dive into the basic function of the app. The main function that does all the magic here is placeMesh()
:
function placeMesh(
meshName,
bodyPartClass,
MeshType,
parentAttachment,
childAttachment,
rotation,
firstLoad,
highLight,
bones,
poseData
) {
loader.load(
"models/" + bodyPartClass + "/" + meshName + ".glb",
gltf => {
var root = gltf.scene.children[0];
root.traverse(function(child) {
if (child instanceof THREE.Mesh) {
// colouring the mesh in a unified color throughout the customizer
child.castShadow = true;
child.material.color = { r: 0.5, g: 0.5, b: 0.5 };
}
});
group.add(root);
scene.updateMatrixWorld(true);
// updating the current value of loadedMeshes
loadedMeshes[MeshType].name = meshName;
loadedMeshes[MeshType].rotation = rotation;
if (MeshType === "Head" && firstLoad) {
changeColor("Head", color);
}
// if the mesh must be highlighted then we change the color to red
if (highLight) {
changeColor(MeshType, color);
}
// Putting the new mesh in the pose configuration if any pose as been selected
if (poseData) {
root.traverse(function(child) {
if (child instanceof THREE.Bone) {
if (poseData[child.name]) {
window.changeRotation(child.name, poseData[child.name].x, "x");
window.changeRotation(child.name, poseData[child.name].y, "y");
window.changeRotation(child.name, poseData[child.name].z, "z");
}
}
});
}
if (
typeof parentAttachment !== "undefined" &&
typeof childAttachment !== "undefined"
) {
//placing the mesh at the correct position
let targetBone = scene.getObjectByName(parentAttachment);
let object = scene.getObjectByName(childAttachment);
clearPosition(object);
rotateElement(object, true);
rotateElement(object, false, rotation);
targetBone.add(object);
}
//Going to look for all children of current mesh (recursive function)
let children = childrenList[MeshType];
if (children) {
for (let i = 0; i < children.length; i++) {
replaceMesh(children[i], firstLoad, bones, poseData);
}
}
if (MeshType === "FootR") {
if (scene.getObjectByName("FootL_Toes_L")) {
scene.updateMatrixWorld();
placeStand();
}
} else if (MeshType === "FootL") {
if (scene.getObjectByName("FootR_Toes_R")) {
scene.updateMatrixWorld();
placeStand();
}
}
window.partloaded = true;
},
null,
function(error) {
console.log(error);
}
);
}
It takes as argument the meshName
which is the name of the glb file to open, the bodyPartClass
to know in which directory we need to fetch the glb file, the MeshType
to know where to put the mesh and to which bones the mesh will be linked to the skeleton with parentAttachment
and childAttachment
, rotation
if the mesh must be loaded with a certain rotation, highlight
if the mesh is selected in the customizer and poseData
to put the current mesh in a certain position.
It firsts loads the glb file into the scene, it deals with changing the color to a unified gray or the highlight color. It updates the loadedMeshes value then changes the rotation of the bones within the placed mesh to the ones found in the poseData (which actually just is a list of rotation for each bones). It then deals with the position of the mesh, in order to position the mesh at the correct coordinates in space I just use the skinned mesh capabilities of Threejs to fetch the parent bone and add the child bone the it's children by using the command parentBone.add(childBone);
One of the great things about this function is that if we select a new torso then I want this function to place again all the children of the Torso (which means every mesh). I made this function recursive by looking at the children of the current mesh, if this mesh as children then it will then call placeMesh()
again on all the children via the function replaceMesh()
.
function replaceMesh(MeshType, firstLoad, bones, poseData) {
group.remove(group.getObjectByName(MeshType));
placeMesh(
loadedMeshes[MeshType].name,
meshStaticInfo[MeshType].bodyPart,
MeshType,
meshStaticInfo[MeshType].parentAttachment,
meshStaticInfo[MeshType].childAttachment,
loadedMeshes[MeshType].rotation,
firstLoad,
false,
bones,
poseData
);
}
This replaceMesh()
function is pretty simple it just delete the mesh and it's bones before placing again the same mesh with placeMesh()
.
changeMesh()
is a function that is global and can be called in a react component. It's the function that will delete the mesh and replace it with the one that was clicked on in the react UI. It uses placeMesh()
to place the new mesh onces the old mesh was deleted.
window.changeMesh = function(bodyPart, part, isLeft, bones, poseData) {
window.partloaded = false;
var meshType;
var file;
var rotation;
switch (bodyPart) {
case "torso":
file = part.file;
rotation = undefined;
meshType = "Torso";
break;
case "head":
file = part.file;
rotation = part.rotation;
meshType = "Head";
break;
case "hand":
meshType = (isLeft) ? "HandL" : "HandR";
file = (isLeft) ? part.file[0] : part.file[1];
rotation = (isLeft) ? part.rotation[0] : part.rotation[1];
break;
case "arm":
meshType = (isLeft) ? "ArmL" : "ArmR";
file = (isLeft) ? part.file[0] : part.file[1];
rotation = (isLeft) ? part.rotation[0] : part.rotation[1];
break;
case "foot":
meshType = (isLeft) ? "FootL" : "FootR";
file = (isLeft) ? part.file[0] : part.file[1];
rotation = (isLeft) ? part.rotation[0] : part.rotation[1];
break;
case "leg":
meshType = (isLeft) ? "LegL" : "LegR";
file = (isLeft) ? part.file[0] : part.file[1];
rotation = (isLeft) ? part.rotation[0] : part.rotation[1];
break;
default:
meshType = undefined;
}
if (meshType) {
let parentAttachment = meshStaticInfo[meshType].parentAttachment;
let childAttachment = meshStaticInfo[meshType].childAttachment;
let currentMesh = group.getObjectByName(meshType);
let bonesToDelete = (meshType === "Torso") ? scene.getObjectByName("Torso_Hip") : group.getObjectByName(parentAttachment);
if (currentMesh) {
group.remove(currentMesh);
if (bonesToDelete.children) {
for (let i=0; i < bonesToDelete.children.length; i++) {
if (bonesToDelete.children[i] instanceof THREE.Bone){
bonesToDelete.remove(bonesToDelete.children[i]);
}
}
}
placeMesh(
file,
bodyPart,
meshType,
parentAttachment,
childAttachment,
rotation,
false,
true,
bones,
poseData
);
}
}
return true;
}
This changeMesh()
function translates the information from the react UI and converts it to information that is understood by the function placeMesh()
and replaceMesh()
.
There are several other functions that are less important here and that will only mention:
The (global) function export()
exports the current configuration to an STL file. It also uses save()
, saveArrayBuffer()
and SaveString()
.
The (global) function selectedMesh()
is used to color a mesh when the category changes, it highlights the correct mesh.
The (global) function loadPose()
is used when the user selects a new pose in the UI. I goes throw every bone on screen and fetches the orientation in the selected pose.
The function placeStand()
is used when the stand is placed for the first time, it fetches the minimum value in the geometry of the character and will update the position of the character in order to position the feet to the stand at all times.
The (global) function changeStand()
is used when the user changes the stand with react. It removes the stand and places a new one at the correct position.
The (global) function loadDefaultMeshes()
is called when the UI is loaded and is called inside the ComponentDidMount of Selector.js. It basically loads all the defaults meshes on the screen.
MyMiniFactory | 2018