aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/TerrainData.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Framework/TerrainData.cs423
1 files changed, 423 insertions, 0 deletions
diff --git a/OpenSim/Framework/TerrainData.cs b/OpenSim/Framework/TerrainData.cs
new file mode 100644
index 0000000..9325df2
--- /dev/null
+++ b/OpenSim/Framework/TerrainData.cs
@@ -0,0 +1,423 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Reflection;
32
33using OpenMetaverse;
34
35using log4net;
36
37namespace OpenSim.Framework
38{
39 public abstract class TerrainData
40 {
41 // Terrain always is a square
42 public int SizeX { get; protected set; }
43 public int SizeY { get; protected set; }
44 public int SizeZ { get; protected set; }
45
46 // A height used when the user doesn't specify anything
47 public const float DefaultTerrainHeight = 21f;
48
49 public abstract float this[int x, int y] { get; set; }
50 // Someday terrain will have caves
51 public abstract float this[int x, int y, int z] { get; set; }
52
53 public bool IsTainted { get; protected set; }
54 public abstract bool IsTaintedAt(int xx, int yy);
55 public abstract void ClearTaint();
56
57 public abstract void ClearLand();
58 public abstract void ClearLand(float height);
59
60 // Return a representation of this terrain for storing as a blob in the database.
61 // Returns 'true' to say blob was stored in the 'out' locations.
62 public abstract bool GetDatabaseBlob(out int DBFormatRevisionCode, out Array blob);
63
64 // Given a revision code and a blob from the database, create and return the right type of TerrainData.
65 // The sizes passed are the expected size of the region. The database info will be used to
66 // initialize the heightmap of that sized region with as much data is in the blob.
67 // Return created TerrainData or 'null' if unsuccessful.
68 public static TerrainData CreateFromDatabaseBlobFactory(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
69 {
70 // For the moment, there is only one implementation class
71 return new HeightmapTerrainData(pSizeX, pSizeY, pSizeZ, pFormatCode, pBlob);
72 }
73
74 // return a special compressed representation of the heightmap in shorts
75 public abstract short[] GetCompressedMap();
76 public abstract float CompressionFactor { get; }
77
78 public abstract double[,] GetDoubles();
79 public abstract TerrainData Clone();
80 }
81
82 // The terrain is stored in the database as a blob with a 'revision' field.
83 // Some implementations of terrain storage would fill the revision field with
84 // the time the terrain was stored. When real revisions were added and this
85 // feature removed, that left some old entries with the time in the revision
86 // field.
87 // Thus, if revision is greater than 'RevisionHigh' then terrain db entry is
88 // left over and it is presumed to be 'Legacy256'.
89 // Numbers are arbitrary and are chosen to to reduce possible mis-interpretation.
90 // If a revision does not match any of these, it is assumed to be Legacy256.
91 public enum DBTerrainRevision
92 {
93 // Terrain is 'double[256,256]'
94 Legacy256 = 11,
95 // Terrain is 'int32, int32, float[,]' where the ints are X and Y dimensions
96 // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
97 Variable2D = 22,
98 // Terrain is 'int32, int32, int32, int16[]' where the ints are X and Y dimensions
99 // and third int is the 'compression factor'. The heights are compressed as
100 // "short compressedHeight = (short)(height * compressionFactor);"
101 // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
102 Compressed2D = 27,
103 // A revision that is not listed above or any revision greater than this value is 'Legacy256'.
104 RevisionHigh = 1234
105 }
106
107 // Version of terrain that is a heightmap.
108 // This should really be 'LLOptimizedHeightmapTerrainData' as it includes knowledge
109 // of 'patches' which are 16x16 terrain areas which can be sent separately to the viewer.
110 // The heighmap is kept as an array of short integers. The integer values are converted to
111 // and from floats by TerrainCompressionFactor. Shorts are used to limit storage used.
112 public class HeightmapTerrainData : TerrainData
113 {
114 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
115 private static string LogHeader = "[HEIGHTMAP TERRAIN DATA]";
116
117 // TerrainData.this[x, y]
118 public override float this[int x, int y]
119 {
120 get { return FromCompressedHeight(m_heightmap[x, y]); }
121 set {
122 short newVal = ToCompressedHeight(value);
123 if (m_heightmap[x, y] != newVal)
124 {
125 m_heightmap[x, y] = newVal;
126 m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true;
127 }
128 }
129 }
130
131 // TerrainData.this[x, y, z]
132 public override float this[int x, int y, int z]
133 {
134 get { return this[x, y]; }
135 set { this[x, y] = value; }
136 }
137
138 // TerrainData.ClearTaint
139 public override void ClearTaint()
140 {
141 IsTainted = false;
142 for (int ii = 0; ii < m_taint.GetLength(0); ii++)
143 for (int jj = 0; jj < m_taint.GetLength(1); jj++)
144 m_taint[ii, jj] = false;
145 }
146
147 // TerrainData.ClearLand
148 public override void ClearLand()
149 {
150 ClearLand(DefaultTerrainHeight);
151 }
152 // TerrainData.ClearLand(float)
153 public override void ClearLand(float pHeight)
154 {
155 short flatHeight = ToCompressedHeight(pHeight);
156 for (int xx = 0; xx < SizeX; xx++)
157 for (int yy = 0; yy < SizeY; yy++)
158 m_heightmap[xx, yy] = flatHeight;
159 }
160
161 public override bool IsTaintedAt(int xx, int yy)
162 {
163 int tx = xx / Constants.TerrainPatchSize;
164 int ty = yy / Constants.TerrainPatchSize;
165 bool ret = m_taint[tx, ty];
166 m_taint[tx, ty] = false;
167 return ret;
168 }
169
170 // TerrainData.GetDatabaseBlob
171 // The user wants something to store in the database.
172 public override bool GetDatabaseBlob(out int DBRevisionCode, out Array blob)
173 {
174 bool ret = false;
175 if (SizeX == Constants.RegionSize && SizeY == Constants.RegionSize)
176 {
177 DBRevisionCode = (int)DBTerrainRevision.Legacy256;
178 blob = ToLegacyTerrainSerialization();
179 ret = true;
180 }
181 else
182 {
183 DBRevisionCode = (int)DBTerrainRevision.Compressed2D;
184 blob = ToCompressedTerrainSerialization();
185 ret = true;
186 }
187 return ret;
188 }
189
190 // TerrainData.CompressionFactor
191 private float m_compressionFactor = 100.0f;
192 public override float CompressionFactor { get { return m_compressionFactor; } }
193
194 // TerrainData.GetCompressedMap
195 public override short[] GetCompressedMap()
196 {
197 short[] newMap = new short[SizeX * SizeY];
198
199 int ind = 0;
200 for (int xx = 0; xx < SizeX; xx++)
201 for (int yy = 0; yy < SizeY; yy++)
202 newMap[ind++] = m_heightmap[xx, yy];
203
204 return newMap;
205
206 }
207 // TerrainData.Clone
208 public override TerrainData Clone()
209 {
210 HeightmapTerrainData ret = new HeightmapTerrainData(SizeX, SizeY, SizeZ);
211 ret.m_heightmap = (short[,])this.m_heightmap.Clone();
212 return ret;
213 }
214
215 // TerrainData.GetDoubles
216 public override double[,] GetDoubles()
217 {
218 double[,] ret = new double[SizeX, SizeY];
219 for (int xx = 0; xx < SizeX; xx++)
220 for (int yy = 0; yy < SizeY; yy++)
221 ret[xx, yy] = FromCompressedHeight(m_heightmap[xx, yy]);
222
223 return ret;
224 }
225
226
227 // =============================================================
228
229 private short[,] m_heightmap;
230 // Remember subregions of the heightmap that has changed.
231 private bool[,] m_taint;
232
233 // To save space (especially for large regions), keep the height as a short integer
234 // that is coded as the float height times the compression factor (usually '100'
235 // to make for two decimal points).
236 public short ToCompressedHeight(double pHeight)
237 {
238 return (short)(pHeight * CompressionFactor);
239 }
240
241 public float FromCompressedHeight(short pHeight)
242 {
243 return ((float)pHeight) / CompressionFactor;
244 }
245
246 // To keep with the legacy theme, create an instance of this class based on the
247 // way terrain used to be passed around.
248 public HeightmapTerrainData(double[,] pTerrain)
249 {
250 SizeX = pTerrain.GetLength(0);
251 SizeY = pTerrain.GetLength(1);
252 SizeZ = (int)Constants.RegionHeight;
253 m_compressionFactor = 100.0f;
254
255 m_heightmap = new short[SizeX, SizeY];
256 for (int ii = 0; ii < SizeX; ii++)
257 {
258 for (int jj = 0; jj < SizeY; jj++)
259 {
260 m_heightmap[ii, jj] = ToCompressedHeight(pTerrain[ii, jj]);
261
262 }
263 }
264 // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
265
266 m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
267 ClearTaint();
268 }
269
270 // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
271 public HeightmapTerrainData(int pX, int pY, int pZ)
272 {
273 SizeX = pX;
274 SizeY = pY;
275 SizeZ = pZ;
276 m_compressionFactor = 100.0f;
277 m_heightmap = new short[SizeX, SizeY];
278 m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
279 // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
280 ClearTaint();
281 ClearLand(0f);
282 }
283
284 public HeightmapTerrainData(short[] cmap, float pCompressionFactor, int pX, int pY, int pZ) : this(pX, pY, pZ)
285 {
286 m_compressionFactor = pCompressionFactor;
287 int ind = 0;
288 for (int xx = 0; xx < SizeX; xx++)
289 for (int yy = 0; yy < SizeY; yy++)
290 m_heightmap[xx, yy] = cmap[ind++];
291 // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
292 }
293
294 // Create a heighmap from a database blob
295 public HeightmapTerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob) : this(pSizeX, pSizeY, pSizeZ)
296 {
297 switch ((DBTerrainRevision)pFormatCode)
298 {
299 case DBTerrainRevision.Compressed2D:
300 FromCompressedTerrainSerialization(pBlob);
301 m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
302 break;
303 default:
304 FromLegacyTerrainSerialization(pBlob);
305 m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
306 break;
307 }
308 }
309
310 // Just create an array of doubles. Presumes the caller implicitly knows the size.
311 public Array ToLegacyTerrainSerialization()
312 {
313 Array ret = null;
314
315 using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
316 {
317 using (BinaryWriter bw = new BinaryWriter(str))
318 {
319 for (int xx = 0; xx < Constants.RegionSize; xx++)
320 {
321 for (int yy = 0; yy < Constants.RegionSize; yy++)
322 {
323 double height = this[xx, yy];
324 if (height == 0.0)
325 height = double.Epsilon;
326 bw.Write(height);
327 }
328 }
329 }
330 ret = str.ToArray();
331 }
332 return ret;
333 }
334
335 // Just create an array of doubles. Presumes the caller implicitly knows the size.
336 public void FromLegacyTerrainSerialization(byte[] pBlob)
337 {
338 // In case database info doesn't match real terrain size, initialize the whole terrain.
339 ClearLand();
340
341 using (MemoryStream mstr = new MemoryStream(pBlob))
342 {
343 using (BinaryReader br = new BinaryReader(mstr))
344 {
345 for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
346 {
347 for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
348 {
349 float val = (float)br.ReadDouble();
350 if (xx < SizeX && yy < SizeY)
351 m_heightmap[xx, yy] = ToCompressedHeight(val);
352 }
353 }
354 }
355 ClearTaint();
356 }
357 }
358
359 // See the reader below.
360 public Array ToCompressedTerrainSerialization()
361 {
362 Array ret = null;
363 using (MemoryStream str = new MemoryStream((3 * sizeof(Int32)) + (SizeX * SizeY * sizeof(Int16))))
364 {
365 using (BinaryWriter bw = new BinaryWriter(str))
366 {
367 bw.Write((Int32)DBTerrainRevision.Compressed2D);
368 bw.Write((Int32)SizeX);
369 bw.Write((Int32)SizeY);
370 bw.Write((Int32)CompressionFactor);
371 for (int yy = 0; yy < SizeY; yy++)
372 for (int xx = 0; xx < SizeX; xx++)
373 {
374 bw.Write((Int16)m_heightmap[xx, yy]);
375 }
376 }
377 ret = str.ToArray();
378 }
379 return ret;
380 }
381
382 // Initialize heightmap from blob consisting of:
383 // int32, int32, int32, int32, int16[]
384 // where the first int32 is format code, next two int32s are the X and y of heightmap data and
385 // the forth int is the compression factor for the following int16s
386 // This is just sets heightmap info. The actual size of the region was set on this instance's
387 // creation and any heights not initialized by theis blob are set to the default height.
388 public void FromCompressedTerrainSerialization(byte[] pBlob)
389 {
390 Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
391
392 using (MemoryStream mstr = new MemoryStream(pBlob))
393 {
394 using (BinaryReader br = new BinaryReader(mstr))
395 {
396 hmFormatCode = br.ReadInt32();
397 hmSizeX = br.ReadInt32();
398 hmSizeY = br.ReadInt32();
399 hmCompressionFactor = br.ReadInt32();
400
401 m_compressionFactor = hmCompressionFactor;
402
403 // In case database info doesn't match real terrain size, initialize the whole terrain.
404 ClearLand();
405
406 for (int yy = 0; yy < hmSizeY; yy++)
407 {
408 for (int xx = 0; xx < hmSizeX; xx++)
409 {
410 Int16 val = br.ReadInt16();
411 if (xx < SizeX && yy < SizeY)
412 m_heightmap[xx, yy] = val;
413 }
414 }
415 }
416 ClearTaint();
417
418 m_log.InfoFormat("{0} Read compressed 2d heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
419 LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
420 }
421 }
422 }
423}