TerrainSplat.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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.Diagnostics;
  29. using System.Drawing;
  30. using System.Drawing.Imaging;
  31. using log4net;
  32. using OpenMetaverse;
  33. using OpenSim.Framework;
  34. using OpenSim.Region.Framework.Interfaces;
  35. using OpenSim.Services.Interfaces;
  36. namespace OpenSim.Region.CoreModules.World.Warp3DMap
  37. {
  38. public static class TerrainSplat
  39. {
  40. #region Constants
  41. private static readonly UUID DIRT_DETAIL = new UUID("0bc58228-74a0-7e83-89bc-5c23464bcec5");
  42. private static readonly UUID GRASS_DETAIL = new UUID("63338ede-0037-c4fd-855b-015d77112fc8");
  43. private static readonly UUID MOUNTAIN_DETAIL = new UUID("303cd381-8560-7579-23f1-f0a880799740");
  44. private static readonly UUID ROCK_DETAIL = new UUID("53a2f406-4895-1d13-d541-d2e3b86bc19c");
  45. private static readonly UUID[] DEFAULT_TERRAIN_DETAIL = new UUID[]
  46. {
  47. DIRT_DETAIL,
  48. GRASS_DETAIL,
  49. MOUNTAIN_DETAIL,
  50. ROCK_DETAIL
  51. };
  52. private static readonly Color[] DEFAULT_TERRAIN_COLOR = new Color[]
  53. {
  54. Color.FromArgb(255, 164, 136, 117),
  55. Color.FromArgb(255, 65, 87, 47),
  56. Color.FromArgb(255, 157, 145, 131),
  57. Color.FromArgb(255, 125, 128, 130)
  58. };
  59. private static readonly UUID TERRAIN_CACHE_MAGIC = new UUID("2c0c7ef2-56be-4eb8-aacb-76712c535b4b");
  60. #endregion Constants
  61. private static readonly ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
  62. private static string LogHeader = "[WARP3D TERRAIN SPLAT]";
  63. /// <summary>
  64. /// Builds a composited terrain texture given the region texture
  65. /// and heightmap settings
  66. /// </summary>
  67. /// <param name="terrain">Terrain heightmap</param>
  68. /// <param name="regionInfo">Region information including terrain texture parameters</param>
  69. /// <returns>A 256x256 square RGB texture ready for rendering</returns>
  70. /// <remarks>Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting
  71. /// Note we create a 256x256 dimension texture even if the actual terrain is larger.
  72. /// </remarks>
  73. public static Bitmap Splat(ITerrainChannel terrain,
  74. UUID[] textureIDs, float[] startHeights, float[] heightRanges,
  75. Vector3d regionPosition, IAssetService assetService, bool textureTerrain)
  76. {
  77. Debug.Assert(textureIDs.Length == 4);
  78. Debug.Assert(startHeights.Length == 4);
  79. Debug.Assert(heightRanges.Length == 4);
  80. Bitmap[] detailTexture = new Bitmap[4];
  81. if (textureTerrain)
  82. {
  83. // Swap empty terrain textureIDs with default IDs
  84. for (int i = 0; i < textureIDs.Length; i++)
  85. {
  86. if (textureIDs[i] == UUID.Zero)
  87. textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i];
  88. }
  89. #region Texture Fetching
  90. if (assetService != null)
  91. {
  92. for (int i = 0; i < 4; i++)
  93. {
  94. AssetBase asset;
  95. UUID cacheID = UUID.Combine(TERRAIN_CACHE_MAGIC, textureIDs[i]);
  96. // Try to fetch a cached copy of the decoded/resized version of this texture
  97. asset = assetService.GetCached(cacheID.ToString());
  98. if (asset != null)
  99. {
  100. try
  101. {
  102. using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data))
  103. detailTexture[i] = (Bitmap)Image.FromStream(stream);
  104. }
  105. catch (Exception ex)
  106. {
  107. m_log.Warn("Failed to decode cached terrain texture " + cacheID +
  108. " (textureID: " + textureIDs[i] + "): " + ex.Message);
  109. }
  110. }
  111. if (detailTexture[i] == null)
  112. {
  113. // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG
  114. asset = assetService.Get(textureIDs[i].ToString());
  115. if (asset != null)
  116. {
  117. // m_log.DebugFormat(
  118. // "[TERRAIN SPLAT]: Got cached original JPEG2000 terrain texture {0} {1}", i, asset.ID);
  119. try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); }
  120. catch (Exception ex)
  121. {
  122. m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message);
  123. }
  124. }
  125. if (detailTexture[i] != null)
  126. {
  127. // Make sure this texture is the correct size, otherwise resize
  128. if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256)
  129. {
  130. using (Bitmap origBitmap = detailTexture[i])
  131. {
  132. detailTexture[i] = ImageUtils.ResizeImage(origBitmap, 256, 256);
  133. }
  134. }
  135. // Save the decoded and resized texture to the cache
  136. byte[] data;
  137. using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
  138. {
  139. detailTexture[i].Save(stream, ImageFormat.Png);
  140. data = stream.ToArray();
  141. }
  142. // Cache a PNG copy of this terrain texture
  143. AssetBase newAsset = new AssetBase
  144. {
  145. Data = data,
  146. Description = "PNG",
  147. Flags = AssetFlags.Collectable,
  148. FullID = cacheID,
  149. ID = cacheID.ToString(),
  150. Local = true,
  151. Name = String.Empty,
  152. Temporary = true,
  153. Type = (sbyte)AssetType.Unknown
  154. };
  155. newAsset.Metadata.ContentType = "image/png";
  156. assetService.Store(newAsset);
  157. }
  158. }
  159. }
  160. }
  161. #endregion Texture Fetching
  162. }
  163. // Fill in any missing textures with a solid color
  164. for (int i = 0; i < 4; i++)
  165. {
  166. if (detailTexture[i] == null)
  167. {
  168. m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color",
  169. LogHeader, i);
  170. // Create a solid color texture for this layer
  171. detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
  172. using (Graphics gfx = Graphics.FromImage(detailTexture[i]))
  173. {
  174. using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i]))
  175. gfx.FillRectangle(brush, 0, 0, 256, 256);
  176. }
  177. }
  178. else
  179. {
  180. if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256)
  181. {
  182. detailTexture[i] = ResizeBitmap(detailTexture[i], 256, 256);
  183. }
  184. }
  185. }
  186. #region Layer Map
  187. float[,] layermap = new float[256, 256];
  188. // Scale difference between actual region size and the 256 texture being created
  189. int xFactor = terrain.Width / 256;
  190. int yFactor = terrain.Height / 256;
  191. // Create 'layermap' where each value is the fractional layer number to place
  192. // at that point. For instance, a value of 1.345 gives the blending of
  193. // layer 1 and layer 2 for that point.
  194. for (int y = 0; y < 256; y++)
  195. {
  196. for (int x = 0; x < 256; x++)
  197. {
  198. float height = (float)terrain[x * xFactor, y * yFactor];
  199. float pctX = (float)x / 255f;
  200. float pctY = (float)y / 255f;
  201. // Use bilinear interpolation between the four corners of start height and
  202. // height range to select the current values at this position
  203. float startHeight = ImageUtils.Bilinear(
  204. startHeights[0],
  205. startHeights[2],
  206. startHeights[1],
  207. startHeights[3],
  208. pctX, pctY);
  209. startHeight = Utils.Clamp(startHeight, 0f, 255f);
  210. float heightRange = ImageUtils.Bilinear(
  211. heightRanges[0],
  212. heightRanges[2],
  213. heightRanges[1],
  214. heightRanges[3],
  215. pctX, pctY);
  216. heightRange = Utils.Clamp(heightRange, 0f, 255f);
  217. // Generate two frequencies of perlin noise based on our global position
  218. // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting
  219. Vector3 vec = new Vector3
  220. (
  221. ((float)regionPosition.X + (x * xFactor)) * 0.20319f,
  222. ((float)regionPosition.Y + (y * yFactor)) * 0.20319f,
  223. height * 0.25f
  224. );
  225. float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f;
  226. float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f;
  227. float noise = (lowFreq + highFreq) * 2f;
  228. // Combine the current height, generated noise, start height, and height range parameters, then scale all of it
  229. float layer = ((height + noise - startHeight) / heightRange) * 4f;
  230. if (Single.IsNaN(layer))
  231. layer = 0f;
  232. layermap[x, y] = Utils.Clamp(layer, 0f, 3f);
  233. }
  234. }
  235. #endregion Layer Map
  236. #region Texture Compositing
  237. Bitmap output = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
  238. BitmapData outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
  239. // Unsafe work as we lock down the source textures for quicker access and access the
  240. // pixel data directly
  241. unsafe
  242. {
  243. // Get handles to all of the texture data arrays
  244. BitmapData[] datas = new BitmapData[]
  245. {
  246. detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat),
  247. detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat),
  248. detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat),
  249. detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat)
  250. };
  251. // Compute size of each pixel data (used to address into the pixel data array)
  252. int[] comps = new int[]
  253. {
  254. (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
  255. (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
  256. (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3,
  257. (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3
  258. };
  259. for (int y = 0; y < 256; y++)
  260. {
  261. for (int x = 0; x < 256; x++)
  262. {
  263. float layer = layermap[x, y];
  264. // Select two textures
  265. int l0 = (int)Math.Floor(layer);
  266. int l1 = Math.Min(l0 + 1, 3);
  267. byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0];
  268. byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1];
  269. byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3;
  270. float aB = *(ptrA + 0);
  271. float aG = *(ptrA + 1);
  272. float aR = *(ptrA + 2);
  273. float bB = *(ptrB + 0);
  274. float bG = *(ptrB + 1);
  275. float bR = *(ptrB + 2);
  276. float layerDiff = layer - l0;
  277. // Interpolate between the two selected textures
  278. *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB));
  279. *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG));
  280. *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR));
  281. }
  282. }
  283. for (int i = 0; i < detailTexture.Length; i++)
  284. detailTexture[i].UnlockBits(datas[i]);
  285. }
  286. for (int i = 0; i < detailTexture.Length; i++)
  287. if (detailTexture[i] != null)
  288. detailTexture[i].Dispose();
  289. output.UnlockBits(outputData);
  290. // We generated the texture upside down, so flip it
  291. output.RotateFlip(RotateFlipType.RotateNoneFlipY);
  292. #endregion Texture Compositing
  293. return output;
  294. }
  295. public static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
  296. {
  297. m_log.DebugFormat("{0} ResizeBitmap. From <{1},{2}> to <{3},{4}>",
  298. LogHeader, b.Width, b.Height, nWidth, nHeight);
  299. Bitmap result = new Bitmap(nWidth, nHeight);
  300. using (Graphics g = Graphics.FromImage(result))
  301. g.DrawImage(b, 0, 0, nWidth, nHeight);
  302. b.Dispose();
  303. return result;
  304. }
  305. public static Bitmap SplatSimple(float[] heightmap)
  306. {
  307. const float BASE_HSV_H = 93f / 360f;
  308. const float BASE_HSV_S = 44f / 100f;
  309. const float BASE_HSV_V = 34f / 100f;
  310. Bitmap img = new Bitmap(256, 256);
  311. BitmapData bitmapData = img.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
  312. unsafe
  313. {
  314. for (int y = 255; y >= 0; y--)
  315. {
  316. for (int x = 0; x < 256; x++)
  317. {
  318. float normHeight = heightmap[y * 256 + x] / 255f;
  319. normHeight = Utils.Clamp(normHeight, BASE_HSV_V, 1.0f);
  320. Color4 color = Color4.FromHSV(BASE_HSV_H, BASE_HSV_S, normHeight);
  321. byte* ptr = (byte*)bitmapData.Scan0 + y * bitmapData.Stride + x * 3;
  322. *(ptr + 0) = (byte)(color.B * 255f);
  323. *(ptr + 1) = (byte)(color.G * 255f);
  324. *(ptr + 2) = (byte)(color.R * 255f);
  325. }
  326. }
  327. }
  328. img.UnlockBits(bitmapData);
  329. return img;
  330. }
  331. }
  332. }