Renderer/src/main/java/electrosphere/renderer/model/Material.java
austin e151bdc34e
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
materials read some properties from assimp
2025-05-19 12:12:36 -04:00

386 lines
14 KiB
Java

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 org.lwjgl.system.MemoryStack;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.file.Path;
/**
* A material
*/
public class Material {
/**
* Default shininess
*/
public static final double DEFAULT_SHININESS = 0.0f;
/**
* The default reflectivity
*/
public static final double DEFAULT_REFLECTIVITY = 0.0f;
/**
* 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;
/**
* The shininess value
*/
private double shininess = Material.DEFAULT_SHININESS;
/**
* The reflectivity value
*/
private double reflectivity = Material.DEFAULT_REFLECTIVITY;
/**
* 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
try(MemoryStack stack = MemoryStack.stackPush()){
//grab specific values
rVal.reflectivity = Material.readFloatProp(stack, Assimp.AI_MATKEY_REFLECTIVITY, input, Material.DEFAULT_REFLECTIVITY);
rVal.shininess = Material.readFloatProp(stack, Assimp.AI_MATKEY_SHININESS, input, Material.DEFAULT_SHININESS);
}
//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 "*<index>" 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 "*<index>" 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;
}
/**
* Reads a float property from a material
* @param stack the memory stack
* @param key The key for the property
* @param input The input material
* @param defaultVal The default value
* @return The float value
*/
private static float readFloatProp(MemoryStack stack, String key, AIMaterial input, double defaultVal){
int numFloats = 1;
FloatBuffer buff = stack.callocFloat(numFloats);
IntBuffer floatCountBuff = stack.ints(numFloats);
ByteBuffer keyBuff = stack.ASCII(key);
if(Assimp.aiReturn_SUCCESS == Assimp.aiGetMaterialFloatArray(input, keyBuff, Assimp.aiTextureType_NONE, 0, buff, floatCountBuff)){
int numFloatsFound = floatCountBuff.get();
if(numFloatsFound > 0){
return buff.get();
}
}
return (float)defaultVal;
}
/**
* 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();
}
//send physical properties
openGLState.getActiveShader().setUniform(openGLState, "material.shininess", this.shininess);
Globals.renderingEngine.checkError();
openGLState.getActiveShader().setUniform(openGLState, "material.reflectivity", this.reflectivity);
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,
});
}
}