From dffa6289666382166dd038e250cad94d24b88c6c Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Sun, 6 May 2018 17:32:03 +0100 Subject: [PATCH] break nap a bit more, add options ExportMapAddScale, ExportMapAddRegionName and (warp3d) AverageTextureColorOnMapTile --- .../World/Warp3DMap/TerrainSplat.cs | 409 ++++++++++-------- .../CoreModules/World/Warp3DMap/Viewport.cs | 1 + .../World/Warp3DMap/Warp3DImageModule.cs | 104 +++-- .../World/WorldMap/WorldMapModule.cs | 29 +- bin/Warp3D.dll | Bin 68608 -> 69120 bytes 5 files changed, 313 insertions(+), 230 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs index 226b330bda..ba6ebfc4f1 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs @@ -56,7 +56,9 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap private static readonly Color[] DEFAULT_TERRAIN_COLOR = new Color[] { - Color.FromArgb(255, 164, 136, 117), +// Color.FromArgb(255, 164, 136, 117), + Color.FromArgb(255, 255, 136, 117), + Color.FromArgb(255, 65, 87, 47), Color.FromArgb(255, 157, 145, 131), Color.FromArgb(255, 125, 128, 130) @@ -82,7 +84,8 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap public static Bitmap Splat(ITerrainChannel terrain, UUID[] textureIDs, float[] startHeights, float[] heightRanges, - Vector3d regionPosition, IAssetService assetService, bool textureTerrain) + uint regionPositionX,uint regionPositionY, + IAssetService assetService, bool textureTerrain, bool averagetextureTerrain) { Debug.Assert(textureIDs.Length == 4); Debug.Assert(startHeights.Length == 4); @@ -90,70 +93,81 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap 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++) + for(int i = 0; i < textureIDs.Length; i++) { - if (textureIDs[i] == UUID.Zero) + if(textureIDs[i] == UUID.Zero) textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i]; } #region Texture Fetching - if (assetService != null) + if(assetService != null) { - for (int i = 0; i < 4; i++) + for(int i = 0; i < 4; i++) { - AssetBase asset; + AssetBase asset = null; UUID cacheID = UUID.Combine(TERRAIN_CACHE_MAGIC, textureIDs[i]); // Try to fetch a cached copy of the decoded/resized version of this texture - asset = assetService.GetCached(cacheID.ToString()); - if (asset != null) + // asset = assetService.GetCached(cacheID.ToString()); + if(asset != null) { try { - using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) + using(System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) detailTexture[i] = (Bitmap)Image.FromStream(stream); } - catch (Exception ex) + catch(Exception ex) { m_log.Warn("Failed to decode cached terrain texture " + cacheID + " (textureID: " + textureIDs[i] + "): " + ex.Message); } + if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb || + detailTexture[i].Width != 16 || detailTexture[i].Height != 16) + { + detailTexture[i].Dispose(); + detailTexture[i] = null; + } } - if (detailTexture[i] == null) + 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) + if(asset != null) { // m_log.DebugFormat( // "[TERRAIN SPLAT]: Got cached original JPEG2000 terrain texture {0} {1}", i, asset.ID); - try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); } - catch (Exception ex) + try + { + detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); + } + catch(Exception ex) { m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message); } } - if (detailTexture[i] != null) + if(detailTexture[i] != null) { - // Make sure this texture is the correct size, otherwise resize - if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) - { - using (Bitmap origBitmap = detailTexture[i]) - { - detailTexture[i] = ImageUtils.ResizeImage(origBitmap, 256, 256); - } - } + if(detailTexture[i].PixelFormat != PixelFormat.Format24bppRgb || + detailTexture[i].Width != 16 || detailTexture[i].Height != 16) + using(Bitmap origBitmap = detailTexture[i]) + detailTexture[i] = ImageUtils.ResizeImageSolid(origBitmap, 16, 16); // Save the decoded and resized texture to the cache byte[] data; - using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) + using(System.IO.MemoryStream stream = new System.IO.MemoryStream()) { detailTexture[i].Save(stream, ImageFormat.Png); data = stream.ToArray(); @@ -180,90 +194,96 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap } #endregion Texture Fetching - } - - // Fill in any missing textures with a solid color - for (int i = 0; i < 4; i++) - { - if (detailTexture[i] == null) + if(averagetextureTerrain) { - 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(256, 256, PixelFormat.Format24bppRgb); - using (Graphics gfx = Graphics.FromImage(detailTexture[i])) + for(int t = 0; t < 4; t++) { - using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i])) - gfx.FillRectangle(brush, 0, 0, 256, 256); + 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) + { + cR += *(ptrc++); + cG += *(ptrc++); + cB += *(ptrc++); + } + } + + } + detailTexture[t].UnlockBits(bmdata); + detailTexture[t].Dispose(); + + mapColorsRed[t] = (byte)Util.Clamp(cR / npixeis, 0 , 255); + mapColorsGreen[t] = (byte)Util.Clamp(cG / npixeis, 0 , 255); + mapColorsBlue[t] = (byte)Util.Clamp(cB / npixeis, 0 , 255); } } else { - if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) + // Fill in any missing textures with a solid color + for(int i = 0; i < 4; i++) { - detailTexture[i] = ResizeBitmap(detailTexture[i], 256, 256); + 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] = ImageUtils.ResizeImageSolid(origBitmap, 16, 16); + } + } } } } + 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[,] layermap = new float[256, 256]; - // Scale difference between actual region size and the 256 texture being created - int xFactor = terrain.Width / 256; - int yFactor = terrain.Height / 256; - - // Create 'layermap' where each value is the fractional layer number to place - // at that point. For instance, a value of 1.345 gives the blending of - // layer 1 and layer 2 for that point. - for (int y = 0; y < 256; y++) - { - for (int x = 0; x < 256; x++) - { - float height = (float)terrain[x * xFactor, y * yFactor]; - - float pctX = (float)x / 255f; - float pctY = (float)y / 255f; - - // 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[0], - startHeights[2], - startHeights[1], - startHeights[3], - pctX, pctY); - startHeight = Utils.Clamp(startHeight, 0f, 255f); - - float heightRange = ImageUtils.Bilinear( - heightRanges[0], - heightRanges[2], - heightRanges[1], - heightRanges[3], - pctX, pctY); - heightRange = Utils.Clamp(heightRange, 0f, 255f); - - // Generate two frequencies of perlin noise based on our global position - // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting - Vector3 vec = new Vector3 - ( - ((float)regionPosition.X + (x * xFactor)) * 0.20319f, - ((float)regionPosition.Y + (y * yFactor)) * 0.20319f, - height * 0.25f - ); - - float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f; - float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f; - float noise = (lowFreq + highFreq) * 2f; - - // Combine the current height, generated noise, start height, and height range parameters, then scale all of it - float layer = ((height + noise - startHeight) / heightRange) * 4f; - if (Single.IsNaN(layer)) - layer = 0f; - layermap[x, y] = Utils.Clamp(layer, 0f, 3f); - } - } + float xFactor = terrain.Width / 256f; + float yFactor = terrain.Height / 256f; #endregion Layer Map @@ -274,115 +294,152 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // Unsafe work as we lock down the source textures for quicker access and access the // pixel data directly - unsafe + if(usecolors) { - // Get handles to all of the texture data arrays - BitmapData[] datas = new BitmapData[] + unsafe { - detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat), - detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat), - detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat), - detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) - }; - - // Compute size of each pixel data (used to address into the pixel data array) - int[] comps = new int[] - { - (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, - (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3 - }; - - for (int y = 0; y < 256; y++) - { - for (int x = 0; x < 256; x++) + for(int y = 0; y < 256; ++y) { - float layer = layermap[x, y]; + int ty = (int)((255 - y) * yFactor); + byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride; - // Select two textures - int l0 = (int)Math.Floor(layer); - int l1 = Math.Min(l0 + 1, 3); + for(int x = 0; x < 256; ++x) + { + int tx = (int)(x * xFactor); + float height = (float)terrain[tx, ty]; + float layer = getLayerTex(height, x, (255 - y), + (uint)tx + regionPositionX, (uint)ty + regionPositionY, + startHeights, heightRanges); - byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0]; - byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1]; - byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3; + // Select two textures + int l0 = (int)layer; + int l1 = Math.Min(l0 + 1, 3); - float aB = *(ptrA + 0); - float aG = *(ptrA + 1); - float aR = *(ptrA + 2); + float layerDiff = layer - l0; - float bB = *(ptrB + 0); - float bG = *(ptrB + 1); - float bR = *(ptrB + 2); + float a = mapColorsRed[l0]; + float b = mapColorsRed[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); - float layerDiff = layer - l0; + a = mapColorsGreen[l0]; + b = mapColorsGreen[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); - // Interpolate between the two selected textures - *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB)); - *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG)); - *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR)); + a = mapColorsBlue[l0]; + b = mapColorsBlue[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + } } } - - for (int i = 0; i < detailTexture.Length; i++) - detailTexture[i].UnlockBits(datas[i]); } + else + { + 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) + }; - for (int i = 0; i < detailTexture.Length; i++) - if (detailTexture[i] != null) - detailTexture[i].Dispose(); + for(int y = 0; y < 256; y++) + { + int ty = (int)((255 - y) * yFactor); + int ypatch = ((int)(y * yFactor) & 0x0f) * datas[0].Stride; + + for(int x = 0; x < 256; x++) + { + int tx = (int)(x * xFactor); + float height = (float)terrain[tx, ty]; + float layer = getLayerTex(height, x, (255 - y), + (uint)tx + regionPositionX, (uint)ty + regionPositionY, + startHeights, heightRanges); + + // Select two textures + int l0 = (int)layer; + int l1 = Math.Min(l0 + 1, 3); + + int patchOffset = (tx & 0x0f) * 3 + ypatch; + byte* ptrA = (byte*)datas[l0].Scan0 + patchOffset; + byte* ptrB = (byte*)datas[l1].Scan0 + patchOffset; + byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3; + + float aB = *(ptrA + 0); + float aG = *(ptrA + 1); + float aR = *(ptrA + 2); + + float bB = *(ptrB + 0); + float bG = *(ptrB + 1); + float bR = *(ptrB + 2); + + float layerDiff = layer - l0; + + // 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); - // We generated the texture upside down, so flip it - output.RotateFlip(RotateFlipType.RotateNoneFlipY); +output.Save("terr.png",ImageFormat.Png); #endregion Texture Compositing return output; } - public static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight) + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static float getLayerTex(float height, int x, int y, uint sourceX, uint sourceY, + float[] startHeights, float[] heightRanges) { - m_log.DebugFormat("{0} ResizeBitmap. From <{1},{2}> to <{3},{4}>", - LogHeader, b.Width, b.Height, nWidth, nHeight); - Bitmap result = new Bitmap(nWidth, nHeight); - using (Graphics g = Graphics.FromImage(result)) - g.DrawImage(b, 0, 0, nWidth, nHeight); - b.Dispose(); - return result; - } - public static Bitmap SplatSimple(float[] heightmap) - { - const float BASE_HSV_H = 93f / 360f; - const float BASE_HSV_S = 44f / 100f; - const float BASE_HSV_V = 34f / 100f; + float pctX = (float)x / 255f; + float pctY = (float)y / 255f; - Bitmap img = new Bitmap(256, 256); - BitmapData bitmapData = img.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); + // 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[0], startHeights[2], + startHeights[1], startHeights[3], + pctX, pctY); + startHeight = Utils.Clamp(startHeight, 0f, 255f); - unsafe - { - for (int y = 255; y >= 0; y--) - { - for (int x = 0; x < 256; x++) - { - float normHeight = heightmap[y * 256 + x] / 255f; - normHeight = Utils.Clamp(normHeight, BASE_HSV_V, 1.0f); + float heightRange = ImageUtils.Bilinear( + heightRanges[0], heightRanges[2], + heightRanges[1], heightRanges[3], + pctX, pctY); + heightRange = Utils.Clamp(heightRange, 0f, 255f); - Color4 color = Color4.FromHSV(BASE_HSV_H, BASE_HSV_S, normHeight); + // Generate two frequencies of perlin noise based on our global position + // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting + Vector3 vec = new Vector3 + ( + sourceX * 0.20319f, + sourceY * 0.20319f, + height * 0.25f + ); - byte* ptr = (byte*)bitmapData.Scan0 + y * bitmapData.Stride + x * 3; - *(ptr + 0) = (byte)(color.B * 255f); - *(ptr + 1) = (byte)(color.G * 255f); - *(ptr + 2) = (byte)(color.R * 255f); - } - } - } + float noise = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 13.0f; + noise += Perlin.turbulence2(vec.X, vec.Y, 2f) * 4.5f; - img.UnlockBits(bitmapData); - return img; + // Combine the current height, generated noise, start height, and height range parameters, then scale all of it + float layer = ((height + noise - startHeight) / heightRange) * 4f; + if(Single.IsNaN(layer)) + return 0; + return Utils.Clamp(layer, 0f, 3f); } } } diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs index 472f86e5fa..5ea4d29c32 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs @@ -57,6 +57,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap NearPlaneDistance = nearPlaneDist; Width = width; Height = height; + Orthographic = false; } public Viewport(Vector3 position, Vector3 lookDirection, float farPlaneDist, float nearPlaneDist, int width, int height, float orthoWindowWidth, float orthoWindowHeight) diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs index 387248c8f6..9186fae8a0 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs @@ -73,6 +73,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap private IConfigSource m_config; private bool m_drawPrimVolume = true; // true if should render the prims on the tile private bool m_textureTerrain = true; // true if to create terrain splatting texture + private bool m_textureAvegareTerrain = true; // replace terrain textures by their average color private bool m_texturePrims = true; // true if should texture the rendered prims private float m_texturePrimSize = 48f; // size of prim before we consider texturing it private bool m_renderMeshes = false; // true if to render meshes rather than just bounding boxes @@ -96,16 +97,21 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap m_Enabled = true; - m_drawPrimVolume - = Util.GetConfigVarFromSections(m_config, "DrawPrimOnMapTile", configSections, m_drawPrimVolume); - m_textureTerrain - = Util.GetConfigVarFromSections(m_config, "TextureOnMapTile", configSections, m_textureTerrain); - m_texturePrims - = Util.GetConfigVarFromSections(m_config, "TexturePrims", configSections, m_texturePrims); - m_texturePrimSize - = Util.GetConfigVarFromSections(m_config, "TexturePrimSize", configSections, m_texturePrimSize); - m_renderMeshes - = Util.GetConfigVarFromSections(m_config, "RenderMeshes", configSections, m_renderMeshes); + m_drawPrimVolume = + Util.GetConfigVarFromSections(m_config, "DrawPrimOnMapTile", configSections, m_drawPrimVolume); + m_textureTerrain = + Util.GetConfigVarFromSections(m_config, "TextureOnMapTile", configSections, m_textureTerrain); + m_textureAvegareTerrain = + Util.GetConfigVarFromSections(m_config, "AverageTextureColorOnMapTile", configSections, m_textureAvegareTerrain); + if(m_textureAvegareTerrain) + m_textureTerrain = true; + m_texturePrims = + Util.GetConfigVarFromSections(m_config, "TexturePrims", configSections, m_texturePrims); + m_texturePrimSize = + Util.GetConfigVarFromSections(m_config, "TexturePrimSize", configSections, m_texturePrimSize); + m_renderMeshes = + Util.GetConfigVarFromSections(m_config, "RenderMeshes", configSections, m_renderMeshes); + } public void AddRegion(Scene scene) @@ -168,13 +174,13 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap Vector3 camPos = new Vector3( (m_scene.RegionInfo.RegionSizeX) * 0.5f, (m_scene.RegionInfo.RegionSizeY) * 0.5f, - 221.7025033688163f); + 250f); // Viewport viewing down onto the region Viewport viewport = new Viewport(camPos, -Vector3.UnitZ, 1024f, 0.1f, (int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY, (float)m_scene.RegionInfo.RegionSizeX, (float)m_scene.RegionInfo.RegionSizeY); - Bitmap tile = CreateMapTile(viewport, false); + Bitmap tile = CreateMapTile(viewport); m_primMesher = null; return tile; /* @@ -187,10 +193,10 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap public Bitmap CreateViewImage(Vector3 camPos, Vector3 camDir, float fov, int width, int height, bool useTextures) { Viewport viewport = new Viewport(camPos, camDir, fov, Constants.RegionSize, 0.1f, width, height); - return CreateMapTile(viewport, useTextures); + return CreateMapTile(viewport); } - public Bitmap CreateMapTile(Viewport viewport, bool useTextures) + public Bitmap CreateMapTile(Viewport viewport) { m_colors.Clear(); @@ -207,29 +213,20 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap warp_Vector pos = ConvertVector(viewport.Position); warp_Vector lookat = warp_Vector.add(ConvertVector(viewport.Position), ConvertVector(viewport.LookDirection)); + + renderer.Scene.defaultCamera.setOrthographic(true, viewport.OrthoWindowWidth, viewport.OrthoWindowHeight); + renderer.Scene.defaultCamera.setPos(pos); renderer.Scene.defaultCamera.lookAt(lookat); - - if (viewport.Orthographic) - { - renderer.Scene.defaultCamera.setOrthographic(true,viewport.OrthoWindowWidth, viewport.OrthoWindowHeight); - } - else - { - float fov = viewport.FieldOfView; - fov *= 1.75f; // FIXME: ??? - renderer.Scene.defaultCamera.setFov(fov); - } - #endregion Camera renderer.Scene.addLight("Light1", new warp_Light(new warp_Vector(1.0f, 0.5f, 1f), 0xffffff, 0, 320, 40)); renderer.Scene.addLight("Light2", new warp_Light(new warp_Vector(-1f, -1f, 1f), 0xffffff, 0, 100, 40)); CreateWater(renderer); - CreateTerrain(renderer, m_textureTerrain); + CreateTerrain(renderer); if (m_drawPrimVolume) - CreateAllPrims(renderer, useTextures); + CreateAllPrims(renderer); renderer.Render(); Bitmap bitmap = renderer.Scene.getImage(); @@ -286,7 +283,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // Add a terrain to the renderer. // Note that we create a 'low resolution' 257x257 vertex terrain rather than trying for // full resolution. This saves a lot of memory especially for very large regions. - private void CreateTerrain(WarpRenderer renderer, bool textureTerrain) + private void CreateTerrain(WarpRenderer renderer) { ITerrainChannel terrain = m_scene.Heightmap; @@ -296,11 +293,14 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // 'diff' is the difference in scale between the real region size and the size of terrain we're buiding float diff = regionsx / 256f; - int npointsx = (int)(regionsx / diff) + 1; - int npointsy = (int)(regionsy / diff) + 1; + int npointsx = (int)(regionsx / diff); + int npointsy = (int)(regionsy / diff); - float invsx = 1.0f / (npointsx * diff); - float invsy = 1.0f / (npointsy * diff); + float invsx = 1.0f / (npointsx); + float invsy = 1.0f / (npointsy); + + npointsx++; + npointsy++; // Create all the vertices for the terrain warp_Object obj = new warp_Object(); @@ -310,6 +310,10 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap { for (x = 0; x < regionsx; x += diff) { + if(x == 48 && y == 36) + { + + } pos = ConvertVector(x , y , (float)terrain[(int)x, (int)y]); obj.addVertex(new warp_Vertex(pos, x * invsx, 1.0f - y * invsy)); } @@ -378,17 +382,18 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap warp_Texture texture; using (Bitmap image = TerrainSplat.Splat( terrain, textureIDs, startHeights, heightRanges, - new Vector3d(globalX, globalY, 0.0), m_scene.AssetService, textureTerrain)) + m_scene.RegionInfo.WorldLocX, m_scene.RegionInfo.WorldLocY, + m_scene.AssetService, m_textureTerrain, m_textureAvegareTerrain)) texture = new warp_Texture(image); warp_Material material = new warp_Material(texture); - material.setReflectivity(50); +// material.setReflectivity(50); renderer.Scene.addMaterial("TerrainColor", material); - renderer.Scene.material("TerrainColor").setReflectivity(0); // reduces tile seams a bit thanks lkalif +// renderer.Scene.material("TerrainColor").setReflectivity(0); // reduces tile seams a bit thanks lkalif renderer.SetObjectMaterial("Terrain", "TerrainColor"); } - private void CreateAllPrims(WarpRenderer renderer, bool useTextures) + private void CreateAllPrims(WarpRenderer renderer) { if (m_primMesher == null) return; @@ -397,13 +402,12 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap delegate(SceneObjectGroup group) { foreach (SceneObjectPart child in group.Parts) - CreatePrim(renderer, child, useTextures); + CreatePrim(renderer, child); } ); } - private void CreatePrim(WarpRenderer renderer, SceneObjectPart prim, - bool useTextures) + private void CreatePrim(WarpRenderer renderer, SceneObjectPart prim) { const float MIN_SIZE_SQUARE = 4f; @@ -489,19 +493,20 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap } Primitive.TextureEntryFace teFace = prim.Shape.Textures.GetFace((uint)i); - Color4 faceColor = GetFaceColor(teFace); + Color4 faceColor = teFace.RGBA; string materialName = String.Empty; if (m_texturePrims && primScaleLenSquared > m_texturePrimSize*m_texturePrimSize) materialName = GetOrCreateMaterial(renderer, faceColor, teFace.TextureID); else - materialName = GetOrCreateMaterial(renderer, faceColor); + materialName = GetOrCreateMaterial(renderer, GetFaceColor(teFace)); faceObj.scaleSelf(prim.Scale.X, prim.Scale.Z, prim.Scale.Y); - warp_Vector primPos = ConvertVector(prim.GetWorldPosition()); warp_Quaternion primRot = ConvertQuaternion(prim.GetWorldRotation()); warp_Matrix m = warp_Matrix.quaternionMatrix(primRot); faceObj.transform(m); + + warp_Vector primPos = ConvertVector(prim.GetWorldPosition()); faceObj.setPos(primPos); renderer.Scene.addObject(meshName, faceObj); @@ -645,7 +650,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap private static warp_Quaternion ConvertQuaternion(Quaternion quat) { - return new warp_Quaternion(quat.X, quat.Z, quat.Y, -quat.W); + return new warp_Quaternion(quat.X, quat.Z, quat.Y, -quat.W); } private static int ConvertColor(Color4 color) @@ -778,18 +783,21 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap /// New width /// New height /// Resized image - public static Bitmap ResizeImage(Image image, int width, int height) + public static Bitmap ResizeImageSolid(Image image, int width, int height) { - Bitmap result = new Bitmap(width, height); + Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb); + using (ImageAttributes atrib = new ImageAttributes()) using (Graphics graphics = Graphics.FromImage(result)) { + atrib.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY); graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None; - graphics.DrawImage(image, 0, 0, result.Width, result.Height); + graphics.DrawImage(image,new Rectangle(0,0, result.Width, result.Height), + 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, atrib); } return result; diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index 476d90a2ef..4b14c71964 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -93,6 +93,9 @@ namespace OpenSim.Region.CoreModules.World.WorldMap private const double expireResponsesTime = 120.0; // 2 minutes ? //private int CacheRegionsDistance = 256; + private bool m_exportPrintScale = false; // prints the scale of map in meters on exported map + private bool m_exportPrintRegionName = false; // prints the region name exported map + #region INonSharedRegionModule Members public virtual void Initialise(IConfigSource config) { @@ -103,6 +106,11 @@ namespace OpenSim.Region.CoreModules.World.WorldMap m_Enabled = true; expireBlackListTime = (double)Util.GetConfigVarFromSections(config, "BlacklistTimeout", configSections, 10 * 60); + + m_exportPrintScale = + Util.GetConfigVarFromSections(config, "ExportMapAddScale", configSections, m_exportPrintScale); + m_exportPrintRegionName = + Util.GetConfigVarFromSections(config, "ExportMapAddRegionName", configSections, m_exportPrintRegionName); } public virtual void AddRegion(Scene scene) @@ -1418,14 +1426,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap int spanY = endY - startY + 2; Bitmap mapTexture = new Bitmap(spanX, spanY); + ImageAttributes gatrib = new ImageAttributes(); Graphics g = Graphics.FromImage(mapTexture); + gatrib.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY); g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None; SolidBrush sea = new SolidBrush(Color.DarkBlue); - g.FillRectangle(sea, 0, 0, spanX - 1, spanY - 1); + g.FillRectangle(sea, 0, 0, spanX, spanY); sea.Dispose(); List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, @@ -1457,8 +1467,11 @@ namespace OpenSim.Region.CoreModules.World.WorldMap int y = r.RegionLocY - startY; int sx = r.RegionSizeX; int sy = r.RegionSizeY; - g.DrawImage(image, x, spanY - y - sy, sx, sy); // y origin is top - if(r.RegionHandle == m_scene.RegionInfo.RegionHandle) + // y origin is top + g.DrawImage(image,new Rectangle(x, spanY - y - sy, sx, sy), + 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, gatrib); + + if(m_exportPrintRegionName && r.RegionHandle == m_scene.RegionInfo.RegionHandle) { SizeF stringSize = g.MeasureString(r.RegionName, drawFont); g.DrawString(r.RegionName, drawFont, drawBrush, x + 30, spanY - y - 30 - stringSize.Height); @@ -1469,13 +1482,17 @@ namespace OpenSim.Region.CoreModules.World.WorldMap if(image != null) image.Dispose(); - String drawString = string.Format("{0}m x {1}m", spanX, spanY); - g.DrawString(drawString, drawFont, drawBrush, 30, 30); + if(m_exportPrintScale) + { + String drawString = string.Format("{0}m x {1}m", spanX, spanY); + g.DrawString(drawString, drawFont, drawBrush, 30, 30); + } drawBrush.Dispose(); drawFont.Dispose(); } + gatrib.Dispose(); g.Dispose(); mapTexture.Save(exportPath, ImageFormat.Jpeg); diff --git a/bin/Warp3D.dll b/bin/Warp3D.dll index 520e04cf01da5dfcc2d26c7705958f6d32328402..4000aebd60b2a5c74846844f41ca168cf561ad78 100755 GIT binary patch delta 18956 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