TerrainData.cs 25 KB

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