package electrosphere.renderer; import electrosphere.renderer.actor.ActorAnimationMask; import electrosphere.renderer.actor.ActorBoneRotator; import electrosphere.renderer.actor.ActorMeshMask; import electrosphere.renderer.actor.ActorShaderMask; import electrosphere.renderer.actor.ActorStaticMorph; import electrosphere.renderer.actor.ActorTextureMask; import electrosphere.renderer.anim.AnimChannel; import electrosphere.renderer.anim.Animation; import electrosphere.renderer.loading.ModelPretransforms; import electrosphere.renderer.loading.ModelPretransforms.MeshMetadata; import electrosphere.renderer.anim.AnimNode; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import org.joml.Matrix4d; import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.BufferUtils; import org.lwjgl.PointerBuffer; import org.lwjgl.assimp.AIMaterial; import org.lwjgl.assimp.AIMesh; import org.lwjgl.assimp.AIScene; import static org.lwjgl.assimp.Assimp.*; import static org.lwjgl.opengl.ARBVertexBufferObject.*; import static org.lwjgl.opengl.ARBVertexProgram.*; import org.lwjgl.opengl.GL11; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL13.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL30.*; import electrosphere.renderer.texture.TextureMap; import java.nio.FloatBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Stack; import org.joml.Quaternionf; import org.joml.Vector3d; import org.lwjgl.assimp.AIAnimation; import org.lwjgl.assimp.AIBone; import org.lwjgl.assimp.AINode; /** * * @author satellite */ public class Model { boolean is_Ready_To_Display = false; AIScene scene; public List meshes; public ArrayList animations; public ArrayList bones; public HashMap boneMap; HashMap animMap; public HashMap node_map; public ArrayList materials; public Matrix4d modelMatrix = new Matrix4d(); ShaderProgram program; Matrix4d globalInverseTransform; AnimNode root_anim_node; ActorMeshMask meshMask; Map shaderMask = new HashMap(); Map textureMap = null; public static Model createModelFromAiscene(String path, AIScene s){ Model rVal = new Model(); // //set the scene // rVal.scene = s; ModelPretransforms.ModelMetadata modelMetadata = Globals.modelPretransforms.getModel(path); ModelPretransforms.GlobalTransform globalTransform = null; if(modelMetadata != null){ globalTransform = modelMetadata.getGlobalTransform(); } // //load meshes // int meshCount = s.mNumMeshes(); PointerBuffer meshesBuffer = s.mMeshes(); rVal.meshes = new ArrayList(); while(meshesBuffer.hasRemaining()){ AIMesh aiMesh = AIMesh.create(meshesBuffer.get()); ModelPretransforms.MeshMetadata meshMetadata = null; if(modelMetadata != null){ meshMetadata = modelMetadata.getMesh(aiMesh.mName().dataString()); } Mesh currentMesh = Mesh.create_mesh_from_aimesh(aiMesh, meshMetadata); rVal.meshes.add(currentMesh); currentMesh.parent = rVal; } // //register bones // rVal.bones = new ArrayList(); rVal.boneMap = new HashMap(); meshesBuffer.rewind(); 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(!rVal.boneMap.containsKey(currentBoneName)){ Bone boneObject = new Bone(currentBone); rVal.boneMap.put(currentBoneName, boneObject); rVal.bones.add(boneObject); } } } Iterator meshIterator = rVal.meshes.iterator(); rVal.bones = new ArrayList(); rVal.boneMap = new HashMap(); rVal.node_map = new HashMap(); while(meshIterator.hasNext()){ Mesh currentMesh = meshIterator.next(); Iterator boneIterator = currentMesh.bones.iterator(); ArrayList to_remove_queue = new ArrayList(); ArrayList to_add_queue = new ArrayList(); while(boneIterator.hasNext()){ Bone currentBone = boneIterator.next(); if(!rVal.boneMap.containsKey(currentBone.boneID)){ rVal.bones.add(currentBone); rVal.boneMap.put(currentBone.boneID, currentBone); } } boneIterator = to_remove_queue.iterator(); while(boneIterator.hasNext()){ currentMesh.bones.remove(boneIterator.next()); } boneIterator = to_add_queue.iterator(); while(boneIterator.hasNext()){ currentMesh.bones.add(boneIterator.next()); } } // //parse animation nodes and form hierarchy // AINode rootNode = s.mRootNode(); rVal.globalInverseTransform = electrosphere.util.Utilities.convertAIMatrixd(rootNode.mTransformation()); if(globalTransform != null){ rVal.globalInverseTransform.scale(globalTransform.getScale()); } // System.out.println("Global inverse transform"); // System.out.println(globalInverseTransform); rVal.root_anim_node = rVal.build_anim_node_map(s.mRootNode(),null); // //load animations // int animCount = s.mNumAnimations(); PointerBuffer animBuffer = s.mAnimations(); rVal.animations = new ArrayList(); rVal.animMap = new HashMap(); for(int i = 0; i < animCount; i++){ Animation newAnim = new Animation(AIAnimation.create(animBuffer.get(i)), i); rVal.animations.add(newAnim); rVal.animMap.put(newAnim.name,newAnim); } // //Load materials // if(s.mNumMaterials() > 0){ rVal.materials = new ArrayList(); PointerBuffer material_buffer = s.mMaterials(); while(material_buffer.hasRemaining()){ rVal.materials.add(Material.load_material_from_aimaterial(AIMaterial.create(material_buffer.get()))); } } //garbage collect System.gc(); return rVal; } public Model(){ /* public boolean is_Ready_To_Display = false; public AIScene scene; public ArrayList meshes; public ArrayList animations; public ArrayList bones; public HashMap boneMap; public HashMap node_map = new HashMap(); public ArrayList materials; public Matrix4f modelMatrix = new Matrix4f().translation(new Vector3f(0,0,0)); ShaderProgram program; public Animation currentAnimation; public Matrix4f globalInverseTransform; */ scene = null; meshes = null; animations = null; bones = null; boneMap = null; node_map = null; materials = null; modelMatrix = new Matrix4d(); program = null; globalInverseTransform = null; } public void free() { aiReleaseImport(scene); scene = null; meshes = null; } public void draw(RenderPipelineState renderPipelineState){ // if(node_map != null && !node_map.isEmpty()){ // update_node_transform(root_anim_node); // } Iterator mesh_Iterator = meshes.iterator(); while(mesh_Iterator.hasNext()){ Mesh currentMesh = mesh_Iterator.next(); if(meshMask == null || (meshMask != null && !meshMask.isBlockedMesh(currentMesh.nodeID))){ //set shader ShaderProgram original = currentMesh.shader; ShaderProgram shader = getCorrectShader(shaderMask, currentMesh, currentMesh.shader); currentMesh.shader = shader; //set texture mask if(this.textureMap != null && textureMap.containsKey(currentMesh.nodeID)){ currentMesh.setTextureMask(textureMap.get(currentMesh.nodeID)); } //draw currentMesh.complexDraw(renderPipelineState); //reset texture mask currentMesh.setTextureMask(null); //reset shader currentMesh.shader = original; } } if(meshMask != null){ for(Mesh toDraw : meshMask.getToDrawMeshes()){ toDraw.bones = bones; toDraw.parent = this; ShaderProgram original = toDraw.shader; ShaderProgram shader = getCorrectShader(shaderMask, toDraw, toDraw.shader); toDraw.shader = shader; toDraw.complexDraw(renderPipelineState); toDraw.shader = original; } } } ShaderProgram getCorrectShader(Map shaderMask, Mesh mesh, ShaderProgram oldShader){ ShaderProgram rVal = oldShader; if(shaderMask.containsKey(mesh.nodeID)){ ActorShaderMask specificMask = shaderMask.get(mesh.nodeID); ShaderProgram overwriteShader = null; if((overwriteShader = Globals.assetManager.fetchShader(specificMask.getVertexShaderPath(), specificMask.getGeometryShaderPath(), specificMask.getFragmentShaderPath())) != null){ // ShaderProgram oldProgram = mesh.shader; rVal = overwriteShader; } } return rVal; } // public void playAnimation(String s){ // this.currentAnimation = null; // Iterator animationIterator = animations.iterator(); // while(animationIterator.hasNext()){ // Animation current_iterator_item = animationIterator.next(); // if(current_iterator_item.name.equals(s)){ // this.currentAnimation = current_iterator_item; // } // } // if(currentAnimation != null){ // // if(s.contains("Walk")){ // // currentAnimation.describeAnimation(); // //// currentAnimation.fullDescribeAnimation(); // // System.exit(0); // // } // currentAnimation.timeCurrent = 0; // Iterator channelIterator = currentAnimation.channels.iterator(); // while(channelIterator.hasNext()){ // AnimChannel currentChannel = channelIterator.next(); // currentChannel.rewind(); // } // } // } public void applyAnimationMask(String animationName, double time, List 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 currentBone.deform = new Matrix4d(); currentBone.deform.translate(currentChannel.getCurrentPosition()); currentBone.deform.rotate(currentChannel.getCurrentRotation()); currentBone.deform.scale(new Vector3d(currentChannel.getCurrentScale())); } } } } // public void incrementTime(double time){ // if(currentAnimation != null){ // boolean isDone = currentAnimation.incrementTime(time); // if(isDone){ // currentAnimation.timeCurrent = 0; // Iterator channelIterator = currentAnimation.channels.iterator(); // while(channelIterator.hasNext()){ // AnimChannel currentChannel = channelIterator.next(); // currentChannel.rewind(); // } // Iterator boneIterator = bones.iterator(); // while(boneIterator.hasNext()){ // Bone currentBone = boneIterator.next(); // currentBone.transform = new Matrix4f(); // } // currentAnimation = null; // } else { // //First we push transformFromParent into deform so that later in the pipeline bones without a current animation take on transformFromParent as their transformation to the hierarchy // //I BELIEVE this is so that control bones still apply their offset to the hierarchy even when they're not animated :tm: // //4/5/20 // // Iterator boneIterator = bones.iterator(); // // while(boneIterator.hasNext()){ // // Bone currentBone = boneIterator.next(); // //// currentBone.deform = currentBone.transformFromParent; // // } // //Once that's done, for every channel we set the corresponding bone's deform to the channels TRS // Iterator channelIterator = currentAnimation.channels.iterator(); // while(channelIterator.hasNext()){ // AnimChannel currentChannel = channelIterator.next(); // currentChannel.incrementTime(time); // Bone currentBone = boneMap.get(currentChannel.getNodeID()); // if(currentBone != null){ // //T * S * R // currentBone.deform = new Matrix4f(); // currentBone.deform.translate(currentChannel.getCurrentPosition()); // currentBone.deform.rotate(currentChannel.getCurrentRotation()); // currentBone.deform.scale(currentChannel.getCurrentScale()); // } // } // } // } // } public void describeAllAnimations(){ if(animations.size() > 0){ System.out.println("====================="); System.out.println(animations.size() + " animations available in model!"); Iterator animIterator = animations.iterator(); while(animIterator.hasNext()){ Animation currentAnim = animIterator.next(); currentAnim.describeAnimation(); } } } public final AnimNode build_anim_node_map(AINode node, AnimNode parent){ AnimNode node_object = new AnimNode(node.mName().dataString(), parent, node); node_map.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 = build_anim_node_map(AINode.create(node.mChildren().get(i)),node_object); // if(boneMap.containsKey(node_object.id)){ // Bone parent_bone = boneMap.get(node_object.id); // node_object.children.add(temp_child); // if(boneMap.containsKey(temp_child.id)){ // Bone child_bone = boneMap.get(temp_child.id); // parent_bone.children.add(child_bone); // child_bone.parent = parent_bone; // } // } node_object.children.add(temp_child); } return node_object; } public void updateNodeTransform(Map boneRotators, ActorStaticMorph staticMorph){ if(this.root_anim_node != null){ update_node_transform(this.root_anim_node,boneRotators,staticMorph); } } void update_node_transform(AnimNode n, Map boneRotators, ActorStaticMorph staticMorph){ if(n.parent != null){ n.transform = new Matrix4d(n.parent.transform); } else { n.transform = new Matrix4d(); } if(n.is_bone){ /* Bone bone = boneList.get(j); Node node = rootNode.findByName(bone.getBoneName()); Matrix4f boneMatrix = Node.getParentTransforms(node, i); boneMatrix.mul(bone.getOffsetMatrix()); boneMatrix = new Matrix4f(rootTransformation).mul(boneMatrix); frame.setMatrix(j, boneMatrix); */ // //bone rotators (turrets, hair, etc) Bone target_bone = boneMap.get(n.id); n.transform = n.transform.mul(target_bone.deform); if(boneRotators.containsKey(target_bone.boneID)){ n.transform.rotate(boneRotators.get(target_bone.boneID).getRotation()); } // //static morph (changing nose size, eye distance, etc) Matrix4d bone_matrix = new Matrix4d(n.transform); if(staticMorph != null && staticMorph.getBoneTransforms(n.id) != null){ bone_matrix.mul(staticMorph.getBoneTransforms(n.id).getTransform()); n.transform.mul(staticMorph.getBoneTransforms(n.id).getTransform()); } // //Calculate final offset from initial bone bone_matrix.mul(target_bone.inverseBindPoseMatrix); bone_matrix = new Matrix4d(globalInverseTransform).mul(bone_matrix); target_bone.final_transform = bone_matrix; // if(!toggled){ // System.out.println(n.id); // System.out.println("Transform:"); // System.out.println(n.parent.transform); // System.out.println(target_bone.deform); // System.out.println(n.transform); // System.out.println("Final transform:"); // System.out.println(globalInverseTransform); // System.out.println(n.transform); // System.out.println(target_bone.inverseBindPoseMatrix); // System.out.println(target_bone.final_transform); // System.out.println("\n\n\n\n"); // } } else { n.transform = n.transform.mul(electrosphere.util.Utilities.convertAIMatrix(n.raw_data.mTransformation())); } Iterator node_iterator = n.children.iterator(); while(node_iterator.hasNext()){ AnimNode current_node = node_iterator.next(); update_node_transform(current_node,boneRotators,staticMorph); } } public void drawUI(){ for(Mesh m : meshes){ m.complexDraw(Globals.renderingEngine.getRenderPipelineState()); } } public void pushUniformToMesh(String meshName, String uniformKey, Object uniform){ for(Mesh m : meshes){ if(m.nodeID.equals(meshName)){ m.setUniform(uniformKey, uniform); } } } public void listAllBoneIDs(){ for(String id : boneMap.keySet()){ System.out.println(id); } } public Mesh getMesh(String meshName){ for(Mesh mesh : meshes){ if(mesh.nodeID.matches(meshName)){ return mesh; } } return null; } public Animation getAnimation(String animName){ return animMap.get(animName); } public Map getShaderMask(){ return shaderMask; } public void describeHighLevel(){ System.out.println("Meshes: "); for(Mesh mesh : meshes){ System.out.println(mesh.nodeID); } System.out.println("Animations: "); for(Animation anim : animations){ System.out.println(anim.name); } System.out.println("Bones:"); for(Bone bone : bones){ System.out.println(bone.boneID); } } public void setMeshMask(ActorMeshMask meshMask){ this.meshMask = meshMask; } public void setTextureMask(Map textureMask){ this.textureMap = textureMask; } /** * Sets the shader program to use drawing the modal * @param program The shader program */ public void setProgram(ShaderProgram program){ this.program = program; } /** * Utility method to attempt overwriting the model's meshes with a new material * @param material The material */ public void tryOverwriteMaterial(Material material){ for(Mesh mesh : meshes){ mesh.setMaterial(material); } } }