1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:          $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/framework/terrain/impl.d)
6  * Documentation:
7  * Coverage:
8  * TODO:
9  *  - use texture_io to load png within a texture
10  *  - setTexCoordMultiplier += -=
11 **/
12 module liberty.framework.terrain.impl;
13 
14 import liberty.framework.terrain.vertex;
15 import liberty.graphics.shader;
16 import liberty.image.format.bmp;
17 import liberty.image.io;
18 import liberty.material.impl;
19 import liberty.math.functions;
20 import liberty.math.transform;
21 import liberty.math.vector;
22 import liberty.model.impl;
23 import liberty.model.io;
24 import liberty.scene.entity;
25 import liberty.scene.meta;
26 import liberty.scene.services;
27 
28 /**
29  *
30 **/
31 final class Terrain : Entity {
32   mixin NodeBody;
33 
34   private {
35     const float maxPixelColor = 256 ^^ 3;
36 
37     // getSize
38     float size;
39     // getMaxHeight
40     float maxHeight = 0;
41     float** heights;
42     int vertexCount;
43 
44     Material[] materials;
45     
46     // getTexCoordMultiplier, setTexCoordMultiplier
47     Vector2F texCoordMultiplier = Vector2F.one;
48 
49     Shader shader;
50   }
51 
52   /**
53    *
54   **/
55   this(string id) {
56     super(id);
57     register;
58 
59     shader = Shader
60       .getOrCreate("Terrain", (shader) {
61         shader
62           .addGlobalRenderMethod((program) {
63             program.loadUniform("uSkyColor", scene.world.getExpHeightFogColor);
64           })
65           .addPerEntityRenderMethod((program) {
66             program.loadUniform("uTexCoordMultiplier", getTexCoordMultiplier);
67           });
68       });
69     
70     shader.registerEntity(this);
71     scene.shaderMap[shader.id] = shader;
72   }
73 
74   ~this() {
75     import core.stdc.stdlib : free;
76     
77     foreach (i; 0..vertexCount)
78       free(heights[i]);
79 
80     free(heights);
81   }
82 
83   /**
84    *
85    * Returns reference to this so it can be used in a stream.
86   **/
87   typeof(this) build(float size, float maxHeight, Material[] materials) {
88     this.size = size;
89     this.maxHeight = maxHeight;
90     this.materials = materials;
91     
92     generateTerrain("res/textures/heightMap.bmp");
93     
94     component!Transform
95       .setLocation(-size / 2.0f, 0.0f, -size / 2.0f);
96     
97     texCoordMultiplier = size;
98 
99     return this;
100   }
101 
102   /**
103    *
104   **/
105   Vector2F getTexCoordMultiplier()   const {
106     return texCoordMultiplier;
107   }
108 
109   /**
110    *
111    * Returns reference to this so it can be used in a stream.
112   **/
113   typeof(this) setTexCoordMultiplier(Vector2F multiplier)   {
114     texCoordMultiplier = multiplier;
115     return this;
116   }
117 
118   /**
119    *
120    * Returns reference to this so it can be used in a stream.
121   **/
122   typeof(this) setTexCoordMultiplier(float x, float y)   {
123     texCoordMultiplier = Vector2F(x, y);
124     return this;
125   }
126 
127   /**
128    *
129    * Returns reference to this so it can be used in a stream.
130   **/
131   typeof(this) increaseTexCoordMultiplier(Vector2F multiplier)   {
132     texCoordMultiplier += multiplier;
133     return this;
134   }
135 
136   /**
137    *
138    * Returns reference to this so it can be used in a stream.
139   **/
140   typeof(this) increaseTexCoordMultiplier(float x, float y)   {
141     texCoordMultiplier += Vector2F(x, y);
142     return this;
143   }
144 
145   /**
146    *
147    * Returns reference to this so it can be used in a stream.
148   **/
149   typeof(this) decreaseTexCoordMultiplier(Vector2F multiplier)   {
150     texCoordMultiplier -= multiplier;
151     return this;
152   }
153 
154   /**
155    *
156    * Returns reference to this so it can be used in a stream.
157   **/
158   typeof(this) decreaseTexCoordMultiplier(float x, float y)   {
159     texCoordMultiplier -= Vector2F(x, y);
160     return this;
161   }
162 
163   /**
164    *
165   **/
166   float getHeight(float worldX, float worldZ) {
167     const float terrainX = worldX - component!Transform.getLocation.x;
168     const float terrainZ = worldZ - component!Transform.getLocation.z;
169 
170     const int heightLen = vertexCount - 1;
171     const float gridSqareSize = size / cast(float)heightLen;
172     
173     int gridX = cast(int)floor(terrainX / gridSqareSize);
174     int gridZ = cast(int)floor(terrainZ / gridSqareSize);
175 
176     if (gridX >= heightLen || gridZ >= heightLen || gridX < 0 || gridZ < 0)
177       return int.min;
178 
179     float xCoord = (terrainX % gridSqareSize) / gridSqareSize;
180     float zCoord = (terrainZ % gridSqareSize) / gridSqareSize;
181 
182     float finalPos;
183     if (xCoord <= (1 - zCoord)) {
184 			finalPos = barryCentric(
185         Vector3F(0, heights[gridX][gridZ], 0),
186         Vector3F(1, heights[gridX + 1][gridZ], 0),
187         Vector3F(0, heights[gridX][gridZ + 1], 1),
188         Vector2F(xCoord, zCoord)
189       );
190 		} else {
191 			finalPos = barryCentric(
192         Vector3F(1, heights[gridX + 1][gridZ], 0),
193         Vector3F(1, heights[gridX + 1][gridZ + 1], 1),
194         Vector3F(0, heights[gridX][gridZ + 1], 1),
195         Vector2F(xCoord, zCoord)
196       );
197 		}
198 
199     return finalPos;
200   }
201 
202   private void generateTerrain(string heightMapPath) {
203     import core.stdc.stdlib : malloc;
204 
205     // Load height map form file
206     auto image = cast(BMPImage)ImageIO.loadImage(heightMapPath);
207 
208     vertexCount = image.getHeight();
209     const int count = vertexCount * vertexCount;
210 
211     heights = cast(float**)malloc(vertexCount * (float*).sizeof);
212     foreach (i; 0..vertexCount)
213       heights[i] = cast(float*)malloc(vertexCount * float.sizeof);
214 
215     TerrainVertex[] vertices = new TerrainVertex[count * 3];
216     uint[] indices = new uint[6 * (vertexCount - 1) * (vertexCount - 1)];
217 
218     int vertexPtr;
219     for (int i; i < vertexCount; i++) {
220       for (int j; j < vertexCount; j++) {
221         const float height = getHeight(j, i, image);
222         heights[j][i] = height;
223 
224         vertices[vertexPtr].position.x = cast(float)j / (cast(float)vertexCount - 1) * size;
225         vertices[vertexPtr].position.y = height;
226         vertices[vertexPtr].position.z = cast(float)i / (cast(float)vertexCount - 1) * size;
227 
228         Vector3F normal = computeNormal(j, i, image);
229 
230         vertices[vertexPtr].normal.x = normal.x;
231         vertices[vertexPtr].normal.y = normal.y;
232         vertices[vertexPtr].normal.z = normal.z;
233         vertices[vertexPtr].texCoord.x = cast(float)j / (cast(float)vertexCount - 1);
234         vertices[vertexPtr].texCoord.y = cast(float)i / (cast(float)vertexCount - 1);
235         vertexPtr++;
236       }
237     }
238     int indexPtr;
239     for (int gz; gz < vertexCount - 1; gz++) {
240       for (int gx; gx < vertexCount - 1; gx++) {
241         const int topLeft = (gz * vertexCount) + gx;
242         const int topRight = topLeft + 1;
243         const int bottomLeft = ((gz + 1) * vertexCount) + gx;
244         const int bottomRight = bottomLeft + 1;
245         indices[indexPtr++] = topLeft;
246         indices[indexPtr++] = bottomLeft;
247         indices[indexPtr++] = topRight;
248         indices[indexPtr++] = topRight;
249         indices[indexPtr++] = bottomLeft;
250         indices[indexPtr++] = bottomRight;
251       }
252     }
253 
254     model = new Model(ModelIO.loadRawModel(vertices, indices), materials);
255   }
256 
257   private float getHeight(int x, int y, BMPImage image) {
258     if (x < 0 || x >= image.getHeight() || y < 0 || y >= image.getHeight())
259       return 0;
260 
261     float height = image.getRGBPixelColor(x, y);
262     height += maxPixelColor / 2.0f;
263     height /= maxPixelColor / 2.0f;
264     height *= maxHeight;
265 
266     return height;
267   }
268 
269   private Vector3F computeNormal(int x, int y, BMPImage image) {
270     const float heightL = getHeight(x - 1, y, image);
271     const float heightR = getHeight(x + 1, y, image);
272     const float heightD = getHeight(x, y - 1, image);
273     const float heightU = getHeight(x, y + 1, image);
274 
275     Vector3F normal = Vector3F(heightL - heightR, 2.0f, heightD - heightU);
276     normal.normalize();
277 
278     return normal;
279   }
280 
281   /**
282    *
283   **/
284   float getSize()   const {
285     return size;
286   }
287 
288   /**
289    *
290   **/
291   float getMaxHeight()   const {
292     return maxHeight;
293   }
294 }