TerrainData.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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.IO.Compression;
  31. using System.Reflection;
  32. using OpenMetaverse;
  33. using log4net;
  34. namespace OpenSim.Framework
  35. {
  36. // The terrain is stored in the database as a blob with a 'revision' field.
  37. // Some implementations of terrain storage would fill the revision field with
  38. // the time the terrain was stored. When real revisions were added and this
  39. // feature removed, that left some old entries with the time in the revision
  40. // field.
  41. // Thus, if revision is greater than 'RevisionHigh' then terrain db entry is
  42. // left over and it is presumed to be 'Legacy256'.
  43. // Numbers are arbitrary and are chosen to to reduce possible mis-interpretation.
  44. // If a revision does not match any of these, it is assumed to be Legacy256.
  45. public enum DBTerrainRevision
  46. {
  47. // Terrain is 'double[256,256]'
  48. Legacy256 = 11,
  49. // Terrain is 'int32, int32, float[,]' where the ints are X and Y dimensions
  50. // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
  51. Variable2D = 22,
  52. Variable2DGzip = 23,
  53. // Terrain is 'int32, int32, int32, int16[]' where the ints are X and Y dimensions
  54. // and third int is the 'compression factor'. The heights are compressed as
  55. // "ushort compressedHeight = (ushort)(height * compressionFactor);"
  56. // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
  57. Compressed2D = 27,
  58. // A revision that is not listed above or any revision greater than this value is 'Legacy256'.
  59. RevisionHigh = 1234
  60. }
  61. public class TerrainData
  62. {
  63. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  64. private static string LogHeader = "[TERRAIN DATA]";
  65. private float[,] m_heightmap;
  66. // Remember subregions of the heightmap that has changed.
  67. private bool[,] m_taint;
  68. // legacy CompressionFactor
  69. public float CompressionFactor { get; private set; }
  70. // Terrain always is a square
  71. public int SizeX { get; protected set; }
  72. public int SizeY { get; protected set; }
  73. public int SizeZ { get; protected set; }
  74. // A height used when the user doesn't specify anything
  75. public const float DefaultTerrainHeight = 21f;
  76. // Given a revision code and a blob from the database, create and return the right type of TerrainData.
  77. // The sizes passed are the expected size of the region. The database info will be used to
  78. // initialize the heightmap of that sized region with as much data is in the blob.
  79. // Return created TerrainData or 'null' if unsuccessful.
  80. public static TerrainData CreateFromDatabaseBlobFactory(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  81. {
  82. // For the moment, there is only one implementation class
  83. return new TerrainData(pSizeX, pSizeY, pSizeZ, pFormatCode, pBlob);
  84. }
  85. public float this[int x, int y]
  86. {
  87. get { return m_heightmap[x, y]; }
  88. set
  89. {
  90. if (m_heightmap[x, y] != value)
  91. {
  92. m_heightmap[x, y] = value;
  93. m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true;
  94. }
  95. }
  96. }
  97. public float this[int x, int y, int z]
  98. {
  99. get { return this[x, y]; }
  100. set { this[x, y] = value; }
  101. }
  102. public void ClearTaint()
  103. {
  104. SetAllTaint(false);
  105. }
  106. public void TaintAllTerrain()
  107. {
  108. SetAllTaint(true);
  109. }
  110. private void SetAllTaint(bool setting)
  111. {
  112. for (int ii = 0; ii < m_taint.GetLength(0); ii++)
  113. for (int jj = 0; jj < m_taint.GetLength(1); jj++)
  114. m_taint[ii, jj] = setting;
  115. }
  116. public void ClearLand()
  117. {
  118. ClearLand(DefaultTerrainHeight);
  119. }
  120. public void ClearLand(float pHeight)
  121. {
  122. for (int xx = 0; xx < SizeX; xx++)
  123. for (int yy = 0; yy < SizeY; yy++)
  124. m_heightmap[xx, yy] = pHeight;
  125. }
  126. // Return 'true' of the patch that contains these region coordinates has been modified.
  127. // Note that checking the taint clears it.
  128. // There is existing code that relies on this feature.
  129. public bool IsTaintedAt(int xx, int yy, bool clearOnTest)
  130. {
  131. int tx = xx / Constants.TerrainPatchSize;
  132. int ty = yy / Constants.TerrainPatchSize;
  133. bool ret = m_taint[tx, ty];
  134. if (ret && clearOnTest)
  135. m_taint[tx, ty] = false;
  136. return ret;
  137. }
  138. // Old form that clears the taint flag when we check it.
  139. // ubit: this dangerus naming should be only check without clear
  140. // keeping for old modules outthere
  141. public bool IsTaintedAt(int xx, int yy)
  142. {
  143. return IsTaintedAt(xx, yy, true /* clearOnTest */);
  144. }
  145. // TerrainData.GetDatabaseBlob
  146. // The user wants something to store in the database.
  147. public bool GetDatabaseBlob(out int DBRevisionCode, out Array blob)
  148. {
  149. DBRevisionCode = (int)DBTerrainRevision.Variable2DGzip;
  150. blob = ToCompressedTerrainSerializationV2DGzip();
  151. return true;
  152. }
  153. // TerrainData.GetCompressedMap
  154. public float[] GetCompressedMap()
  155. {
  156. float[] newMap = new float[SizeX * SizeY];
  157. int ind = 0;
  158. for (int xx = 0; xx < SizeX; xx++)
  159. for (int yy = 0; yy < SizeY; yy++)
  160. newMap[ind++] = m_heightmap[xx, yy];
  161. return newMap;
  162. }
  163. public TerrainData Clone()
  164. {
  165. TerrainData ret = new TerrainData(SizeX, SizeY, SizeZ);
  166. ret.m_heightmap = (float[,])this.m_heightmap.Clone();
  167. return ret;
  168. }
  169. // This one dimensional version is ordered so height = map[y*sizeX+x];
  170. // DEPRECATED: don't use this function as it does not retain the dimensions of the terrain
  171. // and the caller will probably do the wrong thing if the terrain is not the legacy 256x256.
  172. public float[] GetFloatsSerialized()
  173. {
  174. int points = SizeX * SizeY;
  175. float[] heights = new float[points];
  176. int idx = 0;
  177. for (int jj = 0; jj < SizeY; jj++)
  178. for (int ii = 0; ii < SizeX; ii++)
  179. {
  180. heights[idx++] = m_heightmap[ii, jj];
  181. }
  182. return heights;
  183. }
  184. public double[,] GetDoubles()
  185. {
  186. double[,] ret = new double[SizeX, SizeY];
  187. for (int xx = 0; xx < SizeX; xx++)
  188. for (int yy = 0; yy < SizeY; yy++)
  189. ret[xx, yy] = (double)m_heightmap[xx, yy];
  190. return ret;
  191. }
  192. public unsafe void GetPatchMinMax(int px, int py, out float zmin, out float zmax)
  193. {
  194. zmax = float.MinValue;
  195. zmin = float.MaxValue;
  196. int stride = m_heightmap.GetLength(1);
  197. int startx = px * 16 * stride;
  198. int endx = (px + 1) * 16 * stride;
  199. int starty = py * 16;
  200. fixed (float* map = m_heightmap)
  201. {
  202. for (int i = startx; i < endx; i += stride)
  203. {
  204. float* p = &map[i];
  205. for (int j = starty; j < starty + 16; j++)
  206. {
  207. float val = p[j];
  208. if (val > zmax) zmax = val;
  209. if (val < zmin) zmin = val;
  210. }
  211. }
  212. }
  213. }
  214. public unsafe void GetPatchBlock(float[] _block, int px, int py, float sub, float premult)
  215. {
  216. int k = 0;
  217. int stride = m_heightmap.GetLength(1);
  218. int startX = px * 16 * stride;
  219. int endX = (px + 1) * 16 * stride;
  220. int startY = py * 16;
  221. fixed(float* block = _block, map = m_heightmap)
  222. {
  223. for (int y = startY; y < startY + 16; y++)
  224. {
  225. for (int x = startX; x < endX; x += stride)
  226. {
  227. block[k++] = (map[x + y] - sub) * premult;
  228. }
  229. }
  230. }
  231. }
  232. /*
  233. // that is coded as the float height times the compression factor (usually '100'
  234. // to make for two decimal points).
  235. public short ToCompressedHeightshort(float pHeight)
  236. {
  237. // clamp into valid range
  238. pHeight *= CompressionFactor;
  239. if (pHeight < short.MinValue)
  240. return short.MinValue;
  241. else if (pHeight > short.MaxValue)
  242. return short.MaxValue;
  243. return (short)pHeight;
  244. }
  245. public ushort ToCompressedHeightushort(float pHeight)
  246. {
  247. // clamp into valid range
  248. pHeight *= CompressionFactor;
  249. if (pHeight < ushort.MinValue)
  250. return ushort.MinValue;
  251. else if (pHeight > ushort.MaxValue)
  252. return ushort.MaxValue;
  253. return (ushort)pHeight;
  254. }
  255. */
  256. public float FromCompressedHeight(short pHeight)
  257. {
  258. return ((float)pHeight) / CompressionFactor;
  259. }
  260. public float FromCompressedHeight(ushort pHeight)
  261. {
  262. return ((float)pHeight) / CompressionFactor;
  263. }
  264. // To keep with the legacy theme, create an instance of this class based on the
  265. // way terrain used to be passed around.
  266. public TerrainData(double[,] pTerrain)
  267. {
  268. SizeX = pTerrain.GetLength(0);
  269. SizeY = pTerrain.GetLength(1);
  270. SizeZ = (int)Constants.RegionHeight;
  271. CompressionFactor = 100.0f;
  272. m_heightmap = new float[SizeX, SizeY];
  273. for (int ii = 0; ii < SizeX; ii++)
  274. {
  275. for (int jj = 0; jj < SizeY; jj++)
  276. {
  277. m_heightmap[ii, jj] = (float)pTerrain[ii, jj];
  278. }
  279. }
  280. // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  281. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  282. ClearTaint();
  283. }
  284. // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
  285. public TerrainData(int pX, int pY, int pZ)
  286. {
  287. SizeX = pX;
  288. SizeY = pY;
  289. SizeZ = pZ;
  290. CompressionFactor = 100.0f;
  291. m_heightmap = new float[SizeX, SizeY];
  292. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  293. // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  294. ClearTaint();
  295. ClearLand(0f);
  296. }
  297. public TerrainData(float[] cmap, float pCompressionFactor, int pX, int pY, int pZ)
  298. : this(pX, pY, pZ)
  299. {
  300. CompressionFactor = pCompressionFactor;
  301. int ind = 0;
  302. for (int xx = 0; xx < SizeX; xx++)
  303. for (int yy = 0; yy < SizeY; yy++)
  304. m_heightmap[xx, yy] = cmap[ind++];
  305. // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  306. }
  307. // Create a heighmap from a database blob
  308. public TerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  309. : this(pSizeX, pSizeY, pSizeZ)
  310. {
  311. switch ((DBTerrainRevision)pFormatCode)
  312. {
  313. case DBTerrainRevision.Variable2DGzip:
  314. FromCompressedTerrainSerializationV2DGZip(pBlob);
  315. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2DGzip serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  316. break;
  317. case DBTerrainRevision.Variable2D:
  318. FromCompressedTerrainSerializationV2D(pBlob);
  319. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  320. break;
  321. case DBTerrainRevision.Compressed2D:
  322. FromCompressedTerrainSerialization2D(pBlob);
  323. m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  324. break;
  325. default:
  326. FromLegacyTerrainSerialization(pBlob);
  327. m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  328. break;
  329. }
  330. }
  331. // Just create an array of doubles. Presumes the caller implicitly knows the size.
  332. public Array ToLegacyTerrainSerialization()
  333. {
  334. Array ret = null;
  335. using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
  336. {
  337. using (BinaryWriter bw = new BinaryWriter(str))
  338. {
  339. for (int xx = 0; xx < Constants.RegionSize; xx++)
  340. {
  341. for (int yy = 0; yy < Constants.RegionSize; yy++)
  342. {
  343. double height = this[xx, yy];
  344. if (height == 0.0)
  345. height = double.Epsilon;
  346. bw.Write(height);
  347. }
  348. }
  349. }
  350. ret = str.ToArray();
  351. }
  352. return ret;
  353. }
  354. // Presumes the caller implicitly knows the size.
  355. public void FromLegacyTerrainSerialization(byte[] pBlob)
  356. {
  357. // In case database info doesn't match real terrain size, initialize the whole terrain.
  358. ClearLand();
  359. try
  360. {
  361. using (MemoryStream mstr = new MemoryStream(pBlob))
  362. {
  363. using (BinaryReader br = new BinaryReader(mstr))
  364. {
  365. for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
  366. {
  367. for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
  368. {
  369. float val = (float)br.ReadDouble();
  370. if (xx < SizeX && yy < SizeY)
  371. m_heightmap[xx, yy] = val;
  372. }
  373. }
  374. }
  375. }
  376. }
  377. catch
  378. {
  379. ClearLand();
  380. }
  381. ClearTaint();
  382. }
  383. // stores as variable2D
  384. // int32 sizeX
  385. // int32 sizeY
  386. // float[,] array
  387. public Array ToCompressedTerrainSerializationV2D()
  388. {
  389. Array ret = null;
  390. try
  391. {
  392. using (MemoryStream str = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  393. {
  394. using (BinaryWriter bw = new BinaryWriter(str))
  395. {
  396. bw.Write((Int32)SizeX);
  397. bw.Write((Int32)SizeY);
  398. for (int yy = 0; yy < SizeY; yy++)
  399. for (int xx = 0; xx < SizeX; xx++)
  400. {
  401. // reduce to 1cm resolution
  402. float val = (float)Math.Round(m_heightmap[xx, yy],2,MidpointRounding.ToEven);
  403. bw.Write(val);
  404. }
  405. }
  406. ret = str.ToArray();
  407. }
  408. }
  409. catch {}
  410. m_log.DebugFormat("{0} V2D {1} bytes", LogHeader, ret.Length);
  411. return ret;
  412. }
  413. // as above with Gzip compression
  414. public Array ToCompressedTerrainSerializationV2DGzip()
  415. {
  416. Array ret = null;
  417. try
  418. {
  419. using (MemoryStream inp = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  420. {
  421. using (BinaryWriter bw = new BinaryWriter(inp))
  422. {
  423. bw.Write((Int32)SizeX);
  424. bw.Write((Int32)SizeY);
  425. for (int yy = 0; yy < SizeY; yy++)
  426. for (int xx = 0; xx < SizeX; xx++)
  427. {
  428. bw.Write((float)m_heightmap[xx, yy]);
  429. }
  430. bw.Flush();
  431. inp.Seek(0, SeekOrigin.Begin);
  432. using (MemoryStream outputStream = new MemoryStream())
  433. {
  434. using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress))
  435. {
  436. inp.CopyStream(compressionStream, int.MaxValue);
  437. compressionStream.Close();
  438. ret = outputStream.ToArray();
  439. }
  440. }
  441. }
  442. }
  443. }
  444. catch {}
  445. m_log.DebugFormat("{0} V2DGzip {1} bytes", LogHeader, ret.Length);
  446. return ret;
  447. }
  448. // Initialize heightmap from blob consisting of:
  449. // int32, int32, int32, int32, int16[]
  450. // where the first int32 is format code, next two int32s are the X and y of heightmap data and
  451. // the forth int is the compression factor for the following int16s
  452. // This is just sets heightmap info. The actual size of the region was set on this instance's
  453. // creation and any heights not initialized by theis blob are set to the default height.
  454. public void FromCompressedTerrainSerialization2D(byte[] pBlob)
  455. {
  456. Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
  457. using (MemoryStream mstr = new MemoryStream(pBlob))
  458. {
  459. using (BinaryReader br = new BinaryReader(mstr))
  460. {
  461. hmFormatCode = br.ReadInt32();
  462. hmSizeX = br.ReadInt32();
  463. hmSizeY = br.ReadInt32();
  464. hmCompressionFactor = br.ReadInt32();
  465. CompressionFactor = hmCompressionFactor;
  466. // In case database info doesn't match real terrain size, initialize the whole terrain.
  467. ClearLand();
  468. for (int yy = 0; yy < hmSizeY; yy++)
  469. {
  470. for (int xx = 0; xx < hmSizeX; xx++)
  471. {
  472. float val = FromCompressedHeight(br.ReadInt16());
  473. if (xx < SizeX && yy < SizeY)
  474. m_heightmap[xx, yy] = val;
  475. }
  476. }
  477. }
  478. ClearTaint();
  479. m_log.DebugFormat("{0} Read (compressed2D) heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
  480. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
  481. }
  482. }
  483. // Initialize heightmap from blob consisting of:
  484. // int32, int32, int32, float[]
  485. // where the first int32 is format code, next two int32s are the X and y of heightmap data
  486. // This is just sets heightmap info. The actual size of the region was set on this instance's
  487. // creation and any heights not initialized by theis blob are set to the default height.
  488. public void FromCompressedTerrainSerializationV2D(byte[] pBlob)
  489. {
  490. Int32 hmSizeX, hmSizeY;
  491. try
  492. {
  493. using (MemoryStream mstr = new MemoryStream(pBlob))
  494. {
  495. using (BinaryReader br = new BinaryReader(mstr))
  496. {
  497. hmSizeX = br.ReadInt32();
  498. hmSizeY = br.ReadInt32();
  499. // In case database info doesn't match real terrain size, initialize the whole terrain.
  500. ClearLand();
  501. for (int yy = 0; yy < hmSizeY; yy++)
  502. {
  503. for (int xx = 0; xx < hmSizeX; xx++)
  504. {
  505. float val = br.ReadSingle();
  506. if (xx < SizeX && yy < SizeY)
  507. m_heightmap[xx, yy] = val;
  508. }
  509. }
  510. }
  511. }
  512. }
  513. catch (Exception e)
  514. {
  515. ClearTaint();
  516. m_log.ErrorFormat("{0} 2D error: {1} - terrain may be damaged",
  517. LogHeader, e.Message);
  518. return;
  519. }
  520. ClearTaint();
  521. m_log.DebugFormat("{0} V2D Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  522. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  523. }
  524. // as above but Gzip compressed
  525. public void FromCompressedTerrainSerializationV2DGZip(byte[] pBlob)
  526. {
  527. m_log.InfoFormat("{0} VD2Gzip {1} bytes input",
  528. LogHeader, pBlob.Length);
  529. Int32 hmSizeX, hmSizeY;
  530. try
  531. {
  532. using (MemoryStream outputStream = new MemoryStream())
  533. {
  534. using (MemoryStream inputStream = new MemoryStream(pBlob))
  535. {
  536. using (GZipStream decompressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
  537. {
  538. decompressionStream.Flush();
  539. decompressionStream.CopyTo(outputStream);
  540. }
  541. }
  542. outputStream.Seek(0, SeekOrigin.Begin);
  543. using (BinaryReader br = new BinaryReader(outputStream))
  544. {
  545. hmSizeX = br.ReadInt32();
  546. hmSizeY = br.ReadInt32();
  547. // In case database info doesn't match real terrain size, initialize the whole terrain.
  548. ClearLand();
  549. for (int yy = 0; yy < hmSizeY; yy++)
  550. {
  551. for (int xx = 0; xx < hmSizeX; xx++)
  552. {
  553. float val = br.ReadSingle();
  554. if (xx < SizeX && yy < SizeY)
  555. m_heightmap[xx, yy] = val;
  556. }
  557. }
  558. }
  559. }
  560. }
  561. catch( Exception e)
  562. {
  563. ClearTaint();
  564. m_log.ErrorFormat("{0} V2DGzip error: {1} - terrain may be damaged",
  565. LogHeader, e.Message);
  566. return;
  567. }
  568. ClearTaint();
  569. m_log.DebugFormat("{0} V2DGzip. Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  570. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  571. }
  572. }
  573. }