Renderer/src/main/java/electrosphere/server/poseactor/PoseModel.java
2025-01-24 17:34:49 -05:00

271 lines
9.9 KiB
Java

package electrosphere.server.poseactor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.joml.Matrix4d;
import org.joml.Vector3d;
import org.lwjgl.PointerBuffer;
import org.lwjgl.assimp.AIAnimation;
import org.lwjgl.assimp.AIBone;
import org.lwjgl.assimp.AIMesh;
import org.lwjgl.assimp.AINode;
import org.lwjgl.assimp.AIScene;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.actor.ActorBoneRotator;
import electrosphere.renderer.actor.ActorStaticMorph;
import electrosphere.renderer.anim.AnimChannel;
import electrosphere.renderer.anim.AnimNode;
import electrosphere.renderer.anim.Animation;
import electrosphere.renderer.loading.ModelPretransforms;
import electrosphere.renderer.model.Bone;
/**
* Used server side to load model data for positioning hitboxes based on animations
*/
public class PoseModel {
List<Bone> bones;
Map<String,Bone> boneMap;
Matrix4d globalInverseTransform;
Matrix4d rootTransform;
Map<String,AnimNode> nodeMap;
AnimNode rootAnimNode;
List<Animation> animations;
Map<String, Animation> animMap;
/**
* Private constructor for static creation methods
*/
private PoseModel(){
}
/**
* Constructor
* @param path Path on disk to this posemodel
* @param scene The AI Scene parsed from the file on disk
*/
public PoseModel(String path, AIScene scene){
ModelPretransforms.ModelMetadata modelMetadata = Globals.modelPretransforms.getModel(path);
ModelPretransforms.GlobalTransform globalTransform = null;
if(modelMetadata != null){
globalTransform = modelMetadata.getGlobalTransform();
}
bones = new ArrayList<Bone>();
boneMap = new HashMap<String, Bone>();
nodeMap = new HashMap<String, AnimNode>();
animations = new ArrayList<Animation>();
animMap = new HashMap<String, Animation>();
//
//parse bones
//
PointerBuffer meshesBuffer = scene.mMeshes();
while(meshesBuffer.hasRemaining()){
AIMesh currentMeshData = AIMesh.createSafe(meshesBuffer.get());
LoggerInterface.loggerRenderer.DEBUG("mesh name:" + currentMeshData.mName().dataString());
PointerBuffer boneBuffer = currentMeshData.mBones();
for(int j = 0; j < currentMeshData.mNumBones(); j++){
AIBone currentBone = AIBone.createSafe(boneBuffer.get());
String currentBoneName = currentBone.mName().dataString();
if(!boneMap.containsKey(currentBoneName)){
Bone boneObject = new Bone(currentBone);
boneMap.put(currentBoneName, boneObject);
bones.add(boneObject);
}
}
}
//
//parse animation nodes and form hierarchy
//
AINode rootNode = scene.mRootNode();
rootTransform = electrosphere.util.Utilities.convertAIMatrixd(rootNode.mTransformation());
if(globalTransform != null){
globalInverseTransform = new Matrix4d(rootTransform).invert().scale(globalTransform.getScale());
globalInverseTransform.scale(globalTransform.getScale());
} else {
globalInverseTransform = new Matrix4d();
}
rootAnimNode = buildAnimNodeMap(scene.mRootNode(),null);
//
//load animations
//
int animCount = scene.mNumAnimations();
PointerBuffer animBuffer = scene.mAnimations();
animations = new ArrayList<Animation>();
animMap = new HashMap<String,Animation>();
for(int i = 0; i < animCount; i++){
Animation newAnim = new Animation(AIAnimation.create(animBuffer.get(i)));
animations.add(newAnim);
animMap.put(newAnim.name,newAnim);
}
}
/**
* Constructor
* @param path Path on disk to this posemodel
* @param scene The AI Scene parsed from the file on disk
*/
public static PoseModel createEmpty(){
PoseModel rVal = new PoseModel();
rVal.bones = new ArrayList<Bone>();
rVal.boneMap = new HashMap<String, Bone>();
rVal.nodeMap = new HashMap<String, AnimNode>();
rVal.animations = new ArrayList<Animation>();
rVal.animMap = new HashMap<String, Animation>();
return rVal;
}
/**
* Applies an animation to a certain set of bones
* @param animationName The name of the animation
* @param time The time in the animation to apply
* @param mask The set of bones to apply the animation to
*/
public void applyAnimationMask(String animationName, double time, List<String> mask){
Animation animationCurrent = animMap.get(animationName);
if(animationCurrent != null){
for(String boneName : mask){
AnimChannel currentChannel = animationCurrent.getChannel(boneName);
Bone currentBone = boneMap.get(currentChannel.getNodeID());
currentChannel.setTime(time);
// System.out.println(currentChannel + " " + currentBone);
if(currentBone != null){
// System.out.println("Applying to bone");
//T * S * R
Matrix4d deform = new Matrix4d();
deform.translate(currentChannel.getCurrentPosition());
deform.rotate(currentChannel.getCurrentRotation());
deform.scale(new Vector3d(currentChannel.getCurrentScale()));
currentBone.setDeform(deform);
}
}
}
}
/**
* Builds an AnimNode map based on an AINode from assimp
* @param node The AINode from assimp
* @param parent The parent AnimNode if it exists
* @return The AnimNode map relative to this node
*/
public final AnimNode buildAnimNodeMap(AINode node, AnimNode parent){
AnimNode node_object = new AnimNode(node.mName().dataString(), parent, node);
nodeMap.put(node_object.id, node_object);
if(boneMap.containsKey(node_object.id)){
node_object.is_bone = true;
}
int num_children = node.mNumChildren();
for(int i = 0; i < num_children; i++){
AnimNode temp_child = buildAnimNodeMap(AINode.create(node.mChildren().get(i)),node_object);
node_object.children.add(temp_child);
}
return node_object;
}
/**
* Updates node transforms based on bone rotators and static morph
* @param boneRotators The bone rotators
* @param staticMorph The static morph
*/
public void updateNodeTransform(Map<String,ActorBoneRotator> boneRotators, ActorStaticMorph staticMorph){
if(this.rootAnimNode != null){
updateNodeTransform(this.rootAnimNode,boneRotators,staticMorph);
}
}
/**
* Gets the map of bone name -> bone
* @return the map
*/
public Map<String,Bone> getBoneMap(){
return boneMap;
}
/**
* Internal recursive method behind the public updateNodeTransform
* @param boneRotators The bone rotators
* @param staticMorph The static morph
*/
void updateNodeTransform(AnimNode n, Map<String,ActorBoneRotator> boneRotators, ActorStaticMorph staticMorph){
//grab parent transform if exists
Matrix4d parentTransform = new Matrix4d();
if(n.parent != null){
parentTransform = new Matrix4d(n.parent.getTransform());
}
//if this is a bone, calculate the transform for the bone
if(n.is_bone){
Bone target_bone = boneMap.get(n.id);
Matrix4d deformTransform = parentTransform.mul(target_bone.getDeform());
if(boneRotators.containsKey(target_bone.boneID)){
deformTransform.rotate(boneRotators.get(target_bone.boneID).getRotation());
}
Matrix4d bone_matrix = new Matrix4d(deformTransform);
if(staticMorph != null && staticMorph.getBoneTransforms(n.id) != null){
bone_matrix.mul(staticMorph.getBoneTransforms(n.id).getTransform());
}
n.setTransform(deformTransform);
//
//Calculate final offset from initial bone
//https://stackoverflow.com/a/59869381
bone_matrix.mul(target_bone.getMOffset());
bone_matrix = new Matrix4d(globalInverseTransform).mul(bone_matrix);
target_bone.setFinalTransform(bone_matrix);
} else {
//not a bone, so use transform directly from data
n.setTransform(parentTransform.mul(electrosphere.util.Utilities.convertAIMatrix(n.raw_data.mTransformation())));
}
//update all children accordingly
Iterator<AnimNode> node_iterator = n.children.iterator();
while(node_iterator.hasNext()){
AnimNode current_node = node_iterator.next();
updateNodeTransform(current_node,boneRotators,staticMorph);
}
}
/**
* Gets an animation with a specific name
* @param animName The name of the animation
* @return The animation if it exists, null otherwise
*/
public Animation getAnimation(String animName){
return animMap.get(animName);
}
/**
* Logs all animations for a given model
*/
public void describeAllAnimations(){
if(animations.size() > 0){
LoggerInterface.loggerRenderer.DEBUG("=====================");
LoggerInterface.loggerRenderer.DEBUG(animations.size() + " animations available in model!");
Iterator<Animation> animIterator = animations.iterator();
while(animIterator.hasNext()){
Animation currentAnim = animIterator.next();
currentAnim.describeAnimation();
}
}
}
/**
* Gets the list of bones in this pose model
* @return The list of bones
*/
public List<Bone> getBones(){
return this.bones;
}
}