1 /*
2 Copyright (c) 2018-2022 Rafał Ziemniewski, Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.terrain;
29 
30 import dlib.core.memory;
31 import dlib.core.ownership;
32 import dlib.container.array;
33 import dlib.math.vector;
34 import dlib.geometry.sphere;
35 import dlib.geometry.triangle;
36 import dlib.image.color;
37 
38 import dagon.graphics.drawable;
39 import dagon.graphics.mesh;
40 import dagon.graphics.heightmap;
41 import dagon.graphics.entity;
42 import dagon.graphics.material;
43 import dagon.graphics.texture;
44 
45 class TerrainMaterial: Material
46 {
47     Array!Material layers;
48     
49     this(Owner o)
50     {
51         super(o);
52         alphaTestThreshold = 0.0f;
53     }
54     
55     Material addLayer()
56     {
57         Material layerMaterial = New!Material(this);
58         layerMaterial.alphaTestThreshold = alphaTestThreshold;
59         layers.append(layerMaterial);
60         return layerMaterial;
61     }
62     
63     // TODO: remove layer
64     
65     ~this()
66     {
67         layers.free();
68     }
69 }
70 
71 class Terrain: Owner, Drawable
72 {
73     uint width;
74     uint height;
75     Mesh mesh;
76     Mesh collisionMesh;
77     Heightmap heightmap;
78 
79     this(uint meshResolution, uint collisionMeshResolution, Heightmap heightmap, Owner owner)
80     {
81         super(owner);
82 
83         this.width = meshResolution;
84         this.height = meshResolution;
85         this.heightmap = heightmap;
86 
87         mesh = generateMesh(width, height, 1, owner);
88         mesh.dataReady = true;
89         mesh.prepareVAO();
90 
91         float scale = cast(float)meshResolution / collisionMeshResolution;
92         collisionMesh = generateMesh(collisionMeshResolution, collisionMeshResolution, scale, owner);
93     }
94 
95     this(uint meshResolution, Heightmap heightmap, Owner owner)
96     {
97         this(meshResolution, 80, heightmap, owner);
98     }
99 
100     Mesh generateMesh(uint w, uint h, float scale, Owner o)
101     {
102         Mesh mesh = New!Mesh(o);
103 
104         size_t numVerts = w * h;
105         mesh.vertices = New!(Vector3f[])(numVerts);
106         mesh.normals = New!(Vector3f[])(numVerts);
107         mesh.texcoords = New!(Vector2f[])(numVerts);
108         mesh.indices = New!(uint[3][])(numVerts * 2);
109 
110         int i = 0;
111         foreach(x; 0..w)
112         foreach(z; 0..h)
113         {
114             float y = heightmap.getHeight(
115                 cast(float)x / cast(float)(w-1),
116                 cast(float)z / cast(float)(h-1));
117             mesh.vertices[i] = Vector3f(x * scale, y, z * scale);
118             mesh.texcoords[i] = Vector2f(
119                 cast(float)x / cast(float)(w-1),
120                 cast(float)z / cast(float)(h-1));
121             i += 1;
122         }
123 
124         i = 0;
125         foreach(x; 0..w-1)
126         foreach(z; 0..h-1)
127         {
128             uint LU = x + z * w;
129             uint RU = x+1 + z * w;
130             uint LB = x + (z+1) * w;
131             uint RB = x+1 + (z+1) * w;
132 
133             mesh.indices[i] = [LU, RU, RB];
134             mesh.indices[i+1] = [LU, RB, LB];
135             i += 2;
136         }
137 
138         mesh.generateNormals();
139 
140         return mesh;
141     }
142 
143     void update(double dt)
144     {
145 
146     }
147 
148     void render(GraphicsState* state)
149     {
150         mesh.render(state);
151     }
152 
153     void refreshChanges()
154     {
155         mesh.generateNormals();
156         mesh.prepareVAO();
157     }
158     
159     float getHeight(Entity e, Vector3f pos)
160     {
161         Vector3f ts = (pos - e.position) / e.scaling;
162         float x = ts.x / width;
163         float z = ts.z / height;
164         float y = heightmap.getHeight(x, z);
165         return y * e.scaling.y;
166     }
167 
168     TerrainSphereTraverseAggregate traverseBySphere(Sphere* sphere)
169     {
170         return TerrainSphereTraverseAggregate(this, sphere);
171     }
172 }
173 
174 struct TerrainSphereTraverseAggregate
175 {
176     Terrain terrain;
177     Sphere* sphere;
178 
179     int opApply(int delegate(ref Triangle) dg)
180     {
181         int result = 0;
182 
183         uint x = 0;
184         uint y = 0;
185 
186         Vector3f c = sphere.center;
187         // TODO: transform c with position and scale?
188         if (c.x > terrain.width - 1) x = terrain.width - 1;
189         else if (c.x < 0) x = 0;
190         else x = cast(uint)c.x;
191 
192         if (c.z > terrain.height - 1) y = terrain.height - 1;
193         else if (c.z < 0) y = 0;
194         else y = cast(uint)c.z;
195 
196         Triangle tri = terrain.mesh.getTriangle(y * terrain.width + x);
197         tri.barycenter = (tri.v[0] + tri.v[1] + tri.v[2]) / 3;
198 
199         result = dg(tri);
200 
201         return result;
202     }
203 }
204 
205 bool entityIsTerrain(Entity e)
206 {
207     if (e.type == EntityType.Terrain)
208         return true;
209     
210     Drawable d = e.drawable;
211     if (d)
212     {
213         if (cast(Terrain)d)
214             return true;
215     }
216     return false;
217 }