From 0683a07081802105cbc40ca9d75c26e135342f3e Mon Sep 17 00:00:00 2001 From: Teravus Ovares Date: Sun, 17 Aug 2008 18:59:58 +0000 Subject: * Turned on the MapImageModule as opposed to the code in Scene for generating the map image. Copied the code in Scene into the MapImageModule, made a few tweaks to get it to work with the module's interface. * Refactored a few things in Scene to make maptile saving easier to understand. * Added comments to describe the 'clever code' assetcache update without the gridasset being updated so as to lessen possible confusion in patches in the future. * If the MapImageModule isn't loaded, default to the code in Scene still. --- .../Modules/World/WorldMap/MapImageModule.cs | 412 ++++++++++++++++++++- OpenSim/Region/Environment/Scenes/Scene.cs | 166 ++++----- 2 files changed, 478 insertions(+), 100 deletions(-) (limited to 'OpenSim') diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs index 68c8046..a331d37 100644 --- a/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/MapImageModule.cs @@ -26,29 +26,58 @@ */ using System; +using System.Collections.Generic; using System.Drawing; +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 { - internal class MapImageModule : IMapImageGenerator, IRegionModule + public class MapImageModule : IMapImageGenerator, IRegionModule { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; + private IConfigSource m_config; #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; + + try + { + IConfig startupConfig = m_config.Configs["Startup"]; + drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", true); + } + catch (Exception) + { + m_log.Warn("Failed to load StartupConfig"); + } + + if (drawPrimVolume) + { + DrawObjectVolume(m_scene, mapbmp); + } - Bitmap bmp = TerrainToBitmap(gradientmap); try { - imageData = OpenJPEG.EncodeFromImage(bmp, true); + imageData = OpenJPEG.EncodeFromImage(mapbmp, true); } catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke { @@ -65,6 +94,7 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap public void Initialise(Scene scene, IConfigSource source) { m_scene = scene; + m_config = source; m_scene.RegisterModuleInterface(this); } @@ -128,6 +158,381 @@ 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; + //Color prim = Color.FromArgb(120, 120, 120); + //LLVector3 RayEnd = new LLVector3(0, 0, 0); + //LLVector3 RayStart = new LLVector3(0, 0, 0); + //LLVector3 direction = new LLVector3(0, 0, -1); + //Vector3 AXOrigin = new Vector3(); + //Vector3 AXdirection = new Vector3(); + //Ray testRay = new Ray(); + //EntityIntersection rt = new EntityIntersection(); + 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++) + { + //RayEnd = new LLVector3(x, y, 0); + //RayStart = new LLVector3(x, y, 255); + + //direction = LLVector3.Norm(RayEnd - RayStart); + //AXOrigin = new Vector3(RayStart.X, RayStart.Y, RayStart.Z); + //AXdirection = new Vector3(direction.X, direction.Y, direction.Z); + + //testRay = new Ray(AXOrigin, AXdirection); + //rt = m_innerScene.GetClosestIntersectingPrim(testRay); + + //if (rt.HitTF) + //{ + //mapbmp.SetPixel(x, y, prim); + //} + //else + //{ + //float tmpval = (float)hm[x, 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); + //Console.WriteLine("d:" + hfdiff.ToString() + ", i:" + hfdiffi + ", pos: " + x + "," + y + " - R:" + Shade.R.ToString() + ", G:" + Shade.G.ToString() + ", B:" + Shade.G.ToString()); + 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 + { + // 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); + } + } + } + //} + + //tc = System.Environment.TickCount - tc; + //m_log.Info("[MAPTILE]: Completed One row in " + tc + " ms"); + } + 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; + double[,] hm = whichScene.Heightmap.GetDoubles(); + tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); + List objs = whichScene.GetEntities(); + + lock (objs) + { + foreach (EntityBase obj in objs) + { + // Only draw the contents of SceneObjectGroup + if (obj is SceneObjectGroup) + { + SceneObjectGroup mapdot = (SceneObjectGroup)obj; + Color mapdotspot = Color.Gray; // Default color when prim color is white + // Loop over prim in group + foreach (SceneObjectPart part in mapdot.Children.Values) + { + if (part == null) + continue; + + + // Draw if the object is at least 1 meter wide in any direction + if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) + { + // Try to get the RGBA of the default texture entry.. + // + try + { + if (part == null) + continue; + + if (part.Shape == null) + continue; + + if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree) + continue; // eliminates trees from this since we don't really have a good tree representation + // if you want tree blocks on the map comment the above line and uncomment the below line + //mapdotspot = Color.PaleGreen; + + if (part.Shape.Textures == null) + continue; + + if (part.Shape.Textures.DefaultTexture == null) + continue; + + LLColor texcolor = part.Shape.Textures.DefaultTexture.RGBA; + + // Not sure why some of these are null, oh well. + + int colorr = 255 - (int)(texcolor.R * 255f); + int colorg = 255 - (int)(texcolor.G * 255f); + int colorb = 255 - (int)(texcolor.B * 255f); + + if (!(colorr == 255 && colorg == 255 && colorb == 255)) + { + //Try to set the map spot color + try + { + // If the color gets goofy somehow, skip it *shakes fist at LLColor + mapdotspot = Color.FromArgb(colorr, colorg, colorb); + } + catch (ArgumentException) + { + } + } + } + catch (IndexOutOfRangeException) + { + // Windows Array + } + catch (ArgumentOutOfRangeException) + { + // Mono Array + } + + LLVector3 pos = part.GetWorldPosition(); + + // skip prim outside of retion + if (pos.X < 0f || pos.X > 256f || pos.Y < 0f || pos.Y > 256f) + continue; + + // skip prim in non-finite position + if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || Single.IsInfinity(pos.X) + || Single.IsInfinity(pos.Y)) + continue; + + // Figure out if object is under 256m above the height of the terrain + bool isBelow256AboveTerrain = false; + + try + { + isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); + } + catch (Exception) + { + } + + if (isBelow256AboveTerrain) + { + // Translate scale by rotation so scale is represented properly when object is rotated + Vector3 scale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); + LLQuaternion llrot = part.GetWorldRotation(); + Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); + scale = rot * scale; + + // negative scales don't work in this situation + scale.x = Math.Abs(scale.x); + scale.y = Math.Abs(scale.y); + scale.z = Math.Abs(scale.z); + + // This scaling isn't very accurate and doesn't take into account the face rotation :P + int mapdrawstartX = (int)(pos.X - scale.x); + int mapdrawstartY = (int)(pos.Y - scale.y); + int mapdrawendX = (int)(pos.X + scale.x); + int mapdrawendY = (int)(pos.Y + scale.y); + + // If object is beyond the edge of the map, don't draw it to avoid errors + if (mapdrawstartX < 0 || mapdrawstartX > 255 || mapdrawendX < 0 || mapdrawendX > 255 + || mapdrawstartY < 0 || mapdrawstartY > 255 || mapdrawendY < 0 + || mapdrawendY > 255) + continue; + + int wy = 0; + + bool breakYN = false; // If we run into an error drawing, break out of the + // loop so we don't lag to death on error handling + for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) + { + for (wy = mapdrawstartY; wy < mapdrawendY; wy++) + { + //m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); + try + { + // Remember, flip the y! + mapbmp.SetPixel(wx, (255 - wy), mapdotspot); + } + catch (ArgumentException) + { + breakYN = true; + } + + if (breakYN) + break; + } + + if (breakYN) + break; + } + } // Object is within 256m Z of terrain + } // object is at least a meter wide + } // loop over group children + } // entitybase is sceneobject group + } // foreach loop over entities + } // lock entities objs + + m_log.Info("[MAPTILE]: Generating Maptile Step 2: Done in " + (System.Environment.TickCount - tc) + " ms"); + return mapbmp; + } + + # region Depreciated Maptile Generation. Adam may update this private Bitmap TerrainToBitmap(string gradientmap) { Bitmap gradientmapLd = new Bitmap(gradientmap); @@ -163,5 +568,6 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap return bmp; } } + #endregion } } \ No newline at end of file diff --git a/OpenSim/Region/Environment/Scenes/Scene.cs b/OpenSim/Region/Environment/Scenes/Scene.cs index e060f3d..e8a8a78 100644 --- a/OpenSim/Region/Environment/Scenes/Scene.cs +++ b/OpenSim/Region/Environment/Scenes/Scene.cs @@ -1023,6 +1023,8 @@ namespace OpenSim.Region.Environment.Scenes if (terrain == null) { + #region Fallback default maptile generation + int tc = System.Environment.TickCount; m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); Bitmap mapbmp = new Bitmap(256, 256); @@ -1053,13 +1055,13 @@ namespace OpenSim.Region.Environment.Scenes } float mid = (high + low) * 0.5f; - + // temporary initializer float hfvalue = (float)m_regInfo.RegionSettings.WaterHeight; float hfvaluecompare = hfvalue; float hfdiff = hfvalue; int hfdiffi = 0; - + for (int x = 0; x < 256; x++) { @@ -1088,7 +1090,7 @@ namespace OpenSim.Region.Environment.Scenes if (heightvalue > (float)m_regInfo.RegionSettings.WaterHeight) { - + // scale height value heightvalue = low + mid * (heightvalue - low) / mid; @@ -1115,7 +1117,7 @@ namespace OpenSim.Region.Environment.Scenes { hfvalue = (float)hm[x, y]; hfvaluecompare = (float)hm[x - 1, y - 1]; - + if (Single.IsInfinity(hfvalue) || Single.IsNaN(hfvalue)) hfvalue = 0; @@ -1133,7 +1135,7 @@ namespace OpenSim.Region.Environment.Scenes // 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 { @@ -1148,7 +1150,7 @@ namespace OpenSim.Region.Environment.Scenes m_log.Debug("[MAPTILE]: Shadow failed at value: " + hfdiff.ToString()); ShadowDebugContinue = false; } - + if (ShadowDebugContinue) { if ((256 - y) - 1 > 0) @@ -1164,13 +1166,13 @@ namespace OpenSim.Region.Environment.Scenes mapbmp.SetPixel(x - 1, (256 - y) - 1, Shade); } } - + } } - - + + } @@ -1178,7 +1180,7 @@ namespace OpenSim.Region.Environment.Scenes { 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",RegionInfo.RegionName); + 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", RegionInfo.RegionName); terraincorruptedwarningsaid = true; } Color black = Color.Black; @@ -1262,7 +1264,7 @@ namespace OpenSim.Region.Environment.Scenes if (part == null) continue; - + // Draw if the object is at least 1 meter wide in any direction if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) { @@ -1278,8 +1280,8 @@ namespace OpenSim.Region.Environment.Scenes if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree) continue; // eliminates trees from this since we don't really have a good tree representation - // if you want tree blocks on the map comment the above line and uncomment the below line - //mapdotspot = Color.PaleGreen; + // if you want tree blocks on the map comment the above line and uncomment the below line + //mapdotspot = Color.PaleGreen; if (part.Shape.Textures == null) continue; @@ -1410,106 +1412,76 @@ namespace OpenSim.Region.Environment.Scenes return; } - LLUUID lastMapRegionUUID = m_regInfo.lastMapUUID; + LazySaveGeneratedMaptile(data,temporary); - int lastMapRefresh = 0; - int twoDays = 172800; - int RefreshSeconds = twoDays; - - try - { - lastMapRefresh = Convert.ToInt32(m_regInfo.lastMapRefresh); - } - catch (ArgumentException) - { - } - catch (FormatException) - { - } - catch (OverflowException) + #endregion + } + else + { + // Use the module to generate the maptile. + byte[] data = terrain.WriteJpeg2000Image("defaultstripe.png"); + if (data != null) { + LazySaveGeneratedMaptile(data,temporary); } + } + } + public void LazySaveGeneratedMaptile(byte[] data, bool temporary) + { + // Overwrites the local Asset cache with new maptile data + // Assets are single write, this causes the asset server to ignore this update, + // but the local asset cache does not - LLUUID TerrainImageLLUUID = LLUUID.Random(); + // this is on purpose! The net result of this is the region always has the most up to date + // map tile while protecting the (grid) asset database from bloat caused by a new asset each + // time a mapimage is generated! + + LLUUID lastMapRegionUUID = m_regInfo.lastMapUUID; - if (lastMapRegionUUID == LLUUID.Zero || (lastMapRefresh + RefreshSeconds) < Util.UnixTimeSinceEpoch()) - { - m_regInfo.SaveLastMapUUID(TerrainImageLLUUID); + int lastMapRefresh = 0; + int twoDays = 172800; + int RefreshSeconds = twoDays; - m_log.Warn("[MAPTILE]: STORING MAPTILE IMAGE"); - //Extra protection.. probably not needed. - } - else - { - TerrainImageLLUUID = lastMapRegionUUID; - m_log.Warn("[MAPTILE]: REUSING OLD MAPTILE IMAGE ID"); - } + try + { + lastMapRefresh = Convert.ToInt32(m_regInfo.lastMapRefresh); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } - m_regInfo.RegionSettings.TerrainImageID = TerrainImageLLUUID; + LLUUID TerrainImageLLUUID = LLUUID.Random(); - AssetBase asset = new AssetBase(); - asset.FullID = m_regInfo.RegionSettings.TerrainImageID; - asset.Data = data; - asset.Name = "terrainImage_" + m_regInfo.RegionID.ToString() + "_" + lastMapRefresh.ToString(); - asset.Description = RegionInfo.RegionName; + if (lastMapRegionUUID == LLUUID.Zero || (lastMapRefresh + RefreshSeconds) < Util.UnixTimeSinceEpoch()) + { + m_regInfo.SaveLastMapUUID(TerrainImageLLUUID); - asset.Type = 0; - asset.Temporary = temporary; - AssetCache.AddAsset(asset); + m_log.Warn("[MAPTILE]: STORING MAPTILE IMAGE"); } else { - byte[] data = terrain.WriteJpeg2000Image("defaultstripe.png"); - if (data != null) - { - LLUUID lastMapRegionUUID = m_regInfo.lastMapUUID; - - int lastMapRefresh = 0; - int twoDays = 172800; - int RefreshSeconds = twoDays; - - try - { - lastMapRefresh = Convert.ToInt32(m_regInfo.lastMapRefresh); - } - catch (ArgumentException) - { - } - catch (FormatException) - { - } - catch (OverflowException) - { - } - - LLUUID TerrainImageLLUUID = LLUUID.Random(); - - if (lastMapRegionUUID == LLUUID.Zero || (lastMapRefresh + RefreshSeconds) < Util.UnixTimeSinceEpoch()) - { - m_regInfo.SaveLastMapUUID(TerrainImageLLUUID); + TerrainImageLLUUID = lastMapRegionUUID; + m_log.Warn("[MAPTILE]: REUSING OLD MAPTILE IMAGE ID"); + } - //m_log.Warn(terrainImageID); - //Extra protection.. probably not needed. - } - else - { - TerrainImageLLUUID = lastMapRegionUUID; - } + m_regInfo.RegionSettings.TerrainImageID = TerrainImageLLUUID; - m_regInfo.RegionSettings.TerrainImageID = TerrainImageLLUUID; + AssetBase asset = new AssetBase(); + asset.FullID = m_regInfo.RegionSettings.TerrainImageID; + asset.Data = data; + asset.Name = "terrainImage_" + m_regInfo.RegionID.ToString() + "_" + lastMapRefresh.ToString(); + asset.Description = RegionInfo.RegionName; - AssetBase asset = new AssetBase(); - asset.FullID = m_regInfo.RegionSettings.TerrainImageID; - asset.Data = data; - asset.Name = "terrainImage_" + m_regInfo.RegionID.ToString() + "_" + lastMapRefresh.ToString(); - asset.Description = RegionInfo.RegionName; - asset.Type = 0; - asset.Temporary = temporary; - AssetCache.AddAsset(asset); - } - } + asset.Type = 0; + asset.Temporary = temporary; + AssetCache.AddAsset(asset); } - #endregion #region Load Land -- cgit v1.1