123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- /*
- * Copyright (c) Contributors, http://opensimulator.org/
- * See CONTRIBUTORS.TXT for a full list of copyright holders.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the OpenSimulator Project nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- using System;
- using System.Diagnostics;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.IO;
- using log4net;
- using OpenMetaverse;
- using OpenSim.Framework;
- using OpenSim.Region.Framework.Interfaces;
- using OpenSim.Services.Interfaces;
- namespace OpenSim.Region.CoreModules.World.Warp3DMap
- {
- public static class TerrainSplat
- {
- #region Constants
- private static readonly UUID DIRT_DETAIL = new UUID("0bc58228-74a0-7e83-89bc-5c23464bcec5");
- private static readonly UUID GRASS_DETAIL = new UUID("63338ede-0037-c4fd-855b-015d77112fc8");
- private static readonly UUID MOUNTAIN_DETAIL = new UUID("303cd381-8560-7579-23f1-f0a880799740");
- private static readonly UUID ROCK_DETAIL = new UUID("53a2f406-4895-1d13-d541-d2e3b86bc19c");
- private static readonly UUID[] DEFAULT_TERRAIN_DETAIL = new UUID[]
- {
- DIRT_DETAIL,
- GRASS_DETAIL,
- MOUNTAIN_DETAIL,
- ROCK_DETAIL
- };
- private static readonly Color[] DEFAULT_TERRAIN_COLOR = new Color[]
- {
- Color.FromArgb(255, 164, 136, 117),
- Color.FromArgb(255, 65, 87, 47),
- Color.FromArgb(255, 157, 145, 131),
- Color.FromArgb(255, 125, 128, 130)
- };
- private static readonly UUID TERRAIN_CACHE_MAGIC = new UUID("2c0c7ef2-56be-4eb8-aacb-76712c535b4b");
- #endregion Constants
- private static readonly ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
- private static string LogHeader = "[WARP3D TERRAIN SPLAT]";
- /// <summary>
- /// Builds a composited terrain texture given the region texture
- /// and heightmap settings
- /// </summary>
- /// <param name="terrain">Terrain heightmap</param>
- /// <param name="regionInfo">Region information including terrain texture parameters</param>
- /// <returns>A 256x256 square RGB texture ready for rendering</returns>
- /// <remarks>Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting
- /// Note we create a 256x256 dimension texture even if the actual terrain is larger.
- /// </remarks>
- public static Bitmap Splat(ITerrainChannel terrain, UUID[] textureIDs,
- float[] startHeights, float[] heightRanges,
- uint regionPositionX, uint regionPositionY,
- IAssetService assetService, IJ2KDecoder decoder,
- bool textureTerrain, bool averagetextureTerrain,
- int twidth, int theight)
- {
- Bitmap[] detailTexture = new Bitmap[4];
- byte[] mapColorsRed = new byte[4];
- byte[] mapColorsGreen = new byte[4];
- byte[] mapColorsBlue = new byte[4];
- bool usecolors = false;
- if (textureTerrain)
- {
- // Swap empty terrain textureIDs with default IDs
- for(int i = 0; i < textureIDs.Length; i++)
- {
- if(textureIDs[i].IsZero())
- textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i];
- }
- #region Texture Fetching
- if(assetService != null)
- {
- for(int i = 0; i < 4; i++)
- {
- // asset cache indexes are strings
- string cacheName = "MAP" + textureIDs[i].ToString();
- // Try to fetch a cached copy of the decoded/resized version of this texture
- AssetBase asset = assetService.GetCached(cacheName);
- if(asset != null)
- {
- try
- {
- using(MemoryStream stream = new MemoryStream(asset.Data))
- detailTexture[i] = (Bitmap)Image.FromStream(stream);
- if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb ||
- detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
- {
- detailTexture[i].Dispose();
- detailTexture[i] = null;
- }
- }
- catch(Exception ex)
- {
- m_log.Warn("Failed to decode cached terrain patch texture" + textureIDs[i] + "): " + ex.Message);
- }
- }
- if(detailTexture[i] == null)
- {
- // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG
- asset = assetService.Get(textureIDs[i].ToString());
- if(asset != null)
- {
- try
- {
- detailTexture[i] = (Bitmap)decoder.DecodeToImage(asset.Data);
- }
- catch(Exception ex)
- {
- m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message);
- }
- }
- if(detailTexture[i] != null)
- {
- //detailTexture[i].Save("terrOri" + i.ToString() + ".png");
- if (detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb ||
- detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
- using(Bitmap origBitmap = detailTexture[i])
- detailTexture[i] = Util.ResizeImageSolid(origBitmap, 16, 16);
- //detailTexture[i].Save("terr" + i.ToString() + ".png");
- // Save the decoded and resized texture to the cache
- byte[] data;
- using(MemoryStream stream = new MemoryStream())
- {
- detailTexture[i].Save(stream, ImageFormat.Png);
- data = stream.ToArray();
- }
- // Cache a PNG copy of this terrain texture
- AssetBase newAsset = new AssetBase
- {
- Data = data,
- Description = "PNG",
- Flags = AssetFlags.Collectable,
- FullID = UUID.Zero,
- ID = cacheName,
- Local = true,
- Name = String.Empty,
- Temporary = true,
- Type = (sbyte)AssetType.Unknown
- };
- newAsset.Metadata.ContentType = "image/png";
- assetService.Store(newAsset);
- }
- }
- }
- }
- #endregion Texture Fetching
- if(averagetextureTerrain)
- {
- for(int t = 0; t < 4; t++)
- {
- usecolors = true;
- if(detailTexture[t] == null)
- {
- mapColorsRed[t] = DEFAULT_TERRAIN_COLOR[t].R;
- mapColorsGreen[t] = DEFAULT_TERRAIN_COLOR[t].G;
- mapColorsBlue[t] = DEFAULT_TERRAIN_COLOR[t].B;
- continue;
- }
- int npixeis = 0;
- int cR = 0;
- int cG = 0;
- int cB = 0;
- BitmapData bmdata = detailTexture[t].LockBits(new Rectangle(0, 0, 16, 16),
- ImageLockMode.ReadOnly, detailTexture[t].PixelFormat);
- npixeis = bmdata.Height * bmdata.Width;
- int ylen = bmdata.Height * bmdata.Stride;
- unsafe
- {
- for(int y = 0; y < ylen; y += bmdata.Stride)
- {
- byte* ptrc = (byte*)bmdata.Scan0 + y;
- for(int x = 0 ; x < bmdata.Width; ++x, ptrc += 3)
- {
- cR += ptrc[0];
- cG += ptrc[1];
- cB += ptrc[2];
- }
- }
- }
- detailTexture[t].UnlockBits(bmdata);
- detailTexture[t].Dispose();
- mapColorsRed[t] = (byte)Math.Clamp(cR / npixeis, 0 , 255);
- mapColorsGreen[t] = (byte)Math.Clamp(cG / npixeis, 0 , 255);
- mapColorsBlue[t] = (byte)Math.Clamp(cB / npixeis, 0 , 255);
- }
- }
- else
- {
- // Fill in any missing textures with a solid color
- for (int i = 0; i < 4; i++)
- {
- if (detailTexture[i] == null)
- {
- m_log.DebugFormat("{0} Missing terrain texture for layer {1}. Filling with solid default color", LogHeader, i);
- // Create a solid color texture for this layer
- detailTexture[i] = new Bitmap(16, 16, PixelFormat.Format24bppRgb);
- using(Graphics gfx = Graphics.FromImage(detailTexture[i]))
- {
- using(SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i]))
- gfx.FillRectangle(brush, 0, 0, 16, 16);
- }
- }
- else
- {
- if(detailTexture[i].Width != 16 || detailTexture[i].Height != 16)
- {
- using(Bitmap origBitmap = detailTexture[i])
- detailTexture[i] = Util.ResizeImageSolid(origBitmap, 16, 16);
- }
- }
- //detailTexture[i].Save("terr" + i.ToString()+".png", ImageFormat.Png);
- }
- }
- }
- else
- {
- usecolors = true;
- for(int t = 0; t < 4; t++)
- {
- mapColorsRed[t] = DEFAULT_TERRAIN_COLOR[t].R;
- mapColorsGreen[t] = DEFAULT_TERRAIN_COLOR[t].G;
- mapColorsBlue[t] = DEFAULT_TERRAIN_COLOR[t].B;
- }
- }
- #region Layer Map
- float xFactor = terrain.Width / twidth;
- float yFactor = terrain.Height / theight;
- #endregion Layer Map
- #region Texture Compositing
- Bitmap output = new Bitmap(twidth, theight, PixelFormat.Format24bppRgb);
- BitmapData outputData = output.LockBits(new Rectangle(0, 0, twidth, theight), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
- // Unsafe work as we lock down the source textures for quicker access and access the
- // pixel data directly
- float invtwitdthMinus1 = 1.0f / (twidth - 1);
- float invtheightMinus1 = 1.0f / (theight - 1);
- int ty;
- int tx;
- float pctx;
- float pcty;
- float height;
- float layer;
- float layerDiff;
- int l0;
- uint yglobalpos;
- if(usecolors)
- {
- float a;
- float b;
- int l1;
- unsafe
- {
- byte* ptrO;
- for(int y = 0; y < theight; ++y)
- {
- pcty = y * invtheightMinus1;
- ptrO = (byte*)outputData.Scan0.ToPointer() + y * outputData.Stride;
- ty = (int)(y * yFactor);
- yglobalpos = (uint)ty + regionPositionY;
- for(int x = 0; x < twidth; ++x, ptrO +=3)
- {
- tx = (int)(x * xFactor);
- pctx = x * invtwitdthMinus1;
- height = (float)terrain[tx, ty];
- layer = getLayerTex(height, pctx, pcty,
- (uint)tx + regionPositionX, yglobalpos,
- startHeights, heightRanges);
- // Select two textures
- l0 = (int)layer;
- layerDiff = layer - l0;
- if (l0 >= 2)
- l1 = 3;
- else
- l1 = l0 + 1;
- a = mapColorsRed[l0];
- b = mapColorsRed[l1];
- ptrO[0] = (byte)(a + layerDiff * (b - a));
- a = mapColorsGreen[l0];
- b = mapColorsGreen[l1];
- ptrO[1] = (byte)(a + layerDiff * (b - a));
- a = mapColorsBlue[l0];
- b = mapColorsBlue[l1];
- ptrO[2] = (byte)(a + layerDiff * (b - a));
- }
- }
- }
- }
- else
- {
- float aB;
- float aG;
- float aR;
- float bB;
- float bG;
- float bR;
- unsafe
- {
- // Get handles to all of the texture data arrays
- BitmapData[] datas = new BitmapData[]
- {
- detailTexture[0].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat),
- detailTexture[1].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat),
- detailTexture[2].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat),
- detailTexture[3].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat)
- };
- byte* ptr;
- byte* ptrO;
- for (int y = 0; y < theight; y++)
- {
- pcty = y * invtheightMinus1;
- ty = (int)(y * yFactor);
- int ypatch = (ty & 0x0f) * datas[0].Stride;
- yglobalpos = (uint)ty + regionPositionY;
- ptrO = (byte*)outputData.Scan0.ToPointer() + y * outputData.Stride;
- for (int x = 0; x < twidth; x++, ptrO += 3)
- {
- tx = (int)(x * xFactor);
- pctx = x * invtwitdthMinus1;
- height = (float)terrain[tx, ty];
- layer = getLayerTex(height, pctx, pcty,
- (uint)tx + regionPositionX, yglobalpos,
- startHeights, heightRanges);
- // Select two textures
- l0 = (int)layer;
- layerDiff = layer - l0;
- int patchOffset = (tx & 0x0f) * 3 + ypatch;
- ptr = (byte*)datas[l0].Scan0.ToPointer() + patchOffset;
- aB = ptr[0];
- aG = ptr[1];
- aR = ptr[2];
- if(l0 >= 2 )
- l0 = 3;
- else
- l0++;
- ptr = (byte*)datas[l0].Scan0.ToPointer() + patchOffset;
- bB = ptr[0];
- bG = ptr[1];
- bR = ptr[2];
- // Interpolate between the two selected textures
- ptrO[0] = (byte)(aB + layerDiff * (bB - aB));
- ptrO[1] = (byte)(aG + layerDiff * (bG - aG));
- ptrO[2] = (byte)(aR + layerDiff * (bR - aR));
- }
- }
- for (int i = 0; i < detailTexture.Length; i++)
- detailTexture[i].UnlockBits(datas[i]);
- }
- for(int i = 0; i < detailTexture.Length; i++)
- if(detailTexture[i] != null)
- detailTexture[i].Dispose();
- }
- output.UnlockBits(outputData);
- //output.Save("terr.jpg",ImageFormat.Jpeg);
- #endregion Texture Compositing
- return output;
- }
- [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
- private static float getLayerTex(float height, float pctX, float pctY, uint X, uint Y,
- float[] startHeights, float[] heightRanges)
- {
- // Use bilinear interpolation between the four corners of start height and
- // height range to select the current values at this position
- float startHeight = ImageUtils.Bilinear(startHeights, pctX, pctY);
- if (float.IsNaN(startHeight))
- return 0;
- startHeight = Utils.Clamp(startHeight, 0f, 255f);
- float heightRange = ImageUtils.Bilinear(heightRanges, pctX, pctY);
- heightRange = Utils.Clamp(heightRange, 0f, 255f);
- if(heightRange == 0f)
- return 0;
- // Generate two frequencies of perlin noise based on our global position
- // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting
- float sX = X * 0.20319f;
- float sY = Y * 0.20319f;
- float noise = Perlin.noise2(sX * 0.222222f, sY * 0.222222f) * 13.0f;
- noise += Perlin.turbulence2(sX, sY, 2f) * 4.5f;
- // Combine the current height, generated noise, start height, and height range parameters, then scale all of it
- float layer = ((height + noise - startHeight) / heightRange) * 4f;
- return Utils.Clamp(layer, 0f, 3f);
- }
- }
- }
|