-
Notifications
You must be signed in to change notification settings - Fork 0
/
ElevationData.cs
290 lines (235 loc) · 7.21 KB
/
ElevationData.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using System;
using System.Numerics;
using TerrainFactory.Util;
namespace TerrainFactory
{
public class ElevationData
{
public string SourceFileName { get; set; }
private float[,] Data { get; set; }
public bool HasElevationData => Data != null;
public int CellCountX => Data.GetLength(0);
public int CellCountY => Data.GetLength(1);
public float CellAspectRatio => CellCountX / (float)CellCountY;
public int TotalCellCount => CellCountX * CellCountY;
public float CellSize { get; set; }
public float NoDataValue { get; set; } = float.MinValue;
public Vector2 LowerCornerPosition { get; set; }
public Vector2 UpperCornerPosition => LowerCornerPosition + Size;
public Vector2 CenterPosition => LowerCornerPosition + Size * 0.5f;
public Vector2 Size => new Vector2(CellCountX * CellSize, CellCountY * CellSize);
public float TotalArea => Size.X * Size.Y;
//TODO: Refactor for better clarity
public (int x, int y) offsetFromSource = (0, 0);
public float MinElevation { get; private set; } = float.PositiveInfinity;
public float MaxElevation { get; private set; } = float.NegativeInfinity;
//Used for scaling operations. In data created from an image, these values represent the black and white values of the source image (default 0 and 1 respectively)
//In data created from ASC data itself, these are equal to lowestValue and highestValue unless overridden for heightmap export.
public float? OverrideLowPoint { get; set; }
public float? OverrideHighPoint { get; set; }
public float LowPoint => OverrideLowPoint ?? MinElevation;
public float HighPoint => OverrideHighPoint ?? MaxElevation;
public bool WasModified { get; private set; } = false;
public ElevationData()
{
}
public ElevationData(float[,] data, float cellSize)
{
Data = data;
CellSize = cellSize;
}
public ElevationData(int cellCountX, int cellCountY, string sourceFileName = null)
{
SourceFileName = sourceFileName;
Data = new float[cellCountX, cellCountY];
}
public ElevationData(ElevationData original)
{
Data = (float[,])original.Data.Clone();
original.CopyAllPropertiesTo(this);
}
public void RecalculateElevationRange(bool clearLowHighPoints)
{
MinElevation = float.MaxValue;
MaxElevation = float.MinValue;
foreach(float f in Data)
{
if(Math.Abs(f - NoDataValue) > 0.1f)
{
if(f < MinElevation) MinElevation = f;
if(f > MaxElevation) MaxElevation = f;
}
}
if(clearLowHighPoints)
{
OverrideLowPoint = null;
OverrideHighPoint = null;
}
}
public void CopyAllPropertiesTo(ElevationData other)
{
other.CellSize = CellSize;
other.NoDataValue = NoDataValue;
other.LowerCornerPosition = LowerCornerPosition;
other.OverrideLowPoint = OverrideLowPoint;
other.OverrideHighPoint = OverrideHighPoint;
other.WasModified = true;
other.offsetFromSource = offsetFromSource;
RecalculateElevationRange(false);
}
public Bounds GetBounds()
{
return new Bounds(0, 0, CellCountX - 1, CellCountY - 1);
}
#region modification
public void SetHeightAt(int x, int y, float value)
{
Data[x, y] = value;
WasModified = true;
}
public void AddHeightAt(int x, int y, float add)
{
Data[x, y] += add;
WasModified = true;
}
public void Add(ElevationData other)
{
Modify((x, y, rx, ry, v) =>
{
return v + other.GetElevationAtNormalizedPos(rx, ry);
});
}
public void Multiply(ElevationData other)
{
Modify((x, y, rx, ry, v) =>
{
return v * other.GetElevationAtNormalizedPos(rx, ry);
});
}
public void RemapHeights(float fromMin, float fromMax, float toMin, float toMax)
{
Modify((x, y, rx, ry, height) =>
{
return MathUtils.Remap(height, fromMin, fromMax, toMin, toMax);
});
if(OverrideLowPoint.HasValue) OverrideLowPoint = MathUtils.Remap(OverrideLowPoint.Value, fromMin, fromMax, toMin, toMax);
if(OverrideHighPoint.HasValue) OverrideHighPoint = MathUtils.Remap(OverrideHighPoint.Value, fromMin, fromMax, toMin, toMax);
}
public delegate float ModificationFunc(int x, int y, float rx, float ry, float value);
public void Modify(ModificationFunc modificator)
{
for(int y = 0; y < CellCountY; y++)
{
for(int x = 0; x < CellCountX; x++)
{
float rx = x / (float)(CellCountX - 1);
float ry = y / (float)(CellCountY - 1);
Data[x, y] = modificator(x, y, rx, ry, Data[x, y]);
}
}
RecalculateElevationRange(false);
WasModified = true;
}
public void Resample(int newCellCountX, bool scaleHeight)
{
float resampleRatio = newCellCountX / (float)CellCountX;
int newCellCountY = (int)Math.Round(CellCountY * resampleRatio, 0, MidpointRounding.AwayFromZero);
float[,] newData = new float[newCellCountX, newCellCountY];
for(int x = 0; x < newCellCountX; x++)
{
for(int y = 0; y < newCellCountY; y++)
{
newData[x, y] = GetElevationAtCellInterpolated(x / resampleRatio, y / resampleRatio);
}
}
ReplaceData(newData);
CellSize /= resampleRatio;
RecalculateElevationRange(false);
WasModified = true;
}
public void ScaleHeight(float scale)
{
Modify((x,y,rx,ry,h) => h *= scale);
}
public void ReplaceData(float[,] newData)
{
Data = newData;
WasModified = true;
}
public void ClearModifiedFlag() => WasModified = false;
#endregion
#region getter functions
public float[,] GetDataGrid()
{
return Data;
}
public float[,] GetDataGridYFlipped()
{
float[,] grid = new float[CellCountX, CellCountY];
var zLength = grid.GetLength(1);
for(int x = 0; x < grid.GetLength(0); x++)
{
for(int y = 0; y < grid.GetLength(1); y++)
{
//Y starts from top
grid[x, zLength - y - 1] = GetElevationAtCell(x, y);
}
}
return grid;
}
public float GetElevationAtCell(int x, int y)
{
if(x < 0 || y < 0 || x >= CellCountX || y >= CellCountY)
{
return NoDataValue;
}
else
{
return Data[x, y];
}
}
public float GetElevationAtCellUnchecked(int x, int y)
{
return Data[x, y];
}
public float GetElevationAtCellClamped(int x, int y)
{
x = MathUtils.Clamp(x, 0, CellCountX - 1);
y = MathUtils.Clamp(y, 0, CellCountY - 1);
return Data[x, y];
}
public float GetElevationAtCellInterpolated(float x, float y)
{
int x1 = (int)x;
int y1 = (int)y;
int x2 = x1 + 1;
int y2 = y1 + 1;
x1 = MathUtils.Clamp(x1, 0, CellCountX - 1);
x2 = MathUtils.Clamp(x2, 0, CellCountX - 1);
y1 = MathUtils.Clamp(y1, 0, CellCountY - 1);
y2 = MathUtils.Clamp(y2, 0, CellCountY - 1);
float wx = x - x1;
float wy = y - y1;
float vx1 = MathUtils.Lerp(GetElevationAtCell(x1, y1), GetElevationAtCell(x2, y1), wx);
float vx2 = MathUtils.Lerp(GetElevationAtCell(x1, y2), GetElevationAtCell(x2, y2), wx);
return MathUtils.Lerp(vx1, vx2, wy);
}
public float GetElevationAtNormalizedPos(float rx, float ry)
{
return GetElevationAtCellInterpolated(rx * (CellCountX - 1), ry * (CellCountY - 1));
}
public float[,] GetCellRange(Bounds bounds)
{
float[,] newdata = new float[bounds.NumCols, bounds.NumRows];
for(int x = 0; x < bounds.NumCols; x++)
{
for(int y = 0; y < bounds.NumRows; y++)
{
newdata[x, y] = Data[bounds.xMin + x, bounds.yMin + y];
}
}
return newdata;
}
#endregion
}
}