TerrainData.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  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;
  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 TerrainTaintsArray m_taints;
  68. private readonly int m_taintSizeX;
  69. private readonly int m_taintSizeY;
  70. private readonly int m_mapStride;
  71. private readonly int m_mapPatchsStride;
  72. // legacy CompressionFactor
  73. public float CompressionFactor { get; private set; }
  74. public int SizeX { get; protected set; }
  75. public int SizeY { get; protected set; }
  76. public const int SizeZ = 0;
  77. // A height used when the user doesn't specify anything
  78. public const float DefaultTerrainHeight = 21f;
  79. // Given a revision code and a blob from the database, create and return the right type of TerrainData.
  80. // The sizes passed are the expected size of the region. The database info will be used to
  81. // initialize the heightmap of that sized region with as much data is in the blob.
  82. // Return created TerrainData or 'null' if unsuccessful.
  83. public static TerrainData CreateFromDatabaseBlobFactory(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  84. {
  85. // For the moment, there is only one implementation class
  86. return new TerrainData(pSizeX, pSizeY, pSizeZ, pFormatCode, pBlob);
  87. }
  88. public float this[int x, int y]
  89. {
  90. get { return m_heightmap[x, y]; }
  91. set
  92. {
  93. if (m_heightmap[x, y] != value)
  94. {
  95. m_heightmap[x, y] = value;
  96. int yy = y / Constants.TerrainPatchSize;
  97. m_taints.Set(x / Constants.TerrainPatchSize + yy * m_taintSizeX, true);
  98. }
  99. }
  100. }
  101. public float this[int x, int y, int z]
  102. {
  103. get { return this[x, y]; }
  104. set { this[x, y] = value; }
  105. }
  106. public float GetHeight(float x, float y)
  107. {
  108. // integer indexs
  109. int ix, mix;
  110. int iy, miy;
  111. // interpolators offset
  112. float dx;
  113. float dy;
  114. if (x <= 0)
  115. {
  116. if(y <= 0)
  117. return m_heightmap[0, 0];
  118. iy = (int)y;
  119. miy = SizeY - 1;
  120. if (iy >= miy)
  121. return m_heightmap[0, miy];
  122. dy = y - iy;
  123. float h = m_heightmap[0, iy];
  124. ++iy;
  125. return h + (m_heightmap[0, iy] - h) * dy;
  126. }
  127. ix = (int)x;
  128. mix = SizeX - 1;
  129. if (ix >= mix)
  130. {
  131. if(y <= 0)
  132. return m_heightmap[mix, 0];
  133. iy = (int)y;
  134. miy = SizeY - 1;
  135. if (y >= miy)
  136. return m_heightmap[mix, miy];
  137. dy = y - iy;
  138. float h = m_heightmap[mix, iy];
  139. ++iy;
  140. return h + (m_heightmap[mix, iy] - h) * dy;
  141. }
  142. dx = x - ix;
  143. if (y <= 0)
  144. {
  145. float h = m_heightmap[ix, 0];
  146. ++ix;
  147. return h + (m_heightmap[ix, 0] - h) * dx;
  148. }
  149. iy = (int)y;
  150. miy = SizeY - 1;
  151. if (iy >= miy)
  152. {
  153. float h = m_heightmap[ix, miy];
  154. ++ix;
  155. return h + (m_heightmap[ix, miy] - h) * dx;
  156. }
  157. dy = y - iy;
  158. float h0 = m_heightmap[ix, iy]; // 0,0 vertice
  159. float h1;
  160. float h2;
  161. if (dy > dx)
  162. {
  163. ++iy;
  164. h2 = m_heightmap[ix, iy]; // 0,1 vertice
  165. h1 = (h2 - h0) * dy; // 0,1 vertice minus 0,0
  166. ++ix;
  167. h2 = (m_heightmap[ix, iy] - h2) * dx; // 1,1 vertice minus 0,1
  168. }
  169. else
  170. {
  171. ++ix;
  172. h2 = m_heightmap[ix, iy]; // vertice 1,0
  173. h1 = (h2 - h0) * dx; // 1,0 vertice minus 0,0
  174. ++iy;
  175. h2 = (m_heightmap[ix, iy] - h2) * dy; // 1,1 vertice minus 1,0
  176. }
  177. return h0 + h1 + h2;
  178. }
  179. public TerrainTaintsArray GetTaints()
  180. {
  181. return m_taints;
  182. }
  183. public void ClearTaint()
  184. {
  185. m_taints.SetAll(false);
  186. }
  187. public void TaintAllTerrain()
  188. {
  189. m_taints.SetAll(true);
  190. }
  191. private void SetAllTaint(bool setting)
  192. {
  193. m_taints.SetAll(setting);
  194. }
  195. public void ClearLand()
  196. {
  197. ClearLand(DefaultTerrainHeight);
  198. }
  199. public void ClearLand(float pHeight)
  200. {
  201. for (int xx = 0; xx < SizeX; xx++)
  202. for (int yy = 0; yy < SizeY; yy++)
  203. m_heightmap[xx, yy] = pHeight;
  204. }
  205. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  206. public bool IsTainted()
  207. {
  208. return m_taints.IsTaited();
  209. }
  210. // Return 'true' of the patch that contains these region coordinates has been modified.
  211. // Note that checking the taint clears it.
  212. // There is existing code that relies on this feature.
  213. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  214. public bool IsTaintedAt(int xx, int yy, bool clearOnTest)
  215. {
  216. yy /= Constants.TerrainPatchSize;
  217. int indx = xx / Constants.TerrainPatchSize + yy * m_taintSizeX;
  218. return m_taints.Get(indx, clearOnTest);
  219. }
  220. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  221. public bool IsTaintedAt(int xx, int yy)
  222. {
  223. yy /= Constants.TerrainPatchSize;
  224. return m_taints.Get(xx / Constants.TerrainPatchSize + yy * m_taintSizeX);
  225. }
  226. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  227. public bool IsTaintedAtPatch(int xx, int yy, bool clearOnTest)
  228. {
  229. int indx = xx + yy * m_taintSizeX;
  230. return m_taints.Get(indx, clearOnTest);
  231. }
  232. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  233. public bool IsTaintedAtPatch(int xx, int yy)
  234. {
  235. return m_taints.Get(xx + yy * m_taintSizeX);
  236. }
  237. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  238. public bool IsTaintedAtPatch(int indx, bool clearOnTest)
  239. {
  240. return m_taints.Get(indx, clearOnTest);
  241. }
  242. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  243. public bool IsTaintedAtPatchWithClear(int indx)
  244. {
  245. return m_taints.GetAndClear(indx);
  246. }
  247. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  248. public bool IsTaintedAtPatch(int indx)
  249. {
  250. return m_taints.Get(indx);
  251. }
  252. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
  253. public int GetAndClearNextTaint(int startIndex)
  254. {
  255. return m_taints.GetAndClearNextTrue(startIndex);
  256. }
  257. // TerrainData.GetDatabaseBlob
  258. // The user wants something to store in the database.
  259. public bool GetDatabaseBlob(out int DBRevisionCode, out Array blob)
  260. {
  261. DBRevisionCode = (int)DBTerrainRevision.Variable2DGzip;
  262. blob = ToCompressedTerrainSerializationV2DGzip();
  263. return true;
  264. }
  265. // TerrainData.GetCompressedMap
  266. public float[] GetCompressedMap()
  267. {
  268. float[] newMap = new float[SizeX * SizeY];
  269. int ind = 0;
  270. for (int xx = 0; xx < SizeX; xx++)
  271. for (int yy = 0; yy < SizeY; yy++)
  272. newMap[ind++] = m_heightmap[xx, yy];
  273. return newMap;
  274. }
  275. public TerrainData Clone()
  276. {
  277. TerrainData ret = new TerrainData(SizeX, SizeY, SizeZ);
  278. ret.m_heightmap = (float[,])this.m_heightmap.Clone();
  279. return ret;
  280. }
  281. public float[] GetFloatsSerialized()
  282. {
  283. int points = SizeX * SizeY;
  284. float[] heights = new float[points];
  285. int idx = 0;
  286. for (int jj = 0; jj < SizeY; jj++)
  287. for (int ii = 0; ii < SizeX; ii++)
  288. {
  289. heights[idx++] = m_heightmap[ii, jj];
  290. }
  291. return heights;
  292. }
  293. public double[,] GetDoubles()
  294. {
  295. double[,] ret = new double[SizeX, SizeY];
  296. for (int xx = 0; xx < SizeX; xx++)
  297. for (int yy = 0; yy < SizeY; yy++)
  298. ret[xx, yy] = (double)m_heightmap[xx, yy];
  299. return ret;
  300. }
  301. public unsafe void GetPatchMinMax(int px, int py, out float zmin, out float zmax)
  302. {
  303. zmax = float.MinValue;
  304. zmin = float.MaxValue;
  305. int mpy = Constants.TerrainPatchSize * py;
  306. fixed (float* map = m_heightmap)
  307. {
  308. float* p = map + px * m_mapPatchsStride;
  309. float* pend = p + m_mapPatchsStride;
  310. while (p < pend)
  311. {
  312. float* yt = p + mpy;
  313. float* ytend = yt + 16;
  314. while(yt < ytend)
  315. {
  316. float val = *yt;
  317. if (val > zmax)
  318. zmax = val;
  319. else if (val < zmin)
  320. zmin = val;
  321. yt++;
  322. }
  323. p += m_mapStride;
  324. }
  325. }
  326. }
  327. public unsafe void GetPatchBlock(float* block, int px, int py, float sub, float premult)
  328. {
  329. int k = 0;
  330. int startX = px * m_mapPatchsStride;
  331. int endX = startX + m_mapPatchsStride;
  332. int mpy = py * Constants.TerrainPatchSize;
  333. fixed (float* map = m_heightmap)
  334. {
  335. float* yp = map + mpy;
  336. float* yend = yp + 16;
  337. while (yp < yend)
  338. {
  339. for (int x = startX; x < endX; x += m_mapStride)
  340. {
  341. block[k++] = (yp[x] - sub) * premult;
  342. }
  343. ++yp;
  344. }
  345. }
  346. }
  347. /*
  348. // that is coded as the float height times the compression factor (usually '100'
  349. // to make for two decimal points).
  350. public short ToCompressedHeightshort(float pHeight)
  351. {
  352. // clamp into valid range
  353. pHeight *= CompressionFactor;
  354. if (pHeight < short.MinValue)
  355. return short.MinValue;
  356. else if (pHeight > short.MaxValue)
  357. return short.MaxValue;
  358. return (short)pHeight;
  359. }
  360. public ushort ToCompressedHeightushort(float pHeight)
  361. {
  362. // clamp into valid range
  363. pHeight *= CompressionFactor;
  364. if (pHeight < ushort.MinValue)
  365. return ushort.MinValue;
  366. else if (pHeight > ushort.MaxValue)
  367. return ushort.MaxValue;
  368. return (ushort)pHeight;
  369. }
  370. */
  371. public float FromCompressedHeight(short pHeight)
  372. {
  373. return ((float)pHeight) / CompressionFactor;
  374. }
  375. public float FromCompressedHeight(ushort pHeight)
  376. {
  377. return ((float)pHeight) / CompressionFactor;
  378. }
  379. // To keep with the legacy theme, create an instance of this class based on the
  380. // way terrain used to be passed around.
  381. public TerrainData(double[,] pTerrain)
  382. {
  383. SizeX = pTerrain.GetLength(0);
  384. SizeY = pTerrain.GetLength(1);
  385. m_taintSizeX = SizeX / Constants.TerrainPatchSize;
  386. m_taintSizeY = SizeY / Constants.TerrainPatchSize;
  387. m_mapStride = SizeY;
  388. m_mapPatchsStride = m_mapStride * Constants.TerrainPatchSize;
  389. CompressionFactor = 100.0f;
  390. m_heightmap = new float[SizeX, SizeY];
  391. for (int ii = 0; ii < SizeX; ii++)
  392. {
  393. for (int jj = 0; jj < SizeY; jj++)
  394. {
  395. m_heightmap[ii, jj] = (float)pTerrain[ii, jj];
  396. }
  397. }
  398. // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  399. m_taints = new TerrainTaintsArray(m_taintSizeX * m_taintSizeY);
  400. }
  401. // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
  402. public TerrainData(int pX, int pY, int pZ)
  403. {
  404. SizeX = pX;
  405. SizeY = pY;
  406. m_taintSizeX = SizeX / Constants.TerrainPatchSize;
  407. m_taintSizeY = SizeY / Constants.TerrainPatchSize;
  408. m_mapStride = SizeY;
  409. m_mapPatchsStride = m_mapStride * Constants.TerrainPatchSize;
  410. CompressionFactor = 100.0f;
  411. m_heightmap = new float[SizeX, SizeY];
  412. m_taints = new TerrainTaintsArray(m_taintSizeX * m_taintSizeY);
  413. // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  414. ClearLand(0f);
  415. }
  416. public TerrainData(float[] cmap, float pCompressionFactor, int pX, int pY, int pZ)
  417. : this(pX, pY, pZ)
  418. {
  419. CompressionFactor = pCompressionFactor;
  420. int ind = 0;
  421. for (int xx = 0; xx < SizeX; xx++)
  422. for (int yy = 0; yy < SizeY; yy++)
  423. m_heightmap[xx, yy] = cmap[ind++];
  424. // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
  425. }
  426. // Create a heighmap from a database blob
  427. public TerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
  428. : this(pSizeX, pSizeY, pSizeZ)
  429. {
  430. switch ((DBTerrainRevision)pFormatCode)
  431. {
  432. case DBTerrainRevision.Variable2DGzip:
  433. FromCompressedTerrainSerializationV2DGZip(pBlob);
  434. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2DGzip serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  435. break;
  436. case DBTerrainRevision.Variable2D:
  437. FromCompressedTerrainSerializationV2D(pBlob);
  438. m_log.DebugFormat("{0} HeightmapTerrainData create from Variable2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  439. break;
  440. case DBTerrainRevision.Compressed2D:
  441. FromCompressedTerrainSerialization2D(pBlob);
  442. m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  443. break;
  444. default:
  445. FromLegacyTerrainSerialization(pBlob);
  446. m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
  447. break;
  448. }
  449. }
  450. // Just create an array of doubles. Presumes the caller implicitly knows the size.
  451. public Array ToLegacyTerrainSerialization()
  452. {
  453. Array ret = null;
  454. using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
  455. {
  456. using (BinaryWriter bw = new BinaryWriter(str))
  457. {
  458. for (int xx = 0; xx < Constants.RegionSize; xx++)
  459. {
  460. for (int yy = 0; yy < Constants.RegionSize; yy++)
  461. {
  462. double height = this[xx, yy];
  463. if (height == 0.0)
  464. height = double.Epsilon;
  465. bw.Write(height);
  466. }
  467. }
  468. }
  469. ret = str.ToArray();
  470. }
  471. return ret;
  472. }
  473. // Presumes the caller implicitly knows the size.
  474. public void FromLegacyTerrainSerialization(byte[] pBlob)
  475. {
  476. // In case database info doesn't match real terrain size, initialize the whole terrain.
  477. ClearLand();
  478. try
  479. {
  480. using (MemoryStream mstr = new MemoryStream(pBlob))
  481. {
  482. using (BinaryReader br = new BinaryReader(mstr))
  483. {
  484. for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
  485. {
  486. for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
  487. {
  488. float val = (float)br.ReadDouble();
  489. if (xx < SizeX && yy < SizeY)
  490. m_heightmap[xx, yy] = val;
  491. }
  492. }
  493. }
  494. }
  495. }
  496. catch
  497. {
  498. ClearLand();
  499. }
  500. ClearTaint();
  501. }
  502. // stores as variable2D
  503. // int32 sizeX
  504. // int32 sizeY
  505. // float[,] array
  506. public Array ToCompressedTerrainSerializationV2D()
  507. {
  508. Array ret = null;
  509. try
  510. {
  511. using (MemoryStream str = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  512. {
  513. using (BinaryWriter bw = new BinaryWriter(str))
  514. {
  515. bw.Write((Int32)SizeX);
  516. bw.Write((Int32)SizeY);
  517. for (int yy = 0; yy < SizeY; yy++)
  518. for (int xx = 0; xx < SizeX; xx++)
  519. {
  520. // reduce to 1mm resolution
  521. float val = MathF.Round(m_heightmap[xx, yy],3,MidpointRounding.AwayFromZero);
  522. bw.Write(val);
  523. }
  524. }
  525. ret = str.ToArray();
  526. }
  527. }
  528. catch {}
  529. m_log.DebugFormat("{0} V2D {1} bytes", LogHeader, ret.Length);
  530. return ret;
  531. }
  532. // as above with Gzip compression
  533. public Array ToCompressedTerrainSerializationV2DGzip()
  534. {
  535. Array ret = null;
  536. try
  537. {
  538. using (MemoryStream inp = new MemoryStream((2 * sizeof(Int32)) + (SizeX * SizeY * sizeof(float))))
  539. {
  540. using (BinaryWriter bw = new BinaryWriter(inp))
  541. {
  542. bw.Write((Int32)SizeX);
  543. bw.Write((Int32)SizeY);
  544. for (int yy = 0; yy < SizeY; yy++)
  545. for (int xx = 0; xx < SizeX; xx++)
  546. {
  547. //bw.Write((float)m_heightmap[xx, yy]);
  548. bw.Write(MathF.Round(m_heightmap[xx, yy], 3, MidpointRounding.AwayFromZero));
  549. }
  550. bw.Flush();
  551. inp.Seek(0, SeekOrigin.Begin);
  552. using (MemoryStream outputStream = new MemoryStream())
  553. {
  554. using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress))
  555. {
  556. inp.CopyStream(compressionStream, int.MaxValue);
  557. compressionStream.Close();
  558. ret = outputStream.ToArray();
  559. }
  560. }
  561. }
  562. }
  563. }
  564. catch {}
  565. m_log.DebugFormat("{0} V2DGzip {1} bytes", LogHeader, ret.Length);
  566. return ret;
  567. }
  568. // Initialize heightmap from blob consisting of:
  569. // int32, int32, int32, int32, int16[]
  570. // where the first int32 is format code, next two int32s are the X and y of heightmap data and
  571. // the forth int is the compression factor for the following int16s
  572. // This is just sets heightmap info. The actual size of the region was set on this instance's
  573. // creation and any heights not initialized by theis blob are set to the default height.
  574. public void FromCompressedTerrainSerialization2D(byte[] pBlob)
  575. {
  576. Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
  577. using (MemoryStream mstr = new MemoryStream(pBlob))
  578. {
  579. using (BinaryReader br = new BinaryReader(mstr))
  580. {
  581. hmFormatCode = br.ReadInt32();
  582. hmSizeX = br.ReadInt32();
  583. hmSizeY = br.ReadInt32();
  584. hmCompressionFactor = br.ReadInt32();
  585. CompressionFactor = hmCompressionFactor;
  586. // In case database info doesn't match real terrain size, initialize the whole terrain.
  587. bool needClear = false;
  588. if (hmSizeX > SizeX)
  589. hmSizeX = SizeX;
  590. else if (hmSizeX < SizeX)
  591. needClear = true;
  592. if (hmSizeY > SizeY)
  593. hmSizeY = SizeY;
  594. else if (hmSizeY < SizeY)
  595. needClear = true;
  596. if (needClear)
  597. ClearLand();
  598. for (int yy = 0; yy < hmSizeY; yy++)
  599. {
  600. for (int xx = 0; xx < hmSizeX; xx++)
  601. {
  602. float val = FromCompressedHeight(br.ReadInt16());
  603. m_heightmap[xx, yy] = val;
  604. }
  605. }
  606. }
  607. ClearTaint();
  608. m_log.DebugFormat("{0} Read (compressed2D) heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
  609. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
  610. }
  611. }
  612. // Initialize heightmap from blob consisting of:
  613. // int32, int32, int32, float[]
  614. // where the first int32 is format code, next two int32s are the X and y of heightmap data
  615. // This is just sets heightmap info. The actual size of the region was set on this instance's
  616. // creation and any heights not initialized by theis blob are set to the default height.
  617. public void FromCompressedTerrainSerializationV2D(byte[] pBlob)
  618. {
  619. Int32 hmSizeX, hmSizeY;
  620. try
  621. {
  622. using (MemoryStream mstr = new MemoryStream(pBlob))
  623. {
  624. using (BinaryReader br = new BinaryReader(mstr))
  625. {
  626. hmSizeX = br.ReadInt32();
  627. hmSizeY = br.ReadInt32();
  628. bool needClear = false;
  629. if (hmSizeX > SizeX)
  630. hmSizeX = SizeX;
  631. else if (hmSizeX < SizeX)
  632. needClear = true;
  633. if (hmSizeY > SizeY)
  634. hmSizeY = SizeY;
  635. else if (hmSizeY < SizeY)
  636. needClear = true;
  637. if (needClear)
  638. ClearLand();
  639. for (int yy = 0; yy < hmSizeY; yy++)
  640. {
  641. for (int xx = 0; xx < hmSizeX; xx++)
  642. {
  643. float val = br.ReadSingle();
  644. m_heightmap[xx, yy] = val;
  645. }
  646. }
  647. }
  648. }
  649. }
  650. catch (Exception e)
  651. {
  652. ClearTaint();
  653. m_log.ErrorFormat("{0} 2D error: {1} - terrain may be damaged",
  654. LogHeader, e.Message);
  655. return;
  656. }
  657. ClearTaint();
  658. m_log.DebugFormat("{0} V2D Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  659. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  660. }
  661. // as above but Gzip compressed
  662. public void FromCompressedTerrainSerializationV2DGZip(byte[] pBlob)
  663. {
  664. m_log.InfoFormat("{0} VD2Gzip {1} bytes input",
  665. LogHeader, pBlob.Length);
  666. int hmSizeX, hmSizeY;
  667. try
  668. {
  669. using (MemoryStream outputStream = new MemoryStream())
  670. {
  671. using (MemoryStream inputStream = new MemoryStream(pBlob))
  672. {
  673. using (GZipStream decompressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
  674. {
  675. decompressionStream.Flush();
  676. decompressionStream.CopyTo(outputStream);
  677. }
  678. }
  679. outputStream.Seek(0, SeekOrigin.Begin);
  680. using (BinaryReader br = new BinaryReader(outputStream))
  681. {
  682. hmSizeX = br.ReadInt32();
  683. hmSizeY = br.ReadInt32();
  684. bool needClear = false;
  685. if(hmSizeX > SizeX)
  686. hmSizeX = SizeX;
  687. else if (hmSizeX < SizeX)
  688. needClear = true;
  689. if (hmSizeY > SizeY)
  690. hmSizeY = SizeY;
  691. else if (hmSizeY < SizeY)
  692. needClear = true;
  693. if (needClear)
  694. ClearLand();
  695. for (int yy = 0; yy < hmSizeY; yy++)
  696. {
  697. for (int xx = 0; xx < hmSizeX; xx++)
  698. {
  699. float val = br.ReadSingle();
  700. m_heightmap[xx, yy] = val;
  701. }
  702. }
  703. }
  704. }
  705. }
  706. catch( Exception e)
  707. {
  708. ClearTaint();
  709. m_log.ErrorFormat("{0} V2DGzip error: {1} - terrain may be damaged",
  710. LogHeader, e.Message);
  711. return;
  712. }
  713. ClearTaint();
  714. m_log.DebugFormat("{0} V2DGzip. Heightmap size=<{1},{2}>. Region size=<{3},{4}>",
  715. LogHeader, hmSizeX, hmSizeY, SizeX, SizeY);
  716. }
  717. }
  718. }