package electrosphere.renderer.model; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.texture.Texture; import electrosphere.util.FileUtils; import org.lwjgl.PointerBuffer; import org.lwjgl.assimp.AIMaterial; import org.lwjgl.assimp.AIMaterialProperty; import org.lwjgl.assimp.AIScene; import org.lwjgl.assimp.AIString; import org.lwjgl.assimp.AITexture; import org.lwjgl.assimp.Assimp; import org.lwjgl.opengl.GL45; import java.io.File; import java.nio.IntBuffer; import java.nio.file.Path; /** * A material */ public class Material { /** * The path of the diffuse texture when using texture lookups */ private String diffuse; /** * The path of the specular texture when using texture lookups */ private String specular; /** * Tracks whether this material has transparency or not */ public boolean hasTransparency = false; /** * Sets whether this material should get its texture pointers from the assetManager by looking up diffuse and specular paths * or whether it should have a manually set texturePointer and not look up while binding */ private boolean usesFetch = true; /** * texture pointer for the specular */ private int texturePointer; /** * texture pointer for the normal */ private int normalPointer; /** * A material that contains textures */ public Material(){ } /** * Creates a material with a diffuse texture * @param diffuse The path to the diffuse texture */ public Material(String diffuse){ this.diffuse = diffuse; } /** * Loads materials from the ai material object * @param path The path to the file we're loading materials from * @param input The input scene * @param input The input ai material * @return The resulting engine material */ public static Material loadMaterialFromAIMaterial(String path, AIScene scene, AIMaterial input){ Material rVal = new Material(); AIString aiPathString = AIString.calloc(); //read props //read textures PointerBuffer texturePtrBuff = scene.mTextures(); String[] texPaths = new String[scene.mNumTextures()]; for(int i = 0; i < scene.mNumTextures(); i++){ AITexture tex = AITexture.create(texturePtrBuff.get()); texPaths[i] = tex.mFilename().dataString(); } //discover diffuse boolean foundDiffuse = false; int textureCount = Assimp.aiGetMaterialTextureCount(input, Assimp.aiTextureType_DIFFUSE); if(textureCount > 0){ //for the time being, only load the first diffuse int textureIndex = 0; int retCode = Assimp.aiGetMaterialTexture(input, Assimp.aiTextureType_DIFFUSE, textureIndex, aiPathString, (IntBuffer)null, null, null, null, null, null); if(retCode != Assimp.aiReturn_SUCCESS){ throw new Error("Failed to read diffuse! " + textureCount + " " + Assimp.aiGetErrorString()); } String texturePath = aiPathString.dataString(); if(texturePath == null || texturePath.length() <= 0){ throw new Error("Texture path is empty " + texturePath); } if(texturePath.length() == 2 && texturePath.startsWith("*")){ //older versions of Assimp require you to read the INDEX of the texture from the material, then look up that texture in the scene itself //format looks like "*" ie "*0" int indexInLoadedTexturePaths = Integer.parseInt(texturePath.substring(1)); if(indexInLoadedTexturePaths >= texPaths.length){ throw new Error("Index discovered is outside the array's length " + indexInLoadedTexturePaths + " " + texPaths.length); } String resolved = Material.resolveTexturePath(path, texPaths[indexInLoadedTexturePaths]); if(resolved != null && resolved.length() > 0){ rVal.usesFetch = true; rVal.diffuse = resolved; Globals.assetManager.addTexturePathtoQueue(rVal.diffuse); foundDiffuse = true; } } else { String resolved = Material.resolveTexturePath(path, texturePath); if(resolved != null && resolved.length() > 0){ rVal.usesFetch = true; rVal.diffuse = resolved; Globals.assetManager.addTexturePathtoQueue(rVal.diffuse); foundDiffuse = true; } } } if(!foundDiffuse){ textureCount = Assimp.aiGetMaterialTextureCount(input, Assimp.aiTextureType_BASE_COLOR); if(textureCount > 0){ //for the time being, only load the first diffuse int textureIndex = 0; int retCode = Assimp.aiGetMaterialTexture(input, Assimp.aiTextureType_BASE_COLOR, textureIndex, aiPathString, (IntBuffer)null, null, null, null, null, null); if(retCode != Assimp.aiReturn_SUCCESS){ throw new Error("Failed to read diffuse! " + textureCount + " " + Assimp.aiGetErrorString()); } String texturePath = aiPathString.dataString(); if(texturePath == null || texturePath.length() <= 0){ throw new Error("Texture path is empty " + texturePath); } if(texturePath.length() == 2 && texturePath.startsWith("*")){ //older versions of Assimp require you to read the INDEX of the texture from the material, then look up that texture in the scene itself //format looks like "*" ie "*0" int indexInLoadedTexturePaths = Integer.parseInt(texturePath.substring(1)); if(indexInLoadedTexturePaths >= texPaths.length){ throw new Error("Index discovered is outside the array's length " + indexInLoadedTexturePaths + " " + texPaths.length); } String resolved = Material.resolveTexturePath(path, texPaths[indexInLoadedTexturePaths]); if(resolved != null && resolved.length() > 0){ rVal.usesFetch = true; rVal.diffuse = resolved; Globals.assetManager.addTexturePathtoQueue(rVal.diffuse); foundDiffuse = true; } } else { String resolved = Material.resolveTexturePath(path, texturePath); if(resolved != null && resolved.length() > 0){ rVal.usesFetch = true; rVal.diffuse = resolved; Globals.assetManager.addTexturePathtoQueue(rVal.diffuse); foundDiffuse = true; } } } } //free mem aiPathString.free(); return rVal; } /** * Describes the properties of a material * @param material The material */ public static void listMaterialProps(AIMaterial material){ LoggerInterface.loggerRenderer.WARNING("Describing material"); for(int i = 0; i < material.mNumProperties(); i++){ AIMaterialProperty prop = AIMaterialProperty.create(material.mProperties().get(i)); String key = prop.mKey().dataString(); int propType = prop.mSemantic(); if(propType == Assimp.aiTextureType_NONE){ //non-texture prop LoggerInterface.loggerRenderer.WARNING("Prop \"" + key + "\" is not a texture"); } else { LoggerInterface.loggerRenderer.WARNING("Prop \"" + key + "\" is a texture"); } } } /** * Resolves the filepath of the texture * @param path The path of the ai scene itself * @param filename The name of the file * @return The full path to load */ private static String resolveTexturePath(String path, String filename){ File fileObj = FileUtils.getAssetFile(path); File parentDir = fileObj.getParentFile(); File[] contents = parentDir.listFiles(); File discovered = null; for(File child : contents){ String name = child.getName(); String nameNoExt = child.getName().replaceFirst("[.][^.]+$", ""); if(name.equals(filename) || nameNoExt.equals(filename)){ discovered = child; } } if(discovered == null){ LoggerInterface.loggerRenderer.WARNING("Failed to find texture \"" + filename + "\" for model " + path); return null; } else { Path relative = new File("./assets").toPath().relativize(discovered.toPath()); return relative.toString().replace("\\","/"); } } /** * Gets the path for the diffuse of the material * @return The path for the diffuse texture */ public String getDiffuse(){ return diffuse; } /** * Gets the path for the specular of the material * @return The path for the specular texture */ public String getSpecular(){ return specular; } /** * Sets the diffuse texture * @param t The texture path */ public void setDiffuse(String t){ diffuse = t; } /** * Sets the specular texture path * @param t The specular texture path */ public void setSpecular(String t){ specular = t; } /** * Sets the texture pointer * @param pointer The texture pointer */ public void setTexturePointer(int pointer){ texturePointer = pointer; usesFetch = false; } /** * Sets the in-material pointer for the normal * @param pointer the normal texture */ public void setNormalTexturePointer(int pointer){ normalPointer = pointer; usesFetch = false; } /** * Applies the material */ public void applyMaterial(OpenGLState openGLState){ //Controls whether the texturePointer should be resolved by looking up the diffuse in asset manager or using the texture pointer already set in this material if(usesFetch){ if(diffuse != null){ Texture diffuseTexture = Globals.assetManager.fetchTexture(diffuse); if(diffuseTexture != null){ diffuseTexture.bind(openGLState,0); Globals.renderingEngine.checkError(); openGLState.getActiveShader().setUniform(openGLState, "material.diffuse", 0); Globals.renderingEngine.checkError(); } } if(specular != null){ Texture specularTexture = Globals.assetManager.fetchTexture(specular); if(specularTexture != null){ specularTexture.bind(openGLState,1); Globals.renderingEngine.checkError(); openGLState.getActiveShader().setUniform(openGLState, "material.specular", 1); Globals.renderingEngine.checkError(); } } } else { openGLState.glBindTextureUnit(GL45.GL_TEXTURE0, texturePointer, GL45.GL_TEXTURE_2D); Globals.renderingEngine.checkError(); } } /** * Checks if this material has transparency * @return true if there is transparency, false otherwise */ public boolean isTransparent(){ boolean rVal = false; Texture diffuseTexture = Globals.assetManager.fetchTexture(diffuse); if(diffuseTexture != null && diffuseTexture.isTransparent()){ rVal = true; } Texture specularTexture = Globals.assetManager.fetchTexture(specular); if(specularTexture != null && specularTexture.isTransparent()){ rVal = true; } return rVal; } /** * Frees the material */ public void free(){ GL45.glDeleteTextures(new int[]{ this.texturePointer, this.normalPointer, }); } }