From bb089a7d36120c32a89de84aef2e031bee4cf89c Mon Sep 17 00:00:00 2001 From: austin Date: Wed, 26 Apr 2023 18:05:27 -0400 Subject: [PATCH] Terrain Chunk triplanar mapping --- assets/Shaders/terrain2/terrain2.fs | 208 ++++++++++++++++++ assets/Shaders/terrain2/terrain2.vs | 62 ++++++ assets/Textures/leavesStylized1.png | Bin 0 -> 42763 bytes .../java/electrosphere/engine/Globals.java | 5 + .../electrosphere/engine/LoadingThread.java | 12 +- .../engine/assetmanager/AssetDataStrings.java | 1 + .../entity/types/terrain/TerrainChunk.java | 7 +- .../renderer/RenderingEngine.java | 2 +- .../meshgen/TerrainChunkModelGeneration.java | 58 ++++- .../renderer/meshgen/TreeModelGeneration.java | 194 ++++++++++++++++ 10 files changed, 532 insertions(+), 17 deletions(-) create mode 100644 assets/Shaders/terrain2/terrain2.fs create mode 100644 assets/Shaders/terrain2/terrain2.vs create mode 100644 assets/Textures/leavesStylized1.png create mode 100644 src/main/java/electrosphere/renderer/meshgen/TreeModelGeneration.java diff --git a/assets/Shaders/terrain2/terrain2.fs b/assets/Shaders/terrain2/terrain2.fs new file mode 100644 index 00000000..1e59f1a4 --- /dev/null +++ b/assets/Shaders/terrain2/terrain2.fs @@ -0,0 +1,208 @@ +#version 330 core + +#define NR_POINT_LIGHTS 10 + +out vec4 FragColor; + + +layout (std140) uniform Lights { + // this is how many because we have to align + // bytes it SHOULD in multiples of 16, this + // take it where it ACTUALLY is + // + //refer: https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL + // + // base alignment aligned offset + //direct light + vec3 dLDirection; // 16 0 + vec3 dLAmbient; // 16 16 + vec3 dLDiffuse; // 16 32 + vec3 dLSpecular; // 16 48 + + //point light + vec3 pLposition[NR_POINT_LIGHTS]; // 16*10 64 + float pLconstant[NR_POINT_LIGHTS]; // 16*10 224 + float pLlinear[NR_POINT_LIGHTS]; // 16*10 384 + float pLquadratic[NR_POINT_LIGHTS]; // 16*10 544 + vec3 pLambient[NR_POINT_LIGHTS]; // 16*10 704 + vec3 pLdiffuse[NR_POINT_LIGHTS]; // 16*10 864 + vec3 pLspecular[NR_POINT_LIGHTS]; // 16*10 1024 + + //for a total size of 1184 + +}; + +struct Material { + sampler2D diffuse; + sampler2D specular; + float shininess; +}; + +in vec3 FragPos; +in vec3 Normal; +in vec2 texPlane1; +in vec2 texPlane2; +in vec2 texPlane3; +in vec4 FragPosLightSpace; + + +uniform vec3 viewPos; +// uniform DirLight dirLight; +// uniform PointLight pointLights[NR_POINT_LIGHTS]; +// uniform SpotLight spotLight; +uniform Material material; + +//texture stuff +// uniform sampler2D ourTexture; +uniform int hasTransparency; +// uniform sampler2D specularTexture; + +//light depth map +uniform sampler2D shadowMap; + + +// function prototypes +// vec3 CalcDirLight(vec3 normal, vec3 viewDir); +// vec3 CalcPointLight(int i, vec3 normal, vec3 fragPos, vec3 viewDir); +// vec3 CalcSpotLight(vec3 normal, vec3 fragPos, vec3 viewDir); +float calcLightIntensityTotal(vec3 normal); +float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir, vec3 normal); +vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material); + +void main(){ + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(viewPos - FragPos); + + //grab light intensity + float lightIntensity = calcLightIntensityTotal(norm); + + //get color of base texture + vec3 textureColor = getColor(texPlane1, texPlane2, texPlane3, norm, material); + + //shadow + float shadow = ShadowCalculation(FragPosLightSpace, normalize(-dLDirection), norm); + + //calculate final color + vec3 finalColor = textureColor * lightIntensity * max(shadow,0.4); + // vec3 lightAmount = CalcDirLight(norm, viewDir); + // for(int i = 0; i < NR_POINT_LIGHTS; i++){ + // lightAmount += CalcPointLight(i, norm, FragPos, viewDir); + // } + + //this final calculation is for transparency + FragColor = vec4(finalColor, 1); +} + + +vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material){ + + vec3 weights = abs(normal); + + vec3 albedoX = texture(material.diffuse, texPlane1).rgb; + vec3 albedoY = texture(material.diffuse, texPlane2).rgb; + vec3 albedoZ = texture(material.diffuse, texPlane3).rgb; + + + return (albedoX * weights.x + albedoY * weights.y + albedoZ * weights.z); +} + +// +float calcLightIntensityAmbient(){ + //calculate average of ambient light + float avg = (dLAmbient.x + dLAmbient.y + dLAmbient.z)/3.0; + return avg; +} + +// +float calcLightIntensityDir(vec3 normal){ + vec3 lightDir = normalize(-dLDirection); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + + return diff; +} + +// +float calcLightIntensityTotal(vec3 normal){ + //ambient intensity + float ambientLightIntensity = calcLightIntensityAmbient(); + + //get direct intensity + float directLightIntensity = calcLightIntensityDir(normal); + + //sum + float total = ambientLightIntensity + directLightIntensity; + return total; +} + +// +vec3 getTotalLightColor(vec3 normal){ + //get the direct light color adjusted for intensity + vec3 diffuseLightColor = dLDiffuse * calcLightIntensityDir(normal); + + //sum light colors + vec3 totalLightColor = diffuseLightColor; + return totalLightColor; +} + +vec3 CalcPointLight(int i, vec3 normal, vec3 fragPos, vec3 viewDir){ + vec3 lightDir = normalize(pLposition[i] - fragPos); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + // vec3 reflectDir = reflect(-lightDir, normal); + // float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + // attenuation + float distance = length(pLposition[i] - fragPos); + float attenuation = 1.0 / (pLconstant[i] + pLlinear[i] * distance + pLquadratic[i] * (distance * distance)); + // combine results + vec3 ambient = pLambient[i]; + vec3 diffuse = pLdiffuse[i] * diff; + ambient *= attenuation; + diffuse *= attenuation; + // specular *= attenuation; + vec3 specular = vec3(0,0,0); + + vec3 finalValue = (ambient + diffuse + specular); + finalValue = vec3(max(finalValue.x,0),max(finalValue.y,0),max(finalValue.z,0)); + + return finalValue; +} + + +float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir, vec3 normal){ + + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + + //transform to NDC + projCoords = projCoords * 0.5 + 0.5; + + //get closest depth from light's POV + float closestDepth = texture(shadowMap, projCoords.xy).r; + + //get depth of current fragment + float currentDepth = projCoords.z; + + //calculate bias + float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); + + //calculate shadow value + float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; + + if(projCoords.z > 1.0){ + shadow = 0.0; + } + + //calculate dot product, if it is >0 we know they're parallel-ish therefore should disregard the shadow mapping + //ie the fragment is already facing away from the light source + float dotprod = dot(normalize(lightDir),normalize(normal)); + + if(dotprod > 0.0){ + shadow = 0.0; + } + + // shadow = currentDepth; + + return shadow; +} \ No newline at end of file diff --git a/assets/Shaders/terrain2/terrain2.vs b/assets/Shaders/terrain2/terrain2.vs new file mode 100644 index 00000000..25f6219c --- /dev/null +++ b/assets/Shaders/terrain2/terrain2.vs @@ -0,0 +1,62 @@ +//Vertex Shader +#version 330 core + +//defines +#define TEXTURE_MAP_SCALE 3.0 + + +//input buffers +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 4) in vec2 aTex; + + +//coordinate space transformation matrices +uniform mat4 transform; +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform mat4 lightSpaceMatrix; + + + +//output buffers +out vec3 Normal; +out vec3 FragPos; +out vec2 texPlane1; +out vec2 texPlane2; +out vec2 texPlane3; +out vec4 FragPosLightSpace; + + + + +void main() { + //normalize posiiton and normal + vec4 FinalVertex = vec4(aPos, 1.0); + vec4 FinalNormal = vec4(aNormal, 1.0); + + + //push frag, normal, and texture positions to fragment shader + FragPos = vec3(model * FinalVertex); + Normal = mat3(transpose(inverse(model))) * aNormal; + + //reference https://catlikecoding.com/unity/tutorials/advanced-rendering/triplanar-mapping/ + texPlane1 = aPos.zy * TEXTURE_MAP_SCALE; + texPlane2 = aPos.xz * TEXTURE_MAP_SCALE; + texPlane3 = aPos.xy * TEXTURE_MAP_SCALE; + + //flip first coordinate if the normal is negative + //this minimizes texture flipping + texPlane1.x = texPlane1.x * sign(Normal.x); + texPlane2.x = texPlane2.x * sign(Normal.y); + texPlane3.x = texPlane3.x * sign(Normal.z); + + + //shadow map stuff + FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); + + + //set final position with opengl space + gl_Position = projection * view * model * FinalVertex; +} diff --git a/assets/Textures/leavesStylized1.png b/assets/Textures/leavesStylized1.png new file mode 100644 index 0000000000000000000000000000000000000000..d2d9589e3f82d0fbc154e5697f19fb0c83c5461c GIT binary patch literal 42763 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%0A+F))#@Fo_Z@i86G#*&majYxiQIF)6 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 literal 0 HcmV?d00001 diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 82c48a7e..a173e96e 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -45,6 +45,8 @@ import electrosphere.renderer.ShaderProgram; import electrosphere.renderer.light.PointLight; import electrosphere.renderer.light.SpotLight; import electrosphere.renderer.loading.ModelPretransforms; +import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; +import electrosphere.renderer.meshgen.TreeModelGeneration; import electrosphere.renderer.shader.ShaderOptionMap; import electrosphere.renderer.texture.TextureMap; import electrosphere.renderer.ui.ElementManager; @@ -408,8 +410,11 @@ public class Globals { defaultMeshShader = ShaderProgram.smart_assemble_shader(false,true); //init terrain shader program terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain/terrain.vs", "/Shaders/terrain/terrain.fs"); + TerrainChunkModelGeneration.terrainChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs"); //init skybox assetManager.registerModelToSpecificString(RenderUtils.createSkyboxModel(null), AssetDataStrings.ASSET_STRING_SKYBOX_BASIC); + //init leaves + assetManager.registerModelToSpecificString(TreeModelGeneration.generateLeavesModel(), AssetDataStrings.LEAVES_MODEL); //init models assetManager.addModelPathToQueue("Models/unitsphere.fbx"); assetManager.addModelPathToQueue("Models/unitsphere_1.fbx"); diff --git a/src/main/java/electrosphere/engine/LoadingThread.java b/src/main/java/electrosphere/engine/LoadingThread.java index 429924f8..d58379c3 100644 --- a/src/main/java/electrosphere/engine/LoadingThread.java +++ b/src/main/java/electrosphere/engine/LoadingThread.java @@ -13,6 +13,7 @@ import org.joml.Vector3f; import electrosphere.auth.AuthenticationManager; import electrosphere.controls.ControlHandler; +import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.Scene; @@ -44,6 +45,8 @@ import electrosphere.net.NetUtils; import electrosphere.net.client.ClientNetworking; import electrosphere.net.server.Server; import electrosphere.net.server.player.Player; +import electrosphere.renderer.Model; +import electrosphere.renderer.meshgen.TreeModelGeneration; import electrosphere.renderer.ui.Window; import electrosphere.server.ai.creature.adventurer.SeekTown; import electrosphere.server.datacell.DataCellManager; @@ -775,12 +778,17 @@ public class LoadingThread extends Thread { // myCube.putData(EntityDataStrings.DRAW_TRANSPARENT_PASS, true); // EntityUtils.getPosition(myCube).set(3,1,3); - SceneLoader loader = new SceneLoader(); - loader.serverInstantiateSceneFile("Scenes/testscene1/testscene1.json"); + // SceneLoader loader = new SceneLoader(); + // loader.serverInstantiateSceneFile("Scenes/testscene1/testscene1.json"); Entity chunk = TerrainChunk.createTerrainChunkEntity(); + Entity leaves = EntityUtils.spawnDrawableEntity(AssetDataStrings.LEAVES_MODEL); + EntityUtils.repositionEntity(leaves, new Vector3d(1,0,1)); + leaves.putData(EntityDataStrings.DRAW_SOLID_PASS, true); + leaves.putData(EntityDataStrings.DRAW_TRANSPARENT_PASS, true); + // Globals.entityManager.registerBehaviorTree(new BehaviorTree() { // int i = 0; // public void simulate(){ diff --git a/src/main/java/electrosphere/engine/assetmanager/AssetDataStrings.java b/src/main/java/electrosphere/engine/assetmanager/AssetDataStrings.java index f7a458d9..4bb302f3 100644 --- a/src/main/java/electrosphere/engine/assetmanager/AssetDataStrings.java +++ b/src/main/java/electrosphere/engine/assetmanager/AssetDataStrings.java @@ -8,4 +8,5 @@ public class AssetDataStrings { public static final String ASSET_STRING_BITMAP_FONT_MESH_NAME = "quad"; public static final String ASSET_STRING_SKYBOX_BASIC = "skyboxBasic"; public static final String BITMAP_CHARACTER_MODEL = "bitmapCharacterModel"; + public static final String LEAVES_MODEL = "leaves"; } diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index a778c3f9..24cecd13 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -5,6 +5,7 @@ import org.joml.Vector3f; import electrosphere.engine.Globals; import electrosphere.entity.Entity; +import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; import electrosphere.game.client.terrain.manager.ClientTerrainManager; import electrosphere.game.collision.PhysicsUtils; @@ -20,9 +21,9 @@ public class TerrainChunk { //plane 1 { //row 1 - {-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,}, + {1.0f,1.0f,-1.0f,-1.0f,-1.0f,}, //row 2 - {-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,}, + {-1.0f,1.0f,-1.0f,-1.0f,-1.0f,}, //row 3 {-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,}, //row 4 @@ -158,6 +159,8 @@ public class TerrainChunk { Entity rVal = EntityUtils.spawnDrawableEntityWithPreexistingModel(modelPath); PhysicsUtils.attachTerrainChunkRigidBody(rVal, data); + rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true); + EntityUtils.repositionEntity(rVal, new Vector3d(1,-1,1)); return rVal; diff --git a/src/main/java/electrosphere/renderer/RenderingEngine.java b/src/main/java/electrosphere/renderer/RenderingEngine.java index 18af5670..791dc052 100644 --- a/src/main/java/electrosphere/renderer/RenderingEngine.java +++ b/src/main/java/electrosphere/renderer/RenderingEngine.java @@ -1356,7 +1356,7 @@ public class RenderingEngine { public static void incrementOutputFramebuffer(){ outputFramebuffer++; - if(outputFramebuffer > 7){ + if(outputFramebuffer > 8){ outputFramebuffer = 0; } } diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java index bdff47c6..c2625c1e 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java @@ -8,9 +8,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.vecmath.Vector2f; import org.joml.Matrix4f; +import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.BufferUtils; @@ -24,6 +24,7 @@ import electrosphere.entity.types.terrain.TerrainChunkData; import electrosphere.renderer.Material; import electrosphere.renderer.Mesh; import electrosphere.renderer.Model; +import electrosphere.renderer.ShaderProgram; public class TerrainChunkModelGeneration { @@ -365,6 +366,26 @@ public class TerrainChunkModelGeneration { } } + + + + + + + public static ShaderProgram terrainChunkShaderProgram = null; + + + + + + + + + + + + + protected static int polygonize( GridCell grid, double isolevel, @@ -618,20 +639,33 @@ public class TerrainChunkModelGeneration { } float[] temp = new float[3]; + int i = 0; for(Vector3f normal : normals){ + Vector3f vert = verts.get(i); + float absX = Math.abs(normal.x); float absY = Math.abs(normal.y); float absZ = Math.abs(normal.z); - if(absX >= absZ && absX >= absY){ - temp[0] = normal.z / 2.0f + 0.5f; - temp[1] = normal.y / 2.0f + 0.5f; - } else if(absZ >= absX && absZ >= absY){ - temp[0] = normal.x / 2.0f + 0.5f; - temp[1] = normal.y / 2.0f + 0.5f; - } else if(absY >= absX && absY >= absZ){ - temp[0] = normal.x / 2.0f + 0.5f; - temp[1] = normal.z / 2.0f + 0.5f; - } + + float uvX = vert.z * absX + vert.x * absY + vert.x * absZ; + float uvY = vert.y * absX + vert.z * absY + vert.y * absZ; + temp[0] = uvX; + temp[1] = uvY; + + // if(absX >= absZ && absX >= absY){ + // temp[0] = normal.z / 2.0f + 0.5f + vert.z * (absX / (absX + absZ)) + vert.x * (absZ / (absX + absZ)); + // temp[1] = normal.y / 2.0f + 0.5f + vert.x * (absY / (absX + absY)) + vert.y * (absX / (absX + absY)); + // } else if(absZ >= absX && absZ >= absY){ + // temp[0] = normal.x / 2.0f + 0.5f + vert.z * (absX / (absX + absZ)) + vert.x * (absZ / (absX + absZ)); + // temp[1] = normal.y / 2.0f + 0.5f + vert.z * (absY / (absZ + absY)) + vert.y * (absZ / (absZ + absY)); + // } else if(absY >= absX && absY >= absZ){ + // temp[0] = normal.x / 2.0f + 0.5f + vert.y * (absX / (absX + absY)) + vert.x * (absY / (absX + absY)); + // temp[1] = normal.z / 2.0f + 0.5f + vert.y * (absZ / (absZ + absY)) + vert.z * (absY / (absZ + absY)); + // } else { + // temp[0] = vert.x / 1.5f + vert.z / 1.5f; + // temp[1] = vert.y / 1.5f + vert.z / 1.5f; + // } + i++; UVs.add(temp[0]); UVs.add(temp[1]); } @@ -758,7 +792,7 @@ public class TerrainChunkModelGeneration { groundMat.set_specular("/Textures/Ground/Dirt1.png"); m.setMaterial(groundMat); - m.setShader(Globals.defaultMeshShader); + m.setShader(TerrainChunkModelGeneration.terrainChunkShaderProgram); m.parent = rVal; rVal.meshes.add(m); diff --git a/src/main/java/electrosphere/renderer/meshgen/TreeModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TreeModelGeneration.java new file mode 100644 index 00000000..76c5589f --- /dev/null +++ b/src/main/java/electrosphere/renderer/meshgen/TreeModelGeneration.java @@ -0,0 +1,194 @@ +package electrosphere.renderer.meshgen; + +import electrosphere.engine.Globals; +import electrosphere.renderer.Material; +import electrosphere.renderer.Mesh; +import electrosphere.renderer.Model; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; +import static org.lwjgl.opengl.GL30.glGenVertexArrays; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; + +import org.lwjgl.BufferUtils; + +public class TreeModelGeneration { + + protected static Mesh generateLeafMesh(){ + + Mesh mesh = new Mesh(); + + + mesh.mesh = null; + + // + // VAO + // + mesh.vertexArrayObject = glGenVertexArrays(); + glBindVertexArray(mesh.vertexArrayObject); + + + + float[] vertices = new float[]{ + 0.5f, 0.5f, 0, + 0.5f, -0.5f, 0, + -0.5f, -0.5f, 0, + -0.5f, 0.5f, 0, + + + 0, 0.5f, 0.5f, + 0, -0.5f, 0.5f, + 0, -0.5f, -0.5f, + 0, 0.5f, -0.5f, + + + 0.5f, 0, 0.5f, + 0.5f, 0, -0.5f, + -0.5f, 0, -0.5f, + -0.5f, 0, 0.5f + }; + + float[] normals = new float[]{ + 0.707f, 0.707f, 0, + 0.707f, -0.707f, 0, + -0.707f, -0.707f, 0, + -0.707f, 0.707f, 0, + + 0, 0.707f, 0.707f, + 0, -0.707f, 0.707f, + 0, -0.707f, -0.707f, + 0, 0.707f, -0.707f, + + 0.707f, 0, 0.707f, + 0.707f, 0, -0.707f, + -0.707f, 0, -0.707f, + -0.707f, 0, 0.707f, + }; + + int[] elements = new int[]{ + 0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11 + }; + + float[] uvs = new float[]{ + 1, 1, + 1, 0, + 0, 0, + 0, 1, + + 1, 1, + 0, 1, + 0, 0, + 1, 0, + + 1, 1, + 1, 0, + 0, 0, + 0, 1, + }; + + + + + // + //Buffer data to GPU + // + + try { + mesh.vertexCount = vertices.length / 3; + FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(mesh.vertexCount * 3); + VertexArrayBufferData.put(vertices); + VertexArrayBufferData.flip(); + mesh.buffer_vertices(VertexArrayBufferData, 3); + } catch (NullPointerException ex){ + ex.printStackTrace(); + } + + + + // + // FACES + // + mesh.faceCount = elements.length / 3; + mesh.elementCount = elements.length; + try { + IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(mesh.elementCount); + elementArrayBufferData.put(elements); + elementArrayBufferData.flip(); + mesh.buffer_faces(elementArrayBufferData); + } catch (NullPointerException ex){ + ex.printStackTrace(); + } + + + + + // + // NORMALS + // + try { + mesh.normalCount = normals.length / 3; + FloatBuffer NormalArrayBufferData; + if(mesh.normalCount > 0){ + NormalArrayBufferData = BufferUtils.createFloatBuffer(mesh.normalCount * 3); + NormalArrayBufferData.put(normals); + NormalArrayBufferData.flip(); + mesh.buffer_normals(NormalArrayBufferData, 3); + } + } catch (NullPointerException ex){ + ex.printStackTrace(); + } + + // + // TEXTURE COORDINATES + // + try { + mesh.textureCoordCount = uvs.length / 2; + FloatBuffer TextureArrayBufferData; + if(mesh.textureCoordCount > 0){ + TextureArrayBufferData = BufferUtils.createFloatBuffer(mesh.textureCoordCount * 2); + TextureArrayBufferData.put(uvs); + TextureArrayBufferData.flip(); + mesh.buffer_texture_coords(TextureArrayBufferData, 2); + } + } catch (NullPointerException ex){ + ex.printStackTrace(); + } + + + + + glBindVertexArray(0); + mesh.nodeID = "leaves"; + return mesh; + } + + + + public static Model generateLeavesModel(){ + Model rVal = new Model(); + rVal.meshes = new ArrayList(); + Mesh m = generateLeafMesh(); + + + Material groundMat = new Material(); + groundMat.set_diffuse("/Textures/leavesStylized1.png"); + groundMat.set_specular("/Textures/leavesStylized1.png"); + Globals.assetManager.addTexturePathtoQueue("/Textures/leavesStylized1.png"); + m.setMaterial(groundMat); + + m.setShader(Globals.defaultMeshShader); + m.parent = rVal; + + rVal.meshes.add(m); + + return rVal; + } + +}