From 4837a8a8fab0d8ed69c94236f6a52a754c9480ba Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Mon, 7 May 2018 17:29:19 +0100 Subject: [PATCH] breaking map (warp3d); remove warp3d viewport code not realy used and confusing my last neuron --- .../World/Warp3DMap/TerrainSplat.cs | 195 +++++++++++++----- .../CoreModules/World/Warp3DMap/Viewport.cs | 166 --------------- .../World/Warp3DMap/Warp3DImageModule.cs | 86 ++++---- bin/Warp3D.dll | Bin 69120 -> 69632 bytes 4 files changed, 189 insertions(+), 258 deletions(-) delete mode 100644 OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs index 622b16c910..95fa567d4c 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/TerrainSplat.cs @@ -83,7 +83,7 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap public static Bitmap Splat(ITerrainChannel terrain, UUID[] textureIDs, float[] startHeights, float[] heightRanges, uint regionPositionX,uint regionPositionY, - IAssetService assetService, bool textureTerrain, bool averagetextureTerrain) + IAssetService assetService, bool textureTerrain, bool averagetextureTerrain, bool FlipedY) { Debug.Assert(textureIDs.Length == 4); Debug.Assert(startHeights.Length == 4); @@ -294,38 +294,79 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // pixel data directly if(usecolors) { - unsafe + if(FlipedY) { - for(int y = 0; y < 256; ++y) + unsafe { - int ty = (int)((255 - y) * yFactor); - byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride; - - for(int x = 0; x < 256; ++x) + for(int y = 0; y < 256; ++y) { - 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); + int ty = (int)(y * yFactor); + byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride; - // Select two textures - int l0 = (int)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, y, + (uint)tx + regionPositionX, (uint)ty + regionPositionY, + startHeights, heightRanges); - float layerDiff = layer - l0; + // Select two textures + int l0 = (int)layer; + int l1 = Math.Min(l0 + 1, 3); - 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)); + float a = mapColorsRed[l0]; + float b = mapColorsRed[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); - a = mapColorsBlue[l0]; - b = mapColorsBlue[l1]; - *(ptrO++) = (byte)(a + layerDiff * (b - a)); + a = mapColorsGreen[l0]; + b = mapColorsGreen[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + + a = mapColorsBlue[l0]; + b = mapColorsBlue[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + } + } + } + } + else + { + unsafe + { + for(int y = 0; y < 256; ++y) + { + int ty = (int)((255 - y) * yFactor); + byte* ptrO = (byte*)outputData.Scan0 + y * outputData.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); + + float layerDiff = layer - l0; + + float a = mapColorsRed[l0]; + float b = mapColorsRed[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + + a = mapColorsGreen[l0]; + b = mapColorsGreen[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + + a = mapColorsBlue[l0]; + b = mapColorsBlue[l1]; + *(ptrO++) = (byte)(a + layerDiff * (b - a)); + } } } } @@ -343,42 +384,86 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap detailTexture[3].LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) }; - for(int y = 0; y < 256; y++) + if(FlipedY) { - int ty = (int)((255 - y) * yFactor); - int ypatch = ((int)(y * yFactor) & 0x0f) * datas[0].Stride; - - for(int x = 0; x < 256; x++) + for(int y = 0; y < 256; y++) { - 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); + int ty = (int)(y * yFactor); + int ypatch = ((int)(y * yFactor) & 0x0f) * datas[0].Stride; - // Select two textures - int l0 = (int)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, y, + (uint)tx + regionPositionX, (uint)ty + regionPositionY, + startHeights, heightRanges); - 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; + // 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); + 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 bB = *(ptrB + 0); - float bG = *(ptrB + 1); - float bR = *(ptrB + 2); + 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); - // 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)); + 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)); + } + } + } + else + { + 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)); + } } } @@ -420,6 +505,8 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap heightRanges[1], heightRanges[3], 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 @@ -435,8 +522,6 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap // 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 deleted file mode 100644 index 5ea4d29c32..0000000000 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/Viewport.cs +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.Drawing; -using OpenMetaverse; - -namespace OpenSim.Region.CoreModules.World.Warp3DMap -{ - public class Viewport - { - private const float DEG_TO_RAD = (float)Math.PI / 180f; - private static readonly Vector3 UP_DIRECTION = Vector3.UnitZ; - - public Vector3 Position; - public Vector3 LookDirection; - public float FieldOfView; - public float NearPlaneDistance; - public float FarPlaneDistance; - public int Width; - public int Height; - public bool Orthographic; - public float OrthoWindowWidth; - public float OrthoWindowHeight; - - public Viewport(Vector3 position, Vector3 lookDirection, float fieldOfView, float farPlaneDist, float nearPlaneDist, int width, int height) - { - // Perspective projection mode - Position = position; - LookDirection = lookDirection; - FieldOfView = fieldOfView; - FarPlaneDistance = farPlaneDist; - 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) - { - // Orthographic projection mode - Position = position; - LookDirection = lookDirection; - FarPlaneDistance = farPlaneDist; - NearPlaneDistance = nearPlaneDist; - Width = width; - Height = height; - OrthoWindowWidth = orthoWindowWidth; - OrthoWindowHeight = orthoWindowHeight; - Orthographic = true; - } - - public Point VectorToScreen(Vector3 v) - { - Matrix4 m = GetWorldToViewportMatrix(); - Vector3 screenPoint = v * m; - return new Point((int)screenPoint.X, (int)screenPoint.Y); - } - - public Matrix4 GetWorldToViewportMatrix() - { - Matrix4 result = GetViewMatrix(); - result *= GetPerspectiveProjectionMatrix(); - result *= GetViewportMatrix(); - - return result; - } - - public Matrix4 GetViewMatrix() - { - Vector3 zAxis = -LookDirection; - zAxis.Normalize(); - - Vector3 xAxis = Vector3.Cross(UP_DIRECTION, zAxis); - xAxis.Normalize(); - - Vector3 yAxis = Vector3.Cross(zAxis, xAxis); - - Vector3 position = Position; - float offsetX = -Vector3.Dot(xAxis, position); - float offsetY = -Vector3.Dot(yAxis, position); - float offsetZ = -Vector3.Dot(zAxis, position); - - return new Matrix4( - xAxis.X, yAxis.X, zAxis.X, 0f, - xAxis.Y, yAxis.Y, zAxis.Y, 0f, - xAxis.Z, yAxis.Z, zAxis.Z, 0f, - offsetX, offsetY, offsetZ, 1f); - } - - public Matrix4 GetPerspectiveProjectionMatrix() - { - float aspectRatio = (float)Width / (float)Height; - - float hFoV = FieldOfView * DEG_TO_RAD; - float zn = NearPlaneDistance; - float zf = FarPlaneDistance; - - float xScale = 1f / (float)Math.Tan(hFoV / 2f); - float yScale = aspectRatio * xScale; - float m33 = (zf == double.PositiveInfinity) ? -1 : (zf / (zn - zf)); - float m43 = zn * m33; - - return new Matrix4( - xScale, 0f, 0f, 0f, - 0f, yScale, 0f, 0f, - 0f, 0f, m33, -1f, - 0f, 0f, m43, 0f); - } - - public Matrix4 GetOrthographicProjectionMatrix(float aspectRatio) - { - float w = Width; - float h = Height; - float zn = NearPlaneDistance; - float zf = FarPlaneDistance; - - float m33 = 1 / (zn - zf); - float m43 = zn * m33; - - return new Matrix4( - 2f / w, 0f, 0f, 0f, - 0f, 2f / h, 0f, 0f, - 0f, 0f, m33, 0f, - 0f, 0f, m43, 1f); - } - - public Matrix4 GetViewportMatrix() - { - float scaleX = (float)Width * 0.5f; - float scaleY = (float)Height * 0.5f; - float offsetX = 0f + scaleX; - float offsetY = 0f + scaleY; - - return new Matrix4( - scaleX, 0f, 0f, 0f, - 0f, -scaleY, 0f, 0f, - 0f, 0f, 1f, 0f, - offsetX, offsetY, 0f, 1f); - } - } -} diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs index b41a27c81f..b4659cc937 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/Warp3DImageModule.cs @@ -156,65 +156,74 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap #region IMapImageGenerator Members + private Vector3 cameraPos; + private Vector3 cameraDir; + private int viewWitdh = 256; + private int viewHeigth = 256; + private float fov; + private bool orto; + public Bitmap CreateMapTile() { - /* this must be on all map, not just its image - if ((DateTime.Now - lastImageTime).TotalSeconds < 3600) - { - return (Bitmap)lastImage.Clone(); - } - */ - List renderers = RenderingLoader.ListRenderers(Util.ExecutingDirectory()); if (renderers.Count > 0) { m_primMesher = RenderingLoader.LoadRenderer(renderers[0]); } - Vector3 camPos = new Vector3( + cameraPos = new Vector3( (m_scene.RegionInfo.RegionSizeX) * 0.5f, (m_scene.RegionInfo.RegionSizeY) * 0.5f, 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); + cameraDir = -Vector3.UnitZ; + viewWitdh = (int)m_scene.RegionInfo.RegionSizeX; + viewHeigth = (int)m_scene.RegionInfo.RegionSizeY; + orto = true; + + Bitmap tile = GenMapTile(); m_primMesher = null; return tile; -/* - lastImage = tile; - lastImageTime = DateTime.Now; - return (Bitmap)lastImage.Clone(); - */ } - public Bitmap CreateViewImage(Vector3 camPos, Vector3 camDir, float fov, int width, int height, bool useTextures) + public Bitmap CreateViewImage(Vector3 camPos, Vector3 camDir, float pfov, int width, int height, bool useTextures) { - Viewport viewport = new Viewport(camPos, camDir, fov, Constants.RegionSize, 0.1f, width, height); - return CreateMapTile(viewport); + List renderers = RenderingLoader.ListRenderers(Util.ExecutingDirectory()); + if (renderers.Count > 0) + { + m_primMesher = RenderingLoader.LoadRenderer(renderers[0]); + } + + cameraPos = camPos; + cameraDir = camDir; + viewWitdh = width; + viewHeigth = height; + fov = pfov; + orto = false; + + Bitmap tile = GenMapTile(); + m_primMesher = null; + return tile; } - public Bitmap CreateMapTile(Viewport viewport) + private Bitmap GenMapTile() { m_colors.Clear(); - int width = viewport.Width; - int height = viewport.Height; - WarpRenderer renderer = new WarpRenderer(); - if(!renderer.CreateScene(width, height)) - return new Bitmap(width,height); + if(!renderer.CreateScene(viewWitdh, viewHeigth)) + return new Bitmap(viewWitdh, viewHeigth); #region Camera - warp_Vector pos = ConvertVector(viewport.Position); - warp_Vector lookat = warp_Vector.add(ConvertVector(viewport.Position), ConvertVector(viewport.LookDirection)); + warp_Vector pos = ConvertVector(cameraPos); + warp_Vector lookat = warp_Vector.add(pos, ConvertVector(cameraDir)); - - renderer.Scene.defaultCamera.setOrthographic(true, viewport.OrthoWindowWidth, viewport.OrthoWindowHeight); + if(orto) + renderer.Scene.defaultCamera.setOrthographic(true, viewWitdh, viewHeigth); + else + renderer.Scene.defaultCamera.setFov(fov); renderer.Scene.defaultCamera.setPos(pos); renderer.Scene.defaultCamera.lookAt(lookat); @@ -234,7 +243,6 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap renderer.Scene.destroy(); renderer.Reset(); renderer = null; - viewport = null; m_colors.Clear(); GC.Collect(); @@ -303,28 +311,31 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap npointsy++; // Create all the vertices for the terrain + // for texture fliped on Y warp_Object obj = new warp_Object(); warp_Vector pos; float x, y; + float tv; for (y = 0; y < regionsy; y += diff) { + tv = y * invsy; for (x = 0; x < regionsx; x += diff) { pos = ConvertVector(x , y , (float)terrain[(int)x, (int)y]); - obj.addVertex(new warp_Vertex(pos, x * invsx, 1.0f - y * invsy)); + obj.addVertex(new warp_Vertex(pos, x * invsx, tv )); } pos = ConvertVector(x , y , (float)terrain[(int)(x - diff), (int)y]); - obj.addVertex(new warp_Vertex(pos, 1.0f, 1.0f - y * invsy)); + obj.addVertex(new warp_Vertex(pos, 1.0f, tv)); } int lastY = (int)(y - diff); for (x = 0; x < regionsx; x += diff) { pos = ConvertVector(x , y , (float)terrain[(int)x, lastY]); - obj.addVertex(new warp_Vertex(pos, x * invsx, 0f)); + obj.addVertex(new warp_Vertex(pos, x * invsx, 1.0f)); } pos = ConvertVector(x , y , (float)terrain[(int)(x - diff), lastY]); - obj.addVertex(new warp_Vertex(pos, 1.0f, 0f)); + obj.addVertex(new warp_Vertex(pos, 1.0f, 1.0f)); // Now that we have all the vertices, make another pass and // create the list of triangle indices. @@ -376,10 +387,11 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out globalX, out globalY); warp_Texture texture; + // get texture fliped on Y using (Bitmap image = TerrainSplat.Splat( terrain, textureIDs, startHeights, heightRanges, m_scene.RegionInfo.WorldLocX, m_scene.RegionInfo.WorldLocY, - m_scene.AssetService, m_textureTerrain, m_textureAvegareTerrain)) + m_scene.AssetService, m_textureTerrain, m_textureAvegareTerrain, true)) texture = new warp_Texture(image); warp_Material material = new warp_Material(texture); diff --git a/bin/Warp3D.dll b/bin/Warp3D.dll index 21662e2abe206435545082020846bc73a90e6786..2f9a13b44c4c4cc07a03f774d81bdfc88cb0d258 100755 GIT binary patch delta 4704 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 S90L)pU!*JhD}N1DiR^#ijA1