TerrainData.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Reflection;
  31. using OpenMetaverse;
  32. using log4net;
  33. namespace OpenSim.Framework
  34. {
  35. public abstract class TerrainData
  36. {
  37. // Terrain always is a square
  38. public int SizeX { get; protected set; }
  39. public int SizeY { get; protected set; }
  40. public int SizeZ { get; protected set; }
  41. // A height used when the user doesn't specify anything
  42. public const float DefaultTerrainHeight = 21f;
  43. public abstract float this[int x, int y] { get; set; }
  44. // Someday terrain will have caves
  45. public abstract float this[int x, int y, int z] { get; set; }
  46. public abstract bool IsTaintedAt(int xx, int yy);
  47. public abstract bool IsTaintedAt(int xx, int yy, bool clearOnTest);
  48. public abstract void TaintAllTerrain();
  49. public abstract void ClearTaint();
  50. public abstract void ClearLand();
  51. public abstract void ClearLand(float height);
  52. // Return a representation of this terrain for storing as a blob in the database.
  53. // Returns 'true' to say blob was stored in the 'out' locations.
  54. public abstract bool GetDatabaseBlob(out int DBFormatRevisionCode, out Array blob);
  55. // Given a revision code and a blob from the database, create and return the right type of TerrainData.
  56. // The sizes passed are the expected size of the region. The database info will be used to
  57. // initialize the heightmap of that sized region with as much data is in the blob.
  58. // Return created TerrainData or 'null' if unsuccessful.
  59. public static TerrainData CreateFromDatabaseBlobFactory(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  60. {
  61. // For the moment, there is only one implementation class
  62. return new HeightmapTerrainData(pSizeX, pSizeY, pSizeZ, pFormatCode, pBlob);
  63. }
  64. // return a special compressed representation of the heightmap in ints
  65. public abstract int[] GetCompressedMap();
  66. public abstract float CompressionFactor { get; }
  67. public abstract float[] GetFloatsSerialized();
  68. public abstract double[,] GetDoubles();
  69. public abstract TerrainData Clone();
  70. }
  71. // The terrain is stored in the database as a blob with a 'revision' field.
  72. // Some implementations of terrain storage would fill the revision field with
  73. // the time the terrain was stored. When real revisions were added and this
  74. // feature removed, that left some old entries with the time in the revision
  75. // field.
  76. // Thus, if revision is greater than 'RevisionHigh' then terrain db entry is
  77. // left over and it is presumed to be 'Legacy256'.
  78. // Numbers are arbitrary and are chosen to to reduce possible mis-interpretation.
  79. // If a revision does not match any of these, it is assumed to be Legacy256.
  80. public enum DBTerrainRevision
  81. {
  82. // Terrain is 'double[256,256]'
  83. Legacy256 = 11,
  84. // Terrain is 'int32, int32, float[,]' where the ints are X and Y dimensions
  85. // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
  86. Variable2D = 22,
  87. // Terrain is 'int32, int32, int32, int16[]' where the ints are X and Y dimensions
  88. // and third int is the 'compression factor'. The heights are compressed as
  89. // "int compressedHeight = (int)(height * compressionFactor);"
  90. // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
  91. Compressed2D = 27,
  92. // A revision that is not listed above or any revision greater than this value is 'Legacy256'.
  93. RevisionHigh = 1234
  94. }
  95. // Version of terrain that is a heightmap.
  96. // This should really be 'LLOptimizedHeightmapTerrainData' as it includes knowledge
  97. // of 'patches' which are 16x16 terrain areas which can be sent separately to the viewer.
  98. // The heighmap is kept as an array of integers. The integer values are converted to
  99. // and from floats by TerrainCompressionFactor.
  100. public class HeightmapTerrainData : TerrainData
  101. {
  102. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  103. private static string LogHeader = "[HEIGHTMAP TERRAIN DATA]";
  104. // TerrainData.this[x, y]
  105. public override float this[int x, int y]
  106. {
  107. get { return FromCompressedHeight(m_heightmap[x, y]); }
  108. set {
  109. int newVal = ToCompressedHeight(value);
  110. if (m_heightmap[x, y] != newVal)
  111. {
  112. m_heightmap[x, y] = newVal;
  113. m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true;
  114. }
  115. }
  116. }
  117. // TerrainData.this[x, y, z]
  118. public override float this[int x, int y, int z]
  119. {
  120. get { return this[x, y]; }
  121. set { this[x, y] = value; }
  122. }
  123. // TerrainData.ClearTaint
  124. public override void ClearTaint()
  125. {
  126. SetAllTaint(false);
  127. }
  128. // TerrainData.TaintAllTerrain
  129. public override void TaintAllTerrain()
  130. {
  131. SetAllTaint(true);
  132. }
  133. private void SetAllTaint(bool setting)
  134. {
  135. for (int ii = 0; ii < m_taint.GetLength(0); ii++)
  136. for (int jj = 0; jj < m_taint.GetLength(1); jj++)
  137. m_taint[ii, jj] = setting;
  138. }
  139. // TerrainData.ClearLand
  140. public override void ClearLand()
  141. {
  142. ClearLand(DefaultTerrainHeight);
  143. }
  144. // TerrainData.ClearLand(float)
  145. public override void ClearLand(float pHeight)
  146. {
  147. int flatHeight = ToCompressedHeight(pHeight);
  148. for (int xx = 0; xx < SizeX; xx++)
  149. for (int yy = 0; yy < SizeY; yy++)
  150. m_heightmap[xx, yy] = flatHeight;
  151. }
  152. // Return 'true' of the patch that contains these region coordinates has been modified.
  153. // Note that checking the taint clears it.
  154. // There is existing code that relies on this feature.
  155. public override bool IsTaintedAt(int xx, int yy, bool clearOnTest)
  156. {
  157. int tx = xx / Constants.TerrainPatchSize;
  158. int ty = yy / Constants.TerrainPatchSize;
  159. bool ret = m_taint[tx, ty];
  160. if (ret && clearOnTest)
  161. m_taint[tx, ty] = false;
  162. return ret;
  163. }
  164. // Old form that clears the taint flag when we check it.
  165. public override bool IsTaintedAt(int xx, int yy)
  166. {
  167. return IsTaintedAt(xx, yy, true /* clearOnTest */);
  168. }
  169. // TerrainData.GetDatabaseBlob
  170. // The user wants something to store in the database.
  171. public override bool GetDatabaseBlob(out int DBRevisionCode, out Array blob)
  172. {
  173. bool ret = false;
  174. if (SizeX == Constants.RegionSize && SizeY == Constants.RegionSize)
  175. {
  176. DBRevisionCode = (int)DBTerrainRevision.Legacy256;
  177. blob = ToLegacyTerrainSerialization();
  178. ret = true;
  179. }
  180. else
  181. {
  182. DBRevisionCode = (int)DBTerrainRevision.Compressed2D;
  183. blob = ToCompressedTerrainSerialization();
  184. ret = true;
  185. }
  186. return ret;
  187. }
  188. // TerrainData.CompressionFactor
  189. private float m_compressionFactor = 100.0f;
  190. public override float CompressionFactor { get { return m_compressionFactor; } }
  191. // TerrainData.GetCompressedMap
  192. public override int[] GetCompressedMap()
  193. {
  194. int[] newMap = new int[SizeX * SizeY];
  195. int ind = 0;
  196. for (int xx = 0; xx < SizeX; xx++)
  197. for (int yy = 0; yy < SizeY; yy++)
  198. newMap[ind++] = m_heightmap[xx, yy];
  199. return newMap;
  200. }
  201. // TerrainData.Clone
  202. public override TerrainData Clone()
  203. {
  204. HeightmapTerrainData ret = new HeightmapTerrainData(SizeX, SizeY, SizeZ);
  205. ret.m_heightmap = (int[,])this.m_heightmap.Clone();
  206. return ret;
  207. }
  208. // TerrainData.GetFloatsSerialized
  209. // This one dimensional version is ordered so height = map[y*sizeX+x];
  210. // DEPRECATED: don't use this function as it does not retain the dimensions of the terrain
  211. // and the caller will probably do the wrong thing if the terrain is not the legacy 256x256.
  212. public override float[] GetFloatsSerialized()
  213. {
  214. int points = SizeX * SizeY;
  215. float[] heights = new float[points];
  216. int idx = 0;
  217. for (int jj = 0; jj < SizeY; jj++)
  218. for (int ii = 0; ii < SizeX; ii++)
  219. {
  220. heights[idx++] = FromCompressedHeight(m_heightmap[ii, jj]);
  221. }
  222. return heights;
  223. }
  224. // TerrainData.GetDoubles
  225. public override double[,] GetDoubles()
  226. {
  227. double[,] ret = new double[SizeX, SizeY];
  228. for (int xx = 0; xx < SizeX; xx++)
  229. for (int yy = 0; yy < SizeY; yy++)
  230. ret[xx, yy] = FromCompressedHeight(m_heightmap[xx, yy]);
  231. return ret;
  232. }
  233. // =============================================================
  234. private int[,] m_heightmap;
  235. // Remember subregions of the heightmap that has changed.
  236. private bool[,] m_taint;
  237. // To save space (especially for large regions), keep the height as a short integer
  238. // that is coded as the float height times the compression factor (usually '100'
  239. // to make for two decimal points).
  240. public int ToCompressedHeight(double pHeight)
  241. {
  242. return (int)(pHeight * CompressionFactor);
  243. }
  244. public float FromCompressedHeight(int pHeight)
  245. {
  246. return ((float)pHeight) / CompressionFactor;
  247. }
  248. // To keep with the legacy theme, create an instance of this class based on the
  249. // way terrain used to be passed around.
  250. public HeightmapTerrainData(double[,] pTerrain)
  251. {
  252. SizeX = pTerrain.GetLength(0);
  253. SizeY = pTerrain.GetLength(1);
  254. SizeZ = (int)Constants.RegionHeight;
  255. m_compressionFactor = 100.0f;
  256. m_heightmap = new int[SizeX, SizeY];
  257. for (int ii = 0; ii < SizeX; ii++)
  258. {
  259. for (int jj = 0; jj < SizeY; jj++)
  260. {
  261. m_heightmap[ii, jj] = ToCompressedHeight(pTerrain[ii, jj]);
  262. }
  263. }
  264. // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  265. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  266. ClearTaint();
  267. }
  268. // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
  269. public HeightmapTerrainData(int pX, int pY, int pZ)
  270. {
  271. SizeX = pX;
  272. SizeY = pY;
  273. SizeZ = pZ;
  274. m_compressionFactor = 100.0f;
  275. m_heightmap = new int[SizeX, SizeY];
  276. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  277. // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  278. ClearTaint();
  279. ClearLand(0f);
  280. }
  281. public HeightmapTerrainData(int[] cmap, float pCompressionFactor, int pX, int pY, int pZ) : this(pX, pY, pZ)
  282. {
  283. m_compressionFactor = pCompressionFactor;
  284. int ind = 0;
  285. for (int xx = 0; xx < SizeX; xx++)
  286. for (int yy = 0; yy < SizeY; yy++)
  287. m_heightmap[xx, yy] = cmap[ind++];
  288. // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  289. }
  290. // Create a heighmap from a database blob
  291. public HeightmapTerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob) : this(pSizeX, pSizeY, pSizeZ)
  292. {
  293. switch ((DBTerrainRevision)pFormatCode)
  294. {
  295. case DBTerrainRevision.Compressed2D:
  296. FromCompressedTerrainSerialization(pBlob);
  297. m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  298. break;
  299. default:
  300. FromLegacyTerrainSerialization(pBlob);
  301. m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  302. break;
  303. }
  304. }
  305. // Just create an array of doubles. Presumes the caller implicitly knows the size.
  306. public Array ToLegacyTerrainSerialization()
  307. {
  308. Array ret = null;
  309. using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
  310. {
  311. using (BinaryWriter bw = new BinaryWriter(str))
  312. {
  313. for (int xx = 0; xx < Constants.RegionSize; xx++)
  314. {
  315. for (int yy = 0; yy < Constants.RegionSize; yy++)
  316. {
  317. double height = this[xx, yy];
  318. if (height == 0.0)
  319. height = double.Epsilon;
  320. bw.Write(height);
  321. }
  322. }
  323. }
  324. ret = str.ToArray();
  325. }
  326. return ret;
  327. }
  328. // Just create an array of doubles. Presumes the caller implicitly knows the size.
  329. public void FromLegacyTerrainSerialization(byte[] pBlob)
  330. {
  331. // In case database info doesn't match real terrain size, initialize the whole terrain.
  332. ClearLand();
  333. using (MemoryStream mstr = new MemoryStream(pBlob))
  334. {
  335. using (BinaryReader br = new BinaryReader(mstr))
  336. {
  337. for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
  338. {
  339. for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
  340. {
  341. float val = (float)br.ReadDouble();
  342. if (xx < SizeX && yy < SizeY)
  343. m_heightmap[xx, yy] = ToCompressedHeight(val);
  344. }
  345. }
  346. }
  347. ClearTaint();
  348. }
  349. }
  350. // See the reader below.
  351. public Array ToCompressedTerrainSerialization()
  352. {
  353. Array ret = null;
  354. using (MemoryStream str = new MemoryStream((3 * sizeof(Int32)) + (SizeX * SizeY * sizeof(Int16))))
  355. {
  356. using (BinaryWriter bw = new BinaryWriter(str))
  357. {
  358. bw.Write((Int32)DBTerrainRevision.Compressed2D);
  359. bw.Write((Int32)SizeX);
  360. bw.Write((Int32)SizeY);
  361. bw.Write((Int32)CompressionFactor);
  362. for (int yy = 0; yy < SizeY; yy++)
  363. for (int xx = 0; xx < SizeX; xx++)
  364. {
  365. bw.Write((Int16)m_heightmap[xx, yy]);
  366. }
  367. }
  368. ret = str.ToArray();
  369. }
  370. return ret;
  371. }
  372. // Initialize heightmap from blob consisting of:
  373. // int32, int32, int32, int32, int16[]
  374. // where the first int32 is format code, next two int32s are the X and y of heightmap data and
  375. // the forth int is the compression factor for the following int16s
  376. // This is just sets heightmap info. The actual size of the region was set on this instance's
  377. // creation and any heights not initialized by theis blob are set to the default height.
  378. public void FromCompressedTerrainSerialization(byte[] pBlob)
  379. {
  380. Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
  381. using (MemoryStream mstr = new MemoryStream(pBlob))
  382. {
  383. using (BinaryReader br = new BinaryReader(mstr))
  384. {
  385. hmFormatCode = br.ReadInt32();
  386. hmSizeX = br.ReadInt32();
  387. hmSizeY = br.ReadInt32();
  388. hmCompressionFactor = br.ReadInt32();
  389. m_compressionFactor = hmCompressionFactor;
  390. // In case database info doesn't match real terrain size, initialize the whole terrain.
  391. ClearLand();
  392. for (int yy = 0; yy < hmSizeY; yy++)
  393. {
  394. for (int xx = 0; xx < hmSizeX; xx++)
  395. {
  396. Int16 val = br.ReadInt16();
  397. if (xx < SizeX && yy < SizeY)
  398. m_heightmap[xx, yy] = val;
  399. }
  400. }
  401. }
  402. ClearTaint();
  403. m_log.InfoFormat("{0} Read compressed 2d heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
  404. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
  405. }
  406. }
  407. }
  408. }