TerrainData.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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. // TerrainData.GetDoubles
  185. public double[,] GetDoubles()
  186. {
  187. double[,] ret = new double[SizeX, SizeY];
  188. for (int xx = 0; xx < SizeX; xx++)
  189. for (int yy = 0; yy < SizeY; yy++)
  190. ret[xx, yy] = (double)m_heightmap[xx, yy];
  191. return ret;
  192. }
  193. public unsafe void GetPatchMinMax(int px, int py, out float zmin, out float zmax)
  194. {
  195. zmax = float.MinValue;
  196. zmin = float.MaxValue;
  197. int stride = m_heightmap.GetLength(1);
  198. int startx = px * 16 * stride;
  199. int endx = (px + 1) * 16 * stride;
  200. int starty = py * 16;
  201. fixed (float* map = m_heightmap)
  202. {
  203. for (int i = startx; i < endx; i += stride)
  204. {
  205. float* p = &map[i];
  206. for (int j = starty; j < starty + 16; j++)
  207. {
  208. float val = p[j];
  209. if (val > zmax) zmax = val;
  210. if (val < zmin) zmin = val;
  211. }
  212. }
  213. }
  214. }
  215. public unsafe void GetPatchBlock(float[] _block, int px, int py, float sub, float premult)
  216. {
  217. int k = 0;
  218. int stride = m_heightmap.GetLength(1);
  219. int startX = px * 16 * stride;
  220. int endX = (px + 1) * 16 * stride;
  221. int startY = py * 16;
  222. fixed(float* block = _block, map = m_heightmap)
  223. {
  224. for (int y = startY; y < startY + 16; y++)
  225. {
  226. for (int x = startX; x < endX; x += stride)
  227. {
  228. block[k++] = (map[x + y] - sub) * premult;
  229. }
  230. }
  231. }
  232. }
  233. /*
  234. // that is coded as the float height times the compression factor (usually '100'
  235. // to make for two decimal points).
  236. public short ToCompressedHeightshort(float pHeight)
  237. {
  238. // clamp into valid range
  239. pHeight *= CompressionFactor;
  240. if (pHeight < short.MinValue)
  241. return short.MinValue;
  242. else if (pHeight > short.MaxValue)
  243. return short.MaxValue;
  244. return (short)pHeight;
  245. }
  246. public ushort ToCompressedHeightushort(float pHeight)
  247. {
  248. // clamp into valid range
  249. pHeight *= CompressionFactor;
  250. if (pHeight < ushort.MinValue)
  251. return ushort.MinValue;
  252. else if (pHeight > ushort.MaxValue)
  253. return ushort.MaxValue;
  254. return (ushort)pHeight;
  255. }
  256. */
  257. public float FromCompressedHeight(short pHeight)
  258. {
  259. return ((float)pHeight) / CompressionFactor;
  260. }
  261. public float FromCompressedHeight(ushort pHeight)
  262. {
  263. return ((float)pHeight) / CompressionFactor;
  264. }
  265. // To keep with the legacy theme, create an instance of this class based on the
  266. // way terrain used to be passed around.
  267. public TerrainData(double[,] pTerrain)
  268. {
  269. SizeX = pTerrain.GetLength(0);
  270. SizeY = pTerrain.GetLength(1);
  271. SizeZ = (int)Constants.RegionHeight;
  272. CompressionFactor = 100.0f;
  273. m_heightmap = new float[SizeX, SizeY];
  274. for (int ii = 0; ii < SizeX; ii++)
  275. {
  276. for (int jj = 0; jj < SizeY; jj++)
  277. {
  278. m_heightmap[ii, jj] = (float)pTerrain[ii, jj];
  279. }
  280. }
  281. // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  282. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  283. ClearTaint();
  284. }
  285. // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
  286. public TerrainData(int pX, int pY, int pZ)
  287. {
  288. SizeX = pX;
  289. SizeY = pY;
  290. SizeZ = pZ;
  291. CompressionFactor = 100.0f;
  292. m_heightmap = new float[SizeX, SizeY];
  293. m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
  294. // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  295. ClearTaint();
  296. ClearLand(0f);
  297. }
  298. public TerrainData(float[] cmap, float pCompressionFactor, int pX, int pY, int pZ)
  299. : this(pX, pY, pZ)
  300. {
  301. CompressionFactor = pCompressionFactor;
  302. int ind = 0;
  303. for (int xx = 0; xx < SizeX; xx++)
  304. for (int yy = 0; yy < SizeY; yy++)
  305. m_heightmap[xx, yy] = cmap[ind++];
  306. // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  307. }
  308. // Create a heighmap from a database blob
  309. public TerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  310. : this(pSizeX, pSizeY, pSizeZ)
  311. {
  312. switch ((DBTerrainRevision)pFormatCode)
  313. {
  314. case DBTerrainRevision.Variable2DGzip:
  315. FromCompressedTerrainSerializationV2DGZip(pBlob);
  316. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2DGzip serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  317. break;
  318. case DBTerrainRevision.Variable2D:
  319. FromCompressedTerrainSerializationV2D(pBlob);
  320. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  321. break;
  322. case DBTerrainRevision.Compressed2D:
  323. FromCompressedTerrainSerialization2D(pBlob);
  324. m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  325. break;
  326. default:
  327. FromLegacyTerrainSerialization(pBlob);
  328. m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  329. break;
  330. }
  331. }
  332. // Just create an array of doubles. Presumes the caller implicitly knows the size.
  333. public Array ToLegacyTerrainSerialization()
  334. {
  335. Array ret = null;
  336. using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
  337. {
  338. using (BinaryWriter bw = new BinaryWriter(str))
  339. {
  340. for (int xx = 0; xx < Constants.RegionSize; xx++)
  341. {
  342. for (int yy = 0; yy < Constants.RegionSize; yy++)
  343. {
  344. double height = this[xx, yy];
  345. if (height == 0.0)
  346. height = double.Epsilon;
  347. bw.Write(height);
  348. }
  349. }
  350. }
  351. ret = str.ToArray();
  352. }
  353. return ret;
  354. }
  355. // Presumes the caller implicitly knows the size.
  356. public void FromLegacyTerrainSerialization(byte[] pBlob)
  357. {
  358. // In case database info doesn't match real terrain size, initialize the whole terrain.
  359. ClearLand();
  360. try
  361. {
  362. using (MemoryStream mstr = new MemoryStream(pBlob))
  363. {
  364. using (BinaryReader br = new BinaryReader(mstr))
  365. {
  366. for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
  367. {
  368. for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
  369. {
  370. float val = (float)br.ReadDouble();
  371. if (xx < SizeX && yy < SizeY)
  372. m_heightmap[xx, yy] = val;
  373. }
  374. }
  375. }
  376. }
  377. }
  378. catch
  379. {
  380. ClearLand();
  381. }
  382. ClearTaint();
  383. }
  384. // stores as variable2D
  385. // int32 sizeX
  386. // int32 sizeY
  387. // float[,] array
  388. public Array ToCompressedTerrainSerializationV2D()
  389. {
  390. Array ret = null;
  391. try
  392. {
  393. using (MemoryStream str = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  394. {
  395. using (BinaryWriter bw = new BinaryWriter(str))
  396. {
  397. bw.Write((Int32)SizeX);
  398. bw.Write((Int32)SizeY);
  399. for (int yy = 0; yy < SizeY; yy++)
  400. for (int xx = 0; xx < SizeX; xx++)
  401. {
  402. // reduce to 1cm resolution
  403. float val = (float)Math.Round(m_heightmap[xx, yy],2,MidpointRounding.ToEven);
  404. bw.Write(val);
  405. }
  406. }
  407. ret = str.ToArray();
  408. }
  409. }
  410. catch {}
  411. m_log.DebugFormat("{0} V2D {1} bytes", LogHeader, ret.Length);
  412. return ret;
  413. }
  414. // as above with Gzip compression
  415. public Array ToCompressedTerrainSerializationV2DGzip()
  416. {
  417. Array ret = null;
  418. try
  419. {
  420. using (MemoryStream inp = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  421. {
  422. using (BinaryWriter bw = new BinaryWriter(inp))
  423. {
  424. bw.Write((Int32)SizeX);
  425. bw.Write((Int32)SizeY);
  426. for (int yy = 0; yy < SizeY; yy++)
  427. for (int xx = 0; xx < SizeX; xx++)
  428. {
  429. bw.Write((float)m_heightmap[xx, yy]);
  430. }
  431. bw.Flush();
  432. inp.Seek(0, SeekOrigin.Begin);
  433. using (MemoryStream outputStream = new MemoryStream())
  434. {
  435. using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress))
  436. {
  437. inp.CopyStream(compressionStream, int.MaxValue);
  438. compressionStream.Close();
  439. ret = outputStream.ToArray();
  440. }
  441. }
  442. }
  443. }
  444. }
  445. catch {}
  446. m_log.DebugFormat("{0} V2DGzip {1} bytes", LogHeader, ret.Length);
  447. return ret;
  448. }
  449. // Initialize heightmap from blob consisting of:
  450. // int32, int32, int32, int32, int16[]
  451. // where the first int32 is format code, next two int32s are the X and y of heightmap data and
  452. // the forth int is the compression factor for the following int16s
  453. // This is just sets heightmap info. The actual size of the region was set on this instance's
  454. // creation and any heights not initialized by theis blob are set to the default height.
  455. public void FromCompressedTerrainSerialization2D(byte[] pBlob)
  456. {
  457. Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
  458. using (MemoryStream mstr = new MemoryStream(pBlob))
  459. {
  460. using (BinaryReader br = new BinaryReader(mstr))
  461. {
  462. hmFormatCode = br.ReadInt32();
  463. hmSizeX = br.ReadInt32();
  464. hmSizeY = br.ReadInt32();
  465. hmCompressionFactor = br.ReadInt32();
  466. CompressionFactor = hmCompressionFactor;
  467. // In case database info doesn't match real terrain size, initialize the whole terrain.
  468. ClearLand();
  469. for (int yy = 0; yy < hmSizeY; yy++)
  470. {
  471. for (int xx = 0; xx < hmSizeX; xx++)
  472. {
  473. float val = FromCompressedHeight(br.ReadInt16());
  474. if (xx < SizeX && yy < SizeY)
  475. m_heightmap[xx, yy] = val;
  476. }
  477. }
  478. }
  479. ClearTaint();
  480. m_log.DebugFormat("{0} Read (compressed2D) heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
  481. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
  482. }
  483. }
  484. // Initialize heightmap from blob consisting of:
  485. // int32, int32, int32, float[]
  486. // where the first int32 is format code, next two int32s are the X and y of heightmap data
  487. // This is just sets heightmap info. The actual size of the region was set on this instance's
  488. // creation and any heights not initialized by theis blob are set to the default height.
  489. public void FromCompressedTerrainSerializationV2D(byte[] pBlob)
  490. {
  491. Int32 hmSizeX, hmSizeY;
  492. try
  493. {
  494. using (MemoryStream mstr = new MemoryStream(pBlob))
  495. {
  496. using (BinaryReader br = new BinaryReader(mstr))
  497. {
  498. hmSizeX = br.ReadInt32();
  499. hmSizeY = br.ReadInt32();
  500. // In case database info doesn't match real terrain size, initialize the whole terrain.
  501. ClearLand();
  502. for (int yy = 0; yy < hmSizeY; yy++)
  503. {
  504. for (int xx = 0; xx < hmSizeX; xx++)
  505. {
  506. float val = br.ReadSingle();
  507. if (xx < SizeX && yy < SizeY)
  508. m_heightmap[xx, yy] = val;
  509. }
  510. }
  511. }
  512. }
  513. }
  514. catch (Exception e)
  515. {
  516. ClearTaint();
  517. m_log.ErrorFormat("{0} 2D error: {1} - terrain may be damaged",
  518. LogHeader, e.Message);
  519. return;
  520. }
  521. ClearTaint();
  522. m_log.DebugFormat("{0} V2D Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  523. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  524. }
  525. // as above but Gzip compressed
  526. public void FromCompressedTerrainSerializationV2DGZip(byte[] pBlob)
  527. {
  528. m_log.InfoFormat("{0} VD2Gzip {1} bytes input",
  529. LogHeader, pBlob.Length);
  530. Int32 hmSizeX, hmSizeY;
  531. try
  532. {
  533. using (MemoryStream outputStream = new MemoryStream())
  534. {
  535. using (MemoryStream inputStream = new MemoryStream(pBlob))
  536. {
  537. using (GZipStream decompressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
  538. {
  539. decompressionStream.Flush();
  540. decompressionStream.CopyTo(outputStream);
  541. }
  542. }
  543. outputStream.Seek(0, SeekOrigin.Begin);
  544. using (BinaryReader br = new BinaryReader(outputStream))
  545. {
  546. hmSizeX = br.ReadInt32();
  547. hmSizeY = br.ReadInt32();
  548. // In case database info doesn't match real terrain size, initialize the whole terrain.
  549. ClearLand();
  550. for (int yy = 0; yy < hmSizeY; yy++)
  551. {
  552. for (int xx = 0; xx < hmSizeX; xx++)
  553. {
  554. float val = br.ReadSingle();
  555. if (xx < SizeX && yy < SizeY)
  556. m_heightmap[xx, yy] = val;
  557. }
  558. }
  559. }
  560. }
  561. }
  562. catch( Exception e)
  563. {
  564. ClearTaint();
  565. m_log.ErrorFormat("{0} V2DGzip error: {1} - terrain may be damaged",
  566. LogHeader, e.Message);
  567. return;
  568. }
  569. ClearTaint();
  570. m_log.DebugFormat("{0} V2DGzip. Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  571. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  572. }
  573. }
  574. }