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 }