From 50bf3618a32bf72bcbd88e56610cf955737ff388 Mon Sep 17 00:00:00 2001 From: Teravus Ovares Date: Fri, 22 Aug 2008 22:04:43 +0000 Subject: [PATCH] * Homer's amazing terrain MapTileRenderer. Thanks Homer! * By default, texture rendering is on. This may be affected by using secure assets since your region hasn't registered with the gridserver before it asks for texture assets. It might also be affected by a slow asset server, so consider this release experimental. * Defined interface IMapTileTerrainRenderer. * Extracted "old" shaded maptile terrain rendering into ShadedMapTileRenderer; streamlined it a bit and added "highlight" rendering to its "shadow" rendering. * Added "new" terrain-texture based maptile terrain rendering (TexturedMapTileRenderer); made TerrainUtil.InterpolatedNoise public * Adapted MapImageModule to allow switching between those two by configuration * Added configuration option to OpenSim.ini.example --- CONTRIBUTORS.txt | 1 + .../Modules/World/Terrain/TerrainUtil.cs | 4 +- .../World/WorldMap/IMapTileTerrainRenderer.cs | 39 ++ .../Modules/World/WorldMap/MapImageModule.cs | 203 ++------- .../World/WorldMap/ShadedMapTileRenderer.cs | 254 ++++++++++++ .../World/WorldMap/TexturedMapTileRenderer.cs | 386 ++++++++++++++++++ bin/OpenSim.ini.example | 2 + 7 files changed, 707 insertions(+), 182 deletions(-) create mode 100644 OpenSim/Region/Environment/Modules/World/WorldMap/IMapTileTerrainRenderer.cs create mode 100644 OpenSim/Region/Environment/Modules/World/WorldMap/ShadedMapTileRenderer.cs create mode 100644 OpenSim/Region/Environment/Modules/World/WorldMap/TexturedMapTileRenderer.cs diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 83678c89b5..a3413a1c53 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -59,6 +59,7 @@ Patches * YZh * Grumly57 * Junta Kohime +* Homer_Horwitz LSL Devs diff --git a/OpenSim/Region/Environment/Modules/World/Terrain/TerrainUtil.cs b/OpenSim/Region/Environment/Modules/World/Terrain/TerrainUtil.cs index 4cb828ebaf..def28eb614 100644 --- a/OpenSim/Region/Environment/Modules/World/Terrain/TerrainUtil.cs +++ b/OpenSim/Region/Environment/Modules/World/Terrain/TerrainUtil.cs @@ -95,7 +95,7 @@ namespace OpenSim.Region.Environment.Modules.World.Terrain return (x * (1.0 - z)) + (y * z); } - private static double InterpolatedNoise(double x, double y) + public static double InterpolatedNoise(double x, double y) { int integer_X = (int) (x); double fractional_X = x - integer_X; @@ -128,4 +128,4 @@ namespace OpenSim.Region.Environment.Modules.World.Terrain return total; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/IMapTileTerrainRenderer.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/IMapTileTerrainRenderer.cs new file mode 100644 index 0000000000..bebcc5256a --- /dev/null +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/IMapTileTerrainRenderer.cs @@ -0,0 +1,39 @@ +/* + * 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 OpenSim 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.Drawing; +using OpenSim.Region.Environment.Scenes; +using Nini.Config; + +namespace OpenSim.Region.Environment +{ + public interface IMapTileTerrainRenderer + { + void Initialise(Scene scene, IConfigSource config); + void TerrainToBitmap(Bitmap mapbmp); + } +} diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs index 38c52f0de2..2ae4174f70 100644 --- a/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs @@ -69,29 +69,47 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap private Scene m_scene; private IConfigSource m_config; + private IMapTileTerrainRenderer terrainRenderer; #region IMapImageGenerator Members public byte[] WriteJpeg2000Image(string gradientmap) { byte[] imageData = null; - Bitmap mapbmp = new Bitmap(256, 256); - - //Bitmap bmp = TerrainToBitmap(gradientmap); - mapbmp = TerrainToBitmap2(m_scene,mapbmp); bool drawPrimVolume = true; + bool textureTerrain = true; try { IConfig startupConfig = m_config.Configs["Startup"]; - drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", true); + drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", drawPrimVolume); + textureTerrain = startupConfig.GetBoolean("TextureOnMapTile", textureTerrain); } catch (Exception) { m_log.Warn("Failed to load StartupConfig"); } + if (textureTerrain) + { + terrainRenderer = new TexturedMapTileRenderer(); + } + else + { + terrainRenderer = new ShadedMapTileRenderer(); + } + terrainRenderer.Initialise(m_scene, m_config); + + Bitmap mapbmp = new Bitmap(256, 256); + //long t = System.Environment.TickCount; + //for(int i = 0; i < 10; ++i) { + terrainRenderer.TerrainToBitmap(mapbmp); + //} + //t = System.Environment.TickCount - t; + //m_log.InfoFormat("[MAPTILE] generation of 10 maptiles needed {0} ms", t); + + if (drawPrimVolume) { DrawObjectVolume(m_scene, mapbmp); @@ -181,181 +199,6 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap // } // } - private Bitmap TerrainToBitmap2(Scene whichScene, Bitmap mapbmp) - { - int tc = System.Environment.TickCount; - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); - - double[,] hm = whichScene.Heightmap.GetDoubles(); - bool ShadowDebugContinue = true; - - bool terraincorruptedwarningsaid = false; - - float low = 255; - float high = 0; - for (int x = 0; x < 256; x++) - { - for (int y = 0; y < 256; y++) - { - float hmval = (float)hm[x, y]; - if (hmval < low) - low = hmval; - if (hmval > high) - high = hmval; - } - } - - float mid = (high + low) * 0.5f; - - // temporary initializer - float hfvalue = (float)whichScene.RegionInfo.RegionSettings.WaterHeight; - float hfvaluecompare = hfvalue; - float hfdiff = hfvalue; - int hfdiffi = 0; - - - for (int x = 0; x < 256; x++) - { - //int tc = System.Environment.TickCount; - for (int y = 0; y < 256; y++) - { - float heightvalue = (float)hm[x, y]; - - if (heightvalue > (float)whichScene.RegionInfo.RegionSettings.WaterHeight) - { - // scale height value - heightvalue = low + mid * (heightvalue - low) / mid; - - if (heightvalue > 255) - heightvalue = 255; - - if (heightvalue < 0) - heightvalue = 0; - - if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) - heightvalue = 0; - - try - { - Color green = Color.FromArgb((int)heightvalue, 100, (int)heightvalue); - - // Y flip the cordinates - mapbmp.SetPixel(x, (256 - y) - 1, green); - - //X - // . - // - // Shade the terrain for shadows - if ((x - 1 > 0) && (y - 1 > 0)) - { - hfvalue = (float)hm[x, y]; - hfvaluecompare = (float)hm[x - 1, y - 1]; - - if (Single.IsInfinity(hfvalue) || Single.IsNaN(hfvalue)) - hfvalue = 0; - - if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) - hfvaluecompare = 0; - - hfdiff = hfvaluecompare - hfvalue; - - if (hfdiff > 0.3f) - { - } - else if (hfdiff < -0.3f) - { - // We have to desaturate and blacken the land at the same time - // we use floats, colors use bytes, so shrink are space down to - // 0-255 - - - try - { - hfdiffi = Math.Abs((int)((hfdiff * 4) + (hfdiff * 0.5))) + 1; - if (hfdiff % 1 != 0) - { - hfdiffi = hfdiffi + Math.Abs((int)(((hfdiff % 1) * 0.5f) * 10f) - 1); - } - } - catch (System.OverflowException) - { - m_log.Debug("[MAPTILE]: Shadow failed at value: " + hfdiff.ToString()); - ShadowDebugContinue = false; - } - - if (ShadowDebugContinue) - { - if ((256 - y) - 1 > 0) - { - Color Shade = mapbmp.GetPixel(x - 1, (256 - y) - 1); - - int r = Shade.R; - - int g = Shade.G; - int b = Shade.B; - Shade = Color.FromArgb((r - hfdiffi > 0) ? r - hfdiffi : 0, (g - hfdiffi > 0) ? g - hfdiffi : 0, (b - hfdiffi > 0) ? b - hfdiffi : 0); - mapbmp.SetPixel(x - 1, (256 - y) - 1, Shade); - } - } - } - } - } - catch (System.ArgumentException) - { - if (!terraincorruptedwarningsaid) - { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", whichScene.RegionInfo.RegionName); - terraincorruptedwarningsaid = true; - } - Color black = Color.Black; - mapbmp.SetPixel(x, (256 - y) - 1, black); - } - } - else - { - // We're under the water level with the terrain, so paint water instead of land - - // Y flip the cordinates - heightvalue = (float)whichScene.RegionInfo.RegionSettings.WaterHeight - heightvalue; - if (heightvalue > 19) - heightvalue = 19; - if (heightvalue < 0) - heightvalue = 0; - - heightvalue = 100 - (heightvalue * 100) / 19; - - if (heightvalue > 255) - heightvalue = 255; - - if (heightvalue < 0) - heightvalue = 0; - - if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) - heightvalue = 0; - - try - { - Color water = Color.FromArgb((int)heightvalue, (int)heightvalue, 255); - mapbmp.SetPixel(x, (256 - y) - 1, water); - } - catch (System.ArgumentException) - { - if (!terraincorruptedwarningsaid) - { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", whichScene.RegionInfo.RegionName); - terraincorruptedwarningsaid = true; - } - Color black = Color.Black; - mapbmp.SetPixel(x, (256 - y) - 1, black); - } - } - } - } - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (System.Environment.TickCount - tc) + " ms"); - - return mapbmp; - } - private Bitmap DrawObjectVolume(Scene whichScene, Bitmap mapbmp) { int tc = 0; diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/ShadedMapTileRenderer.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/ShadedMapTileRenderer.cs new file mode 100644 index 0000000000..253a7f5369 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/ShadedMapTileRenderer.cs @@ -0,0 +1,254 @@ +/* + * 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 OpenSim 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.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using Axiom.Math; +using Nini.Config; +using log4net; +using OpenJPEGNet; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using libsecondlife; + +namespace OpenSim.Region.Environment.Modules.World.WorldMap +{ + public class ShadedMapTileRenderer : IMapTileTerrainRenderer + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + //private IConfigSource m_config; // not used currently + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + // m_config = config; // not used currently + } + + public void TerrainToBitmap(Bitmap mapbmp) + { + int tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + + double[,] hm = m_scene.Heightmap.GetDoubles(); + bool ShadowDebugContinue = true; + + bool terraincorruptedwarningsaid = false; + + float low = 255; + float high = 0; + for (int x = 0; x < 256; x++) + { + for (int y = 0; y < 256; y++) + { + float hmval = (float)hm[x, y]; + if (hmval < low) + low = hmval; + if (hmval > high) + high = hmval; + } + } + + float waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; + + for (int x = 0; x < 256; x++) + { + for (int y = 0; y < 256; y++) + { + // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left + int yr = 255 - y; + + float heightvalue = (float)hm[x, y]; + + if (heightvalue > waterHeight) + { + // scale height value + // No, that doesn't scale it: + // heightvalue = low + mid * (heightvalue - low) / mid; => low + (heightvalue - low) * mid / mid = low + (heightvalue - low) * 1 = low + heightvalue - low = heightvalue + + + + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0; + else if (heightvalue > 255f) + heightvalue = 255f; + else if (heightvalue < 0f) + heightvalue = 0f; + + Color color = Color.FromArgb((int)heightvalue, 100, (int)heightvalue); + + mapbmp.SetPixel(x, yr, color); + + try + { + //X + // . + // + // Shade the terrain for shadows + if (x < 255 && yr < 255) + { + float hfvalue = (float)hm[x, y]; + float hfvaluecompare = 0f; + + if ((x + 1 < 256) && (y + 1 < 256)) + { + hfvaluecompare = (float)hm[x + 1, y + 1]; // light from north-east => look at land height there + } + if (Single.IsInfinity(hfvalue) || Single.IsNaN(hfvalue)) + hfvalue = 0f; + + if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) + hfvaluecompare = 0f; + + float hfdiff = hfvalue - hfvaluecompare; // => positive if NE is lower, negative if here is lower + + int hfdiffi = 0; + int hfdiffihighlight = 0; + float highlightfactor = 0.18f; + + try + { + // hfdiffi = Math.Abs((int)((hfdiff * 4) + (hfdiff * 0.5))) + 1; + hfdiffi = Math.Abs((int)(hfdiff * 4.5f)) + 1; + if (hfdiff % 1f != 0) + { + // hfdiffi = hfdiffi + Math.Abs((int)(((hfdiff % 1) * 0.5f) * 10f) - 1); + hfdiffi = hfdiffi + Math.Abs((int)((hfdiff % 1f) * 5f) - 1); + } + + hfdiffihighlight = Math.Abs((int)((hfdiff * highlightfactor) * 4.5f)) + 1; + if (hfdiff % 1f != 0) + { + // hfdiffi = hfdiffi + Math.Abs((int)(((hfdiff % 1) * 0.5f) * 10f) - 1); + hfdiffihighlight = hfdiffihighlight + Math.Abs((int)(((hfdiff * highlightfactor) % 1f) * 5f) - 1); + } + } + catch (System.OverflowException) + { + m_log.Debug("[MAPTILE]: Shadow failed at value: " + hfdiff.ToString()); + ShadowDebugContinue = false; + } + + if (hfdiff > 0.3f) + { + // NE is lower than here + // We have to desaturate and lighten the land at the same time + // we use floats, colors use bytes, so shrink are space down to + // 0-255 + + if (ShadowDebugContinue) + { + int r = color.R; + int g = color.G; + int b = color.B; + color = Color.FromArgb((r + hfdiffihighlight < 255) ? r + hfdiffihighlight : 255, + (g + hfdiffihighlight < 255) ? g + hfdiffihighlight : 255, + (b + hfdiffihighlight < 255) ? b + hfdiffihighlight : 255); + } + } + else if (hfdiff < -0.3f) + { + // here is lower than NE: + // We have to desaturate and blacken the land at the same time + // we use floats, colors use bytes, so shrink are space down to + // 0-255 + + if (ShadowDebugContinue) + { + if ((x - 1 > 0) && (yr + 1 < 256)) + { + color = mapbmp.GetPixel(x - 1, yr + 1); + int r = color.R; + int g = color.G; + int b = color.B; + color = Color.FromArgb((r - hfdiffi > 0) ? r - hfdiffi : 0, + (g - hfdiffi > 0) ? g - hfdiffi : 0, + (b - hfdiffi > 0) ? b - hfdiffi : 0); + + mapbmp.SetPixel(x-1, yr+1, color); + } + + } + } + } + } + catch (System.ArgumentException) + { + if (!terraincorruptedwarningsaid) + { + m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + terraincorruptedwarningsaid = true; + } + color = Color.Black; + mapbmp.SetPixel(x, yr, color); + } + + } + else + { + // We're under the water level with the terrain, so paint water instead of land + + // Y flip the cordinates + heightvalue = waterHeight - heightvalue; + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0f; + else if (heightvalue > 19f) + heightvalue = 19f; + else if (heightvalue < 0f) + heightvalue = 0f; + + heightvalue = 100f - (heightvalue * 100f) / 19f; + + try + { + Color water = Color.FromArgb((int)heightvalue, (int)heightvalue, 255); + mapbmp.SetPixel(x, yr, water); + } + catch (System.ArgumentException) + { + if (!terraincorruptedwarningsaid) + { + m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + terraincorruptedwarningsaid = true; + } + Color black = Color.Black; + mapbmp.SetPixel(x, (256 - y) - 1, black); + } + } + } + } + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (System.Environment.TickCount - tc) + " ms"); + } + } +} diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/TexturedMapTileRenderer.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/TexturedMapTileRenderer.cs new file mode 100644 index 0000000000..957a1dfaa6 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/TexturedMapTileRenderer.cs @@ -0,0 +1,386 @@ +/* + * 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 OpenSim 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.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using Axiom.Math; +using Nini.Config; +using log4net; +using OpenJPEGNet; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using OpenSim.Region.Environment.Modules.World.Terrain; +using libsecondlife; + +namespace OpenSim.Region.Environment.Modules.World.WorldMap +{ + // Hue, Saturation, Value; used for color-interpolation + struct HSV { + public float h; + public float s; + public float v; + + public HSV(float h, float s, float v) + { + this.h = h; + this.s = s; + this.v = v; + } + + // (for info about algorithm, see http://en.wikipedia.org/wiki/HSL_and_HSV) + public HSV(Color c) + { + float r = c.R / 255f; + float g = c.G / 255f; + float b = c.B / 255f; + float max = Math.Max(Math.Max(r, g), b); + float min = Math.Min(Math.Min(r, g), b); + float diff = max - min; + + if (max == min) h = 0f; + else if (max == r) h = (g - b) / diff * 60f; + else if (max == g) h = (b - r) / diff * 60f + 120f; + else h = (r - g) / diff * 60f + 240f; + if (h < 0f) h += 360f; + + if (max == 0f) s = 0f; + else s = diff / max; + + v = max; + } + + // (for info about algorithm, see http://en.wikipedia.org/wiki/HSL_and_HSV) + public Color toColor() + { + if(s < 0f) Console.WriteLine("S < 0: " + s); + else if(s > 1f) Console.WriteLine("S > 1: " + s); + if(v < 0f) Console.WriteLine("V < 0: " + v); + else if(v > 1f) Console.WriteLine("V > 1: " + v); + + float f = h / 60f; + int sector = (int)f % 6; + f = f - (int)f; + int pi = (int)(v * (1f - s) * 255f); + int qi = (int)(v * (1f - s * f) * 255f); + int ti = (int)(v * (1f - (1f - f) * s) * 255f); + int vi = (int)(v * 255f); + + switch(sector) + { + case 0: + return Color.FromArgb(vi, ti, pi); + case 1: + return Color.FromArgb(qi, vi, pi); + case 2: + return Color.FromArgb(pi, vi, ti); + case 3: + return Color.FromArgb(pi, qi, vi); + case 4: + return Color.FromArgb(ti, pi, vi); + default: + return Color.FromArgb(vi, pi, qi); + } + } + } + + public class TexturedMapTileRenderer : IMapTileTerrainRenderer + { + #region Constants + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // some hardcoded terrain UUIDs that work with SL 1.20 (the four default textures and "Blank"). + // The color-values were choosen because they "look right" (at least to me) ;-) + private static readonly LLUUID defaultTerrainTexture1 = new LLUUID("0bc58228-74a0-7e83-89bc-5c23464bcec5"); + private static readonly Color defaultColor1 = Color.FromArgb(165, 137, 118); + private static readonly LLUUID defaultTerrainTexture2 = new LLUUID("63338ede-0037-c4fd-855b-015d77112fc8"); + private static readonly Color defaultColor2 = Color.FromArgb(69, 89, 49); + private static readonly LLUUID defaultTerrainTexture3 = new LLUUID("303cd381-8560-7579-23f1-f0a880799740"); + private static readonly Color defaultColor3 = Color.FromArgb(162, 154, 141); + private static readonly LLUUID defaultTerrainTexture4 = new LLUUID("53a2f406-4895-1d13-d541-d2e3b86bc19c"); + private static readonly Color defaultColor4 = Color.FromArgb(200, 200, 200); + private static readonly LLUUID blankTerrainTexture = new LLUUID("5748decc-f629-461c-9a36-a35a221fe21f"); + + #endregion + + + private Scene m_scene; + // private IConfigSource m_config; // not used currently + + // mapping from texture UUIDs to averaged color. This will contain 5-9 values, in general; new values are only + // added when the terrain textures are changed in the estate dialog and a new map is generated (and will stay in + // that map until the region-server restarts. This could be considered a memory-leak, but it's a *very* small one. + // TODO does it make sense to use a "real" cache and regenerate missing entries on fetch? + private Dictionary m_mapping; + + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + // m_config = source; // not used currently + m_mapping = new Dictionary(); + m_mapping.Add(defaultTerrainTexture1, defaultColor1); + m_mapping.Add(defaultTerrainTexture2, defaultColor2); + m_mapping.Add(defaultTerrainTexture3, defaultColor3); + m_mapping.Add(defaultTerrainTexture4, defaultColor4); + m_mapping.Add(blankTerrainTexture, Color.White); + } + + #region Helpers + // This fetches the texture from the asset server synchroneously. That should be ok, as we + // call map-creation only in those places: + // - on start: We can wait here until the asset server returns the texture + // TODO (- on "map" command: We are in the command-line thread, we will wait for completion anyway) + // TODO (- on "automatic" update after some change: We are called from the mapUpdateTimer here and + // will wait anyway) + private Bitmap fetchTexture(LLUUID id) + { + AssetBase asset = m_scene.AssetCache.GetAsset(id, true); + m_log.DebugFormat("Fetched texture {0}, found: {1}", id, asset != null); + if(asset == null) return null; + return new Bitmap(OpenJPEG.DecodeToImage(asset.Data)); + } + + // Compute the average color of a texture. + private Color computeAverageColor(Bitmap bmp) + { + // we have 256 x 256 pixel, each with 256 possible color-values per + // color-channel, so 2^24 is the maximum value we can get, adding everything. + // int is be big enough for that. + int r = 0, g = 0, b = 0; + for(int y = 0; y < bmp.Height; ++y) + { + for(int x = 0; x < bmp.Width; ++x) + { + Color c = bmp.GetPixel(x, y); + r += (int)c.R & 0xff; + g += (int)c.G & 0xff; + b += (int)c.B & 0xff; + } + } + + int pixels = bmp.Width * bmp.Height; + return Color.FromArgb(r / pixels, g / pixels, b / pixels); + } + + // return either the average color of the texture, or the defaultColor if the texturID is invalid + // or the texture couldn't be found + private Color computeAverageColor(LLUUID textureID, Color defaultColor) { + if (textureID == LLUUID.Zero) return defaultColor; // not set + if (m_mapping.ContainsKey(textureID)) return m_mapping[textureID]; // one of the predefined textures + + Bitmap bmp = fetchTexture(textureID); + Color color = bmp == null ? defaultColor : computeAverageColor(bmp); + // store it for future reference + m_mapping[textureID] = color; + + return color; + } + + // S-curve: f(x) = 3x² - 2x³: + // f(0) = 0, f(0.5) = 0.5, f(1) = 1, + // f'(x) = 0 at x = 0 and x = 1; f'(0.5) = 1.5, + // f''(0.5) = 0, f''(x) != 0 for x != 0.5 + private float S(float v) { + return (v * v * (3f - 2f * v)); + } + + // interpolate two colors in HSV space and return the resulting color + private HSV interpolateHSV(ref HSV c1, ref HSV c2, float ratio) { + if(ratio <= 0f) return c1; + if(ratio >= 1f) return c2; + + // make sure we are on the same side on the hue-circle for interpolation + // We change the hue of the parameters here, but we don't change the color + // represented by that value + if(c1.h - c2.h > 180f) c1.h -= 360f; + else if(c2.h - c1.h > 180f) c1.h += 360f; + + return new HSV(c1.h * (1f - ratio) + c2.h * ratio, + c1.s * (1f - ratio) + c2.s * ratio, + c1.v * (1f - ratio) + c2.v * ratio); + } + + // the heigthfield might have some jumps in values. Rendered land is smooth, though, + // as a slope is rendered at that place. So average 4 neighbour values to emulate that. + private float getHeight(double[,] hm, int x, int y) { + if (x < 255 && y < 255) + return (float)(hm[x, y] * .444 + (hm[x + 1, y] + hm[x, y + 1]) * .222 + hm[x + 1, y +1] * .112); + else + return (float)hm[x, y]; + } + #endregion + + public void TerrainToBitmap(Bitmap mapbmp) + { + int tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + + // These textures should be in the AssetCache anyway, as every client conneting to this + // region needs them. Except on start, when the map is recreated (before anyone connected), + // and on change of the estate settings (textures and terrain values), when the map should + // be recreated. + RegionSettings settings = m_scene.RegionInfo.RegionSettings; + + // the four terrain colors as HSVs for interpolation + HSV hsv1 = new HSV(computeAverageColor(settings.TerrainTexture1, defaultColor1)); + HSV hsv2 = new HSV(computeAverageColor(settings.TerrainTexture2, defaultColor2)); + HSV hsv3 = new HSV(computeAverageColor(settings.TerrainTexture3, defaultColor3)); + HSV hsv4 = new HSV(computeAverageColor(settings.TerrainTexture4, defaultColor4)); + + float levelNElow = (float)settings.Elevation1NE; + float levelNEhigh = (float)settings.Elevation2NE; + + float levelNWlow = (float)settings.Elevation1NW; + float levelNWhigh = (float)settings.Elevation2NW; + + float levelSElow = (float)settings.Elevation1SE; + float levelSEhigh = (float)settings.Elevation2SE; + + float levelSWlow = (float)settings.Elevation1SW; + float levelSWhigh = (float)settings.Elevation2SW; + + float waterHeight = (float)settings.WaterHeight; + + double[,] hm = m_scene.Heightmap.GetDoubles(); + + for (int x = 0; x < 256; x++) + { + float columnRatio = x / 255f; // 0 - 1, for interpolation + for (int y = 0; y < 256; y++) + { + float rowRatio = y / 255f; // 0 - 1, for interpolation + + // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left + int yr = 255 - y; + + float heightvalue = getHeight(hm, x, y); + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0; + + if (heightvalue > waterHeight) + { + // add a bit noise for breaking up those flat colors: + // - a large-scale noise, for the "patches" (using an doubled s-curve for sharper contrast) + // - a small-scale noise, for bringing in some small scale variation + //float bigNoise = (float)TerrainUtil.InterpolatedNoise(x / 8.0, y / 8.0) * .5f + .5f; // map to 0.0 - 1.0 + //float smallNoise = (float)TerrainUtil.InterpolatedNoise(x + 33, y + 43) * .5f + .5f; + //float hmod = heightvalue + smallNoise * 3f + S(S(bigNoise)) * 10f; + float hmod = + heightvalue + + (float)TerrainUtil.InterpolatedNoise(x + 33, y + 43) * 1.5f + 1.5f + // 0 - 3 + S(S((float)TerrainUtil.InterpolatedNoise(x / 8.0, y / 8.0) * .5f + .5f)) * 10f; // 0 - 10 + + // find the low/high values for this point (interpolated bilinearily) + // (and remember, x=0,y=0 is SW) + float low = levelSWlow * (1f - rowRatio) * (1f - columnRatio) + + levelSElow * (1f - rowRatio) * columnRatio + + levelNWlow * rowRatio * (1f - columnRatio) + + levelNElow * rowRatio * columnRatio; + float high = levelSWhigh * (1f - rowRatio) * (1f - columnRatio) + + levelSEhigh * (1f - rowRatio) * columnRatio + + levelNWhigh * rowRatio * (1f - columnRatio) + + levelNEhigh * rowRatio * columnRatio; + if (high < low) + { + // someone tried to fool us. High value should be higher than low every time + float tmp = high; + high = low; + low = tmp; + } + + HSV hsv; + if(hmod <= low) hsv = hsv1; // too low + else if(hmod >= high) hsv = hsv4; // too high + else + { + // HSV-interpolate along the colors + // first, rescale h to 0.0 - 1.0 + hmod = (hmod - low) / (high - low); + // now we have to split: 0.00 => color1, 0.33 => color2, 0.67 => color3, 1.00 => color4 + if(hmod < 1f/3f) hsv = interpolateHSV(ref hsv1, ref hsv2, hmod * 3f); + else if(hmod < 2f/3f) hsv = interpolateHSV(ref hsv2, ref hsv3, (hmod * 3f) - 1f); + else hsv = interpolateHSV(ref hsv3, ref hsv4, (hmod * 3f) - 2f); + } + + // Shade the terrain for shadows + if (x < 255 && y < 255) + { + float hfvaluecompare = getHeight(hm, x + 1, y + 1); // light from north-east => look at land height there + if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) + hfvaluecompare = 0f; + + float hfdiff = heightvalue - hfvaluecompare; // => positive if NE is lower, negative if here is lower + hfdiff *= 0.06f; // some random factor so "it looks good" + if (hfdiff > 0.02f) + { + float highlightfactor = 0.18f; + // NE is lower than here + // We have to desaturate and lighten the land at the same time + hsv.s = (hsv.s - (hfdiff * highlightfactor) > 0f) ? hsv.s - (hfdiff * highlightfactor) : 0f; + hsv.v = (hsv.v + (hfdiff * highlightfactor) < 1f) ? hsv.v + (hfdiff * highlightfactor) : 1f; + } + else if (hfdiff < -0.02f) + { + // here is lower than NE: + // We have to desaturate and blacken the land at the same time + hsv.s = (hsv.s + hfdiff > 0f) ? hsv.s + hfdiff : 0f; + hsv.v = (hsv.v + hfdiff > 0f) ? hsv.v + hfdiff : 0f; + } + } + mapbmp.SetPixel(x, yr, hsv.toColor()); + } + else + { + // We're under the water level with the terrain, so paint water instead of land + + heightvalue = waterHeight - heightvalue; + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0f; + else if (heightvalue > 19f) + heightvalue = 19f; + else if (heightvalue < 0f) + heightvalue = 0f; + + heightvalue = 100f - (heightvalue * 100f) / 19f; // 0 - 19 => 100 - 0 + + Color water = Color.FromArgb((int)heightvalue, (int)heightvalue, 255); + mapbmp.SetPixel(x, yr, water); + } + } + } + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (System.Environment.TickCount - tc) + " ms"); + } + } +} diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 01633fad2f..eb1cc0ba80 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -25,6 +25,8 @@ region_info_source = "filesystem" ; Draw objects on maptile. This step might take a long time if you've got a huge amount of ; objects, so you can turn it off here if you'd like. DrawPrimOnMapTile = true +; Use terrain texture for maptiles if true, use shaded green if false +TextureOnMapTile = false ; Maximum total size, and maximum size where a prim can be physical NonPhysicalPrimMax = 256