Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "MessageHandler.h"
21 :
22 : #include "../CommandProc.h"
23 :
24 : #include "graphics/RenderableObject.h"
25 : #include "graphics/Terrain.h"
26 : #include "graphics/UnitManager.h"
27 : #include "ps/CStr.h"
28 : #include "ps/Game.h"
29 : #include "ps/World.h"
30 : #include "maths/MathUtil.h"
31 : #include "simulation2/Simulation2.h"
32 : #include "simulation2/components/ICmpTerrain.h"
33 :
34 : #include "../Brushes.h"
35 : #include "../DeltaArray.h"
36 :
37 : namespace AtlasMessage {
38 :
39 0 : class TerrainArray : public DeltaArray2D<u16>
40 : {
41 : public:
42 0 : void Init()
43 : {
44 0 : m_Heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap();
45 0 : m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
46 0 : }
47 :
48 0 : void RaiseVertex(ssize_t x, ssize_t y, int amount)
49 : {
50 : // Ignore out-of-bounds vertices
51 0 : if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
52 0 : return;
53 :
54 0 : set(x, y, static_cast<u16>(Clamp(get(x,y) + amount, 0, 65535)));
55 : }
56 :
57 0 : void MoveVertexTowards(ssize_t x, ssize_t y, int target, int amount)
58 : {
59 0 : if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
60 0 : return;
61 :
62 0 : int h = get(x,y);
63 0 : if (h < target)
64 0 : h = std::min(target, h + amount);
65 0 : else if (h > target)
66 0 : h = std::max(target, h - amount);
67 : else
68 0 : return;
69 :
70 0 : set(x, y, static_cast<u16>(Clamp(h, 0, 65535)));
71 : }
72 :
73 : void SetVertex(ssize_t x, ssize_t y, u16 value)
74 : {
75 : if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
76 : return;
77 :
78 : set(x,y, value);
79 : }
80 :
81 0 : u16 GetVertex(ssize_t x, ssize_t y)
82 : {
83 0 : return get(Clamp<ssize_t>(x, 0, m_VertsPerSide - 1), Clamp<ssize_t>(y, 0, m_VertsPerSide - 1));
84 : }
85 :
86 : protected:
87 0 : u16 getOld(ssize_t x, ssize_t y)
88 : {
89 0 : return m_Heightmap[y*m_VertsPerSide + x];
90 : }
91 0 : void setNew(ssize_t x, ssize_t y, const u16& val)
92 : {
93 0 : m_Heightmap[y*m_VertsPerSide + x] = val;
94 0 : }
95 :
96 : u16* m_Heightmap;
97 : ssize_t m_VertsPerSide;
98 : };
99 :
100 : //////////////////////////////////////////////////////////////////////////
101 :
102 0 : BEGIN_COMMAND(AlterElevation)
103 : {
104 : TerrainArray m_TerrainDelta;
105 : ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
106 :
107 0 : cAlterElevation()
108 0 : {
109 0 : m_TerrainDelta.Init();
110 0 : }
111 :
112 0 : void MakeDirty()
113 : {
114 0 : g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
115 0 : g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
116 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
117 0 : if (cmpTerrain)
118 0 : cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
119 0 : }
120 :
121 0 : void Do()
122 : {
123 0 : int amount = (int)msg->amount;
124 :
125 : // If the framerate is very high, 'amount' is often very
126 : // small (even zero) so the integer truncation is significant
127 : static float roundingError = 0.0;
128 0 : roundingError += msg->amount - (float)amount;
129 0 : if (roundingError >= 1.f)
130 : {
131 0 : amount += (int)roundingError;
132 0 : roundingError -= (float)(int)roundingError;
133 : }
134 :
135 0 : static CVector3D previousPosition;
136 0 : g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
137 0 : previousPosition = g_CurrentBrush.m_Centre;
138 :
139 : ssize_t x0, y0;
140 0 : g_CurrentBrush.GetBottomLeft(x0, y0);
141 :
142 0 : for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
143 : {
144 0 : for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
145 : {
146 : // TODO: proper variable raise amount (store floats in terrain delta array?)
147 0 : float b = g_CurrentBrush.Get(dx, dy);
148 0 : if (b)
149 0 : m_TerrainDelta.RaiseVertex(x0+dx, y0+dy, (int)(amount*b));
150 : }
151 : }
152 :
153 0 : m_i0 = x0 - 1;
154 0 : m_j0 = y0 - 1;
155 0 : m_i1 = x0 + g_CurrentBrush.m_W;
156 0 : m_j1 = y0 + g_CurrentBrush.m_H;
157 0 : MakeDirty();
158 0 : }
159 :
160 0 : void Undo()
161 : {
162 0 : m_TerrainDelta.Undo();
163 0 : MakeDirty();
164 0 : }
165 :
166 0 : void Redo()
167 : {
168 0 : m_TerrainDelta.Redo();
169 0 : MakeDirty();
170 0 : }
171 :
172 0 : void MergeIntoPrevious(cAlterElevation* prev)
173 : {
174 0 : prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
175 0 : prev->m_i0 = std::min(prev->m_i0, m_i0);
176 0 : prev->m_j0 = std::min(prev->m_j0, m_j0);
177 0 : prev->m_i1 = std::max(prev->m_i1, m_i1);
178 0 : prev->m_j1 = std::max(prev->m_j1, m_j1);
179 0 : }
180 : };
181 0 : END_COMMAND(AlterElevation)
182 :
183 : //////////////////////////////////////////////////////////////////////////
184 :
185 0 : BEGIN_COMMAND(SmoothElevation)
186 : {
187 : TerrainArray m_TerrainDelta;
188 : ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
189 :
190 0 : cSmoothElevation()
191 0 : {
192 0 : m_TerrainDelta.Init();
193 0 : }
194 :
195 0 : void MakeDirty()
196 : {
197 0 : g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
198 0 : g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
199 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
200 0 : if (cmpTerrain)
201 0 : cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
202 0 : }
203 :
204 0 : void Do()
205 : {
206 0 : int amount = (int)msg->amount;
207 :
208 : // If the framerate is very high, 'amount' is often very
209 : // small (even zero) so the integer truncation is significant
210 : static float roundingError = 0.0;
211 0 : roundingError += msg->amount - (float)amount;
212 0 : if (roundingError >= 1.f)
213 : {
214 0 : amount += (int)roundingError;
215 0 : roundingError -= (float)(int)roundingError;
216 : }
217 :
218 0 : static CVector3D previousPosition;
219 0 : g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
220 0 : previousPosition = g_CurrentBrush.m_Centre;
221 :
222 : ssize_t x0, y0;
223 0 : g_CurrentBrush.GetBottomLeft(x0, y0);
224 :
225 0 : if (g_CurrentBrush.m_H > 2)
226 : {
227 0 : std::vector<float> terrainDeltas;
228 0 : ssize_t num = (g_CurrentBrush.m_H - 2) * (g_CurrentBrush.m_W - 2);
229 0 : terrainDeltas.resize(num);
230 :
231 : // For each vertex, compute the average of the 9 adjacent vertices
232 0 : for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
233 : {
234 0 : for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
235 : {
236 0 : float delta = m_TerrainDelta.GetVertex(x0+dx, y0+dy) / 9.0f;
237 0 : ssize_t x1_min = std::max((ssize_t)1, dx - 1);
238 0 : ssize_t x1_max = std::min(dx + 1, g_CurrentBrush.m_W - 2);
239 0 : ssize_t y1_min = std::max((ssize_t)1, dy - 1);
240 0 : ssize_t y1_max = std::min(dy + 1, g_CurrentBrush.m_H - 2);
241 :
242 0 : for (ssize_t yy = y1_min; yy <= y1_max; ++yy)
243 : {
244 0 : for (ssize_t xx = x1_min; xx <= x1_max; ++xx)
245 : {
246 0 : ssize_t index = (yy-1)*(g_CurrentBrush.m_W-2) + (xx-1);
247 0 : terrainDeltas[index] += delta;
248 : }
249 : }
250 : }
251 : }
252 :
253 : // Move each vertex towards the computed average of its neighbours
254 0 : for (ssize_t dy = 1; dy < g_CurrentBrush.m_H - 1; ++dy)
255 : {
256 0 : for (ssize_t dx = 1; dx < g_CurrentBrush.m_W - 1; ++dx)
257 : {
258 0 : ssize_t index = (dy-1)*(g_CurrentBrush.m_W-2) + (dx-1);
259 0 : float b = g_CurrentBrush.Get(dx, dy);
260 0 : if (b)
261 0 : m_TerrainDelta.MoveVertexTowards(x0+dx, y0+dy, (int)terrainDeltas[index], (int)(amount*b));
262 : }
263 : }
264 : }
265 :
266 0 : m_i0 = x0;
267 0 : m_j0 = y0;
268 0 : m_i1 = x0 + g_CurrentBrush.m_W - 1;
269 0 : m_j1 = y0 + g_CurrentBrush.m_H - 1;
270 0 : MakeDirty();
271 0 : }
272 :
273 0 : void Undo()
274 : {
275 0 : m_TerrainDelta.Undo();
276 0 : MakeDirty();
277 0 : }
278 :
279 0 : void Redo()
280 : {
281 0 : m_TerrainDelta.Redo();
282 0 : MakeDirty();
283 0 : }
284 :
285 0 : void MergeIntoPrevious(cSmoothElevation* prev)
286 : {
287 0 : prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
288 0 : prev->m_i0 = std::min(prev->m_i0, m_i0);
289 0 : prev->m_j0 = std::min(prev->m_j0, m_j0);
290 0 : prev->m_i1 = std::max(prev->m_i1, m_i1);
291 0 : prev->m_j1 = std::max(prev->m_j1, m_j1);
292 0 : }
293 : };
294 0 : END_COMMAND(SmoothElevation)
295 :
296 : //////////////////////////////////////////////////////////////////////////
297 :
298 0 : BEGIN_COMMAND(FlattenElevation)
299 : {
300 : TerrainArray m_TerrainDelta;
301 : ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
302 :
303 0 : cFlattenElevation()
304 0 : {
305 0 : m_TerrainDelta.Init();
306 0 : }
307 :
308 0 : void MakeDirty()
309 : {
310 0 : g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
311 0 : g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
312 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
313 0 : if (cmpTerrain)
314 0 : cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
315 0 : }
316 :
317 0 : void Do()
318 : {
319 0 : int amount = (int)msg->amount;
320 :
321 0 : static CVector3D previousPosition;
322 0 : g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
323 0 : previousPosition = g_CurrentBrush.m_Centre;
324 :
325 : ssize_t xc, yc;
326 0 : g_CurrentBrush.GetCentre(xc, yc);
327 0 : u16 height = m_TerrainDelta.GetVertex(xc, yc);
328 :
329 : ssize_t x0, y0;
330 0 : g_CurrentBrush.GetBottomLeft(x0, y0);
331 :
332 0 : for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
333 : {
334 0 : for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
335 : {
336 0 : float b = g_CurrentBrush.Get(dx, dy);
337 0 : if (b)
338 0 : m_TerrainDelta.MoveVertexTowards(x0+dx, y0+dy, height, 1 + (int)(b*amount));
339 : }
340 : }
341 :
342 0 : m_i0 = x0 - 1;
343 0 : m_j0 = y0 - 1;
344 0 : m_i1 = x0 + g_CurrentBrush.m_W;
345 0 : m_j1 = y0 + g_CurrentBrush.m_H;
346 0 : MakeDirty();
347 0 : }
348 :
349 0 : void Undo()
350 : {
351 0 : m_TerrainDelta.Undo();
352 0 : MakeDirty();
353 0 : }
354 :
355 0 : void Redo()
356 : {
357 0 : m_TerrainDelta.Redo();
358 0 : MakeDirty();
359 0 : }
360 :
361 0 : void MergeIntoPrevious(cFlattenElevation* prev)
362 : {
363 0 : prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
364 0 : prev->m_i0 = std::min(prev->m_i0, m_i0);
365 0 : prev->m_j0 = std::min(prev->m_j0, m_j0);
366 0 : prev->m_i1 = std::max(prev->m_i1, m_i1);
367 0 : prev->m_j1 = std::max(prev->m_j1, m_j1);
368 0 : }
369 : };
370 0 : END_COMMAND(FlattenElevation)
371 :
372 :
373 0 : BEGIN_COMMAND(PikeElevation)
374 : {
375 : TerrainArray m_TerrainDelta;
376 : ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
377 :
378 0 : cPikeElevation()
379 0 : {
380 0 : m_TerrainDelta.Init();
381 0 : }
382 :
383 0 : void MakeDirty()
384 : {
385 0 : g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
386 0 : g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
387 0 : CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
388 0 : if (cmpTerrain)
389 0 : cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
390 0 : }
391 :
392 0 : void Do()
393 : {
394 0 : int amount = (int)msg->amount;
395 :
396 : // If the framerate is very high, 'amount' is often very
397 : // small (even zero) so the integer truncation is significant
398 : static float roundingError = 0.0;
399 0 : roundingError += msg->amount - (float)amount;
400 0 : if (roundingError >= 1.f)
401 : {
402 0 : amount += (int)roundingError;
403 0 : roundingError -= (float)(int)roundingError;
404 : }
405 :
406 0 : static CVector3D previousPosition;
407 0 : g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
408 0 : previousPosition = g_CurrentBrush.m_Centre;
409 :
410 : ssize_t x0, y0;
411 0 : g_CurrentBrush.GetBottomLeft(x0, y0);
412 0 : float h = ((float) g_CurrentBrush.m_H - 1) / 2.f;
413 :
414 0 : for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
415 : {
416 0 : for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
417 : {
418 0 : float b = g_CurrentBrush.Get(dx, dy);
419 0 : if (b)
420 : {
421 0 : float x = (float)dx - ((float)g_CurrentBrush.m_H - 1) / 2.f;
422 0 : float y = (float)dy - ((float)g_CurrentBrush.m_W - 1) / 2.f;
423 0 : float distance = Clamp(1 - static_cast<float>(sqrt(x * x + y * y)) / h, 0.01f, 1.0f);
424 0 : distance *= distance;
425 0 : m_TerrainDelta.RaiseVertex(x0 + dx, y0 + dy, (int)(amount * distance));
426 : }
427 : }
428 : }
429 0 : m_i0 = x0 - 1;
430 0 : m_j0 = y0 - 1;
431 0 : m_i1 = x0 + g_CurrentBrush.m_W;
432 0 : m_j1 = y0 + g_CurrentBrush.m_H;
433 0 : MakeDirty();
434 0 : }
435 :
436 0 : void Undo()
437 : {
438 0 : m_TerrainDelta.Undo();
439 0 : MakeDirty();
440 0 : }
441 :
442 0 : void Redo()
443 : {
444 0 : m_TerrainDelta.Redo();
445 0 : MakeDirty();
446 0 : }
447 :
448 0 : void MergeIntoPrevious(cPikeElevation* prev)
449 : {
450 0 : prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
451 0 : prev->m_i0 = std::min(prev->m_i0, m_i0);
452 0 : prev->m_j0 = std::min(prev->m_j0, m_j0);
453 0 : prev->m_i1 = std::max(prev->m_i1, m_i1);
454 0 : prev->m_j1 = std::max(prev->m_j1, m_j1);
455 0 : }
456 : };
457 0 : END_COMMAND(PikeElevation)
458 :
459 : }
|