1 module evael.graphics.Terrain; 2 3 import std.exception : enforce; 4 import std.random : uniform; 5 import std.math; 6 7 import evael.graphics.GraphicsDevice; 8 import evael.graphics.Drawable; 9 import evael.graphics.shaders.BasicTerrainShader; 10 import evael.graphics.Vertex; 11 import evael.graphics.Texture; 12 import evael.graphics.TextureArray; 13 import evael.graphics.lights; 14 15 import evael.utils.Math; 16 import evael.utils.Color; 17 import evael.utils.Size; 18 19 /** 20 * Terrain 21 */ 22 class Terrain : Drawable 23 { 24 struct TerrainHeader 25 { 26 public Size!int size; 27 public int textureSplatting; 28 public int XZworldScaleFactor; 29 public float YworldScaleFactor; 30 } 31 32 /// Terrain configuration 33 private TerrainHeader m_header; 34 35 private Texture m_blendMapTexture; 36 private Texture m_shadowMapTexture; 37 private Texture m_normalMap; 38 39 /// Terrain textures 40 private TextureArray m_textures; 41 42 private uint m_trianglesNumber; 43 44 /** 45 * Terrain constructor. 46 * Params: 47 * graphicsDevice : 48 * header : terrain configuration 49 * textures : textures 50 */ 51 @nogc 52 public this()(GraphicsDevice graphicsDevice, in auto ref TerrainHeader header, TextureArray textures) nothrow 53 { 54 super(graphicsDevice); 55 this.m_header = header; 56 this.m_textures = textures; 57 } 58 59 /** 60 * Terrain destructor. 61 */ 62 @nogc 63 public void dispose() nothrow 64 { 65 this.m_textures.dispose(); 66 } 67 68 /** 69 * Initializes terrain. 70 */ 71 public void initialize() 72 { 73 TerrainVertex[] vertices = null; 74 75 immutable int halfWidth = (this.m_header.size.width / 2), halfHeight = (this.m_header.size.height / 2); 76 77 for (int z = -halfHeight; z < halfHeight; z++) 78 { 79 for (int x = -halfWidth; x < halfWidth; x++) 80 { 81 vertices ~= TerrainVertex( 82 vec3(x * this.m_header.XZworldScaleFactor, this.m_header.YworldScaleFactor, z * this.m_header.XZworldScaleFactor), 83 Color.White, 84 vec3(0, 0, 0), 85 vec2(cast(float)x / this.m_header.textureSplatting, cast(float)z / this.m_header.textureSplatting), 86 vec3(0, 0, 0), 87 vec3(0, 0, 0), 88 0, 89 0 90 ); 91 } 92 } 93 94 uint[] indices = null; 95 96 // Generating indices 97 for (int y = 0; y < this.m_header.size.height - 1 ; y++) 98 { 99 for (int x = 0; x < this.m_header.size.width - 1 ; x++) 100 { 101 immutable uint vertexIndex = (y * this.m_header.size.width) + x; 102 103 // Top triangle (T0) 104 immutable uint indice1 = vertexIndex; 105 immutable uint indice2 = vertexIndex + this.m_header.size.width + 1; 106 immutable uint indice3 = vertexIndex + 1; 107 108 // Bottom triangle (T1) 109 immutable uint indice4 = vertexIndex; 110 immutable uint indice5 = vertexIndex + this.m_header.size.width; 111 immutable uint indice6 = vertexIndex + this.m_header.size.width + 1; 112 113 indices ~= [indice1, indice2, indice3, indice4, indice5, indice6]; 114 } 115 } 116 117 // We compute normals 118 for (int i = 0; i < indices.length; i += 3) 119 { 120 vec3 p0 = vertices[indices[i+0]].position; 121 vec3 p1 = vertices[indices[i+1]].position; 122 vec3 p2 = vertices[indices[i+2]].position; 123 124 vec3 e1 = p1 - p0; 125 vec3 e2 = p2 - p0; 126 vec3 normal = cross(e1, e2); 127 normal.normalize(); 128 129 vertices[indices[i]].normal = vertices[indices[i]].normal + normal; 130 vertices[indices[i+1]].normal = vertices[indices[i+1]].normal + normal; 131 vertices[indices[i+2]].normal = vertices[indices[i+2]].normal + normal; 132 } 133 134 foreach (ref vertice; vertices) 135 { 136 vertice.normal.normalize(); 137 } 138 139 this.m_vao = this.m_graphicsDevice.generateVAO(); 140 this.m_vertexBuffer = this.m_graphicsDevice.createVertexBuffer(TerrainVertex.sizeof * vertices.length, vertices.ptr); 141 this.m_indexBuffer = this.m_graphicsDevice.createIndexBuffer(uint.sizeof * indices.length, indices.ptr); 142 143 this.m_graphicsDevice.setVertexBuffer!(TerrainVertex)(this.m_vertexBuffer); 144 this.m_graphicsDevice.bindVAO(0); 145 146 this.m_trianglesNumber = indices.length / 3; 147 } 148 149 public override void draw(in float deltaTime, mat4 view, mat4 projection) 150 { 151 } 152 153 public void drawWithShadow(in float deltaTime, mat4 view, mat4 projection, mat4 lightView, mat4 lightProjection) 154 { 155 mat4 model = mat4.identity; 156 157 this.m_graphicsDevice.enableShader(this.m_shader); 158 159 auto terrainShader = cast(BasicTerrainShader)this.m_shader; 160 161 gl.ActiveTexture(GL_TEXTURE3); 162 this.m_graphicsDevice.bindTexture(this.m_blendMapTexture); 163 gl.Uniform1i(terrainShader.blendMapLocation, 3); 164 165 gl.ActiveTexture(GL_TEXTURE2); 166 this.m_graphicsDevice.bindTexture(this.m_textures.id, TextureTarget.TextureArray); 167 gl.Uniform1i(terrainShader.terrainTexturesLocation, 2); 168 169 gl.ActiveTexture(GL_TEXTURE1); 170 this.m_graphicsDevice.bindTexture(this.m_shadowMapTexture); 171 gl.Uniform1i(terrainShader.shadowMapLocation, 1); 172 173 this.m_graphicsDevice.setMatrix(terrainShader.viewMatrix, view.arrayof.ptr); 174 this.m_graphicsDevice.setMatrix(terrainShader.modelMatrix, model.arrayof.ptr); 175 this.m_graphicsDevice.setMatrix(terrainShader.projectionMatrix, projection.arrayof.ptr); 176 this.m_graphicsDevice.setMatrix(terrainShader.lightViewMatrix, lightView.arrayof.ptr); 177 this.m_graphicsDevice.setMatrix(terrainShader.lightProjectionMatrix, lightProjection.arrayof.ptr); 178 179 static float[16] bias = 180 [ 181 0.5, 0.0, 0.0, 0.0, 182 0.0, 0.5, 0.0, 0.0, 183 0.0, 0.0, 0.5, 0.0, 184 0.5, 0.5, 0.5, 1.0 185 ]; 186 187 this.m_graphicsDevice.setMatrix(terrainShader.biasMatrix, bias.ptr, 1, false); 188 189 // Lighting 190 this.m_graphicsDevice.setEnvironment(); 191 192 this.m_graphicsDevice.bindVAO(this.m_vao); 193 this.m_graphicsDevice.drawIndexedPrimitives!(PrimitiveType.Triangle)(this.m_trianglesNumber); 194 this.m_graphicsDevice.bindVAO(0); 195 196 this.m_graphicsDevice.disableShader(); 197 198 gl.ActiveTexture(GL_TEXTURE0); 199 } 200 201 public void loadFromHeightmap(in string heightmap) 202 { 203 ubyte[] bytes = Texture.loadBytes(heightmap); 204 205 immutable int width = this.m_header.size.width; 206 immutable int height = this.m_header.size.height; 207 immutable int halfWidth = (this.m_header.size.width / 2); 208 immutable int halfHeight = (this.m_header.size.height / 2); 209 210 TerrainVertex[] vertices = null; 211 212 for (int z = -halfHeight; z < halfHeight; z++) 213 { 214 for (int x = -halfWidth; x < halfWidth; x++) 215 { 216 // bytes[(z * width + x) * 4]; 217 immutable int redIndex = ((z + halfHeight) * width + (x + halfWidth)) * 4; 218 219 // Free image is BGR(A) 220 immutable int blue = bytes[redIndex]; 221 immutable int green = bytes[redIndex + 1]; 222 immutable int red = bytes[redIndex + 2]; 223 224 immutable color = Color(blue, green, red); 225 float y = 0.0f; 226 int textureId = 0; 227 228 auto position = vec3(x * this.m_header.XZworldScaleFactor, y, z * this.m_header.XZworldScaleFactor); 229 230 vertices ~= TerrainVertex(position, 231 Color.White, 232 vec3(0, 0, 0), 233 vec2(cast(float)(x + halfWidth) / width, cast(float)(z + halfHeight) / height), 234 vec3(0, 0, 0), 235 vec3(0, 0, 0), 236 textureId, 237 1.0f); 238 } 239 } 240 241 uint[] indices = null; 242 243 // Generating indices 244 for (int y = 0; y < this.m_header.size.height - 1 ; y++) 245 { 246 for (int x = 0; x < this.m_header.size.width - 1 ; x++) 247 { 248 immutable uint vertexIndex = (y * this.m_header.size.width) + x; 249 250 // Top triangle (T0) 251 // ---- 252 // \ | 253 // \| 254 immutable uint indice1 = vertexIndex; 255 immutable uint indice2 = vertexIndex + this.m_header.size.width + 1; 256 immutable uint indice3 = vertexIndex + 1; 257 258 // Bottom triangle (T1) 259 // |\ 260 // | \ 261 // ---- 262 immutable uint indice4 = vertexIndex; 263 immutable uint indice5 = vertexIndex + this.m_header.size.width; 264 immutable uint indice6 = vertexIndex + this.m_header.size.width + 1; 265 266 indices ~= [indice1, indice2, indice3, indice4, indice5, indice6]; 267 } 268 } 269 270 // We compute normals 271 for (int i = 0; i < indices.length; i += 3) 272 { 273 vec3 p0 = vertices[indices[i+0]].position; 274 vec3 p1 = vertices[indices[i+1]].position; 275 vec3 p2 = vertices[indices[i+2]].position; 276 277 vec3 e1 = p1 - p0; 278 vec3 e2 = p2 - p0; 279 vec3 normal = cross(e1, e2); 280 normal.normalize(); 281 282 // Store the face's normal for each of the vertices that make up the face 283 vertices[indices[i]].normal = vertices[indices[i]].normal + normal ; 284 vertices[indices[i+1]].normal = vertices[indices[i+1]].normal + normal ; 285 vertices[indices[i+2]].normal = vertices[indices[i+2]].normal + normal ; 286 287 // Shortcuts for UVs 288 auto uv0 = vertices[indices[i+0]].textureCoordinate; 289 auto uv1 = vertices[indices[i+1]].textureCoordinate; 290 auto uv2 = vertices[indices[i+2]].textureCoordinate; 291 292 // Edges of the triangle : postion delta 293 vec3 deltaPos1 = p1-p0; 294 vec3 deltaPos2 = p2-p0; 295 296 // UV delta 297 vec2 deltaUV1 = uv1-uv0; 298 vec2 deltaUV2 = uv2-uv0; 299 300 immutable float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); 301 vec3 tangent; 302 303 tangent.x = r * (deltaPos1.x * deltaUV2.y - deltaPos2.x * deltaUV1.y); 304 tangent.y = r * (deltaPos1.y * deltaUV2.y - deltaPos2.y * deltaUV1.y); 305 tangent.z = r * (deltaPos1.z * deltaUV2.y - deltaPos2.z * deltaUV1.y); 306 tangent.normalize(); 307 308 vec3 bitangent; 309 bitangent.x = (deltaPos2.x * deltaUV1.x - deltaPos1.x * deltaUV2.x) *r; 310 bitangent.y = (deltaPos2.y * deltaUV1.x - deltaPos1.y * deltaUV2.x) *r; 311 bitangent.z = (deltaPos2.z * deltaUV1.x - deltaPos1.z * deltaUV2.x) *r; 312 bitangent.normalize(); 313 314 // Set the same tangent for all three vertices of the triangle 315 vertices[indices[i+0]].tangent = tangent; 316 vertices[indices[i+1]].tangent = tangent; 317 vertices[indices[i+2]].tangent = tangent; 318 319 // Same thing for binormals 320 vertices[indices[i+0]].bitangent = bitangent; 321 vertices[indices[i+1]].bitangent = bitangent; 322 vertices[indices[i+2]].bitangent = bitangent; 323 } 324 325 foreach (ref vertice; vertices) 326 { 327 vertice.normal.normalize(); 328 } 329 330 this.m_vao = this.m_graphicsDevice.generateVAO(); 331 this.m_vertexBuffer = this.m_graphicsDevice.createVertexBuffer(TerrainVertex.sizeof * vertices.length, vertices.ptr); 332 this.m_indexBuffer = this.m_graphicsDevice.createIndexBuffer(uint.sizeof * indices.length, indices.ptr); 333 334 this.m_graphicsDevice.setVertexBuffer!(TerrainVertex)(this.m_vertexBuffer); 335 336 this.m_graphicsDevice.bindVAO(0); 337 338 this.m_trianglesNumber = indices.length / 3; 339 } 340 341 @nogc 342 @property nothrow 343 { 344 public ref const(TerrainHeader) header() const 345 { 346 return this.m_header; 347 } 348 349 public void blendMapTexture(Texture value) 350 { 351 this.m_blendMapTexture = value; 352 } 353 354 public void shadowMapTexture(Texture value) 355 { 356 this.m_shadowMapTexture = value; 357 } 358 359 public void normalMap(Texture value) 360 { 361 this.m_normalMap = value; 362 } 363 } 364 }