首页 文章

如何在运行时操纵形状的地形区域 - Unity 3D

提问于
浏览
2

我的游戏有一个绘图工具 - 一个循环线渲染器,用作标记来操纵线条形状的地形区域 . 一旦玩家停止绘制线条,这一切都会在运行时发生 . 到目前为止,我已经设法提升了与线条渲染器点的坐标相匹配的地形椎体,但是我很难提高落在标记形状内的点 . 这是描述我目前所拥有的图像:

我尝试使用"Polygon Fill Algorithm"(http://alienryderflex.com/polygon_fill/),但是一次将一个线提升到地形顶点是太有用了(即使算法缩小到仅围绕标记区域的矩形) . 此外,我的标记的轮廓点之间存在间隙,这意味着我需要在引发地形的线上添加半径,但这可能会使结果变得邋..

也许我应该丢弃绘图机制并使用带网格对撞机的网格作为标记?

如何将地形操作为与标记完全相同的形状,我们对任何想法都表示赞赏 .


当前代码:我使用this script来创建直线 - 第一个和最后一个直线点具有相同的坐标 . 当单击GUI按钮时,当前触发了用于操纵地形操纵的代码:

using System;
using System.Collections;
using UnityEngine;
public class changeTerrainHeight_lineMarker : MonoBehaviour
{
    public Terrain TerrainMain;
    public LineRenderer line;

    void OnGUI()
    {
        //Get the terrain heightmap width and height.
        int xRes = TerrainMain.terrainData.heightmapWidth;
        int yRes = TerrainMain.terrainData.heightmapHeight;

        //GetHeights - gets the heightmap points of the tarrain. Store them in array
        float[,] heights = TerrainMain.terrainData.GetHeights(0, 0, xRes, yRes);

        if (GUI.Button(new Rect(30, 30, 200, 30), "Line points"))
        {
            /* Set the positions to array "positions" */
            Vector3[] positions = new Vector3[line.positionCount];
            line.GetPositions(positions);

            /* use this height to the affected terrain verteces */
            float height = 0.05f;

            for (int i = 0; i < line.positionCount; i++)
            {
                /* Assign height data */
                heights[Mathf.RoundToInt(positions[i].z), Mathf.RoundToInt(positions[i].x)] = height;
            }

            //SetHeights to change the terrain height.
            TerrainMain.terrainData.SetHeights(0, 0, heights);
        } 
    }
}

1 回答

  • 0

    感谢Siim的个人帮助,得到了解决方案,感谢文章:How can I determine whether a 2D Point is within a Polygon? .

    最终结果在这里可视化:

    首先是代码,然后是解释:

    using System;
    using System.Collections;
    using UnityEngine;
    public class changeTerrainHeight_lineMarker : MonoBehaviour
    {
    
        public Terrain TerrainMain;
        public LineRenderer line;
    
        void OnGUI()
        {
            //Get the terrain heightmap width and height.
            int xRes = TerrainMain.terrainData.heightmapWidth;
            int yRes = TerrainMain.terrainData.heightmapHeight;
    
            //GetHeights - gets the heightmap points of the tarrain. Store them in array
            float[,] heights = TerrainMain.terrainData.GetHeights(0, 0, xRes, yRes);
    
            //Trigger line area raiser
            if (GUI.Button(new Rect(30, 30, 200, 30), "Line fill"))
            {
                /* Set the positions to array "positions" */
                Vector3[] positions = new Vector3[line.positionCount];
                line.GetPositions(positions);
    
                float height = 0.10f; // define the height of the affected verteces of the terrain
    
                /* Find the reactangle the shape is in! The sides of the rectangle are based on the most-top, -right, -bottom and -left vertex. */
                float ftop = float.NegativeInfinity;
                float fright = float.NegativeInfinity;
                float fbottom = Mathf.Infinity;
                float fleft = Mathf.Infinity;
                for (int i = 0; i < line.positionCount; i++)
                {
                    //find the outmost points
                    if (ftop < positions[i].z)
                    {
                        ftop = positions[i].z;
                    }
                    if (fright < positions[i].x)
                    {
                        fright = positions[i].x;
                    }
                    if (fbottom > positions[i].z)
                    {
                        fbottom = positions[i].z;
                    }
                    if (fleft > positions[i].x)
                    {
                        fleft = positions[i].x;
                    }
                }
                int top = Mathf.RoundToInt(ftop);
                int right = Mathf.RoundToInt(fright);
                int bottom = Mathf.RoundToInt(fbottom);
                int left = Mathf.RoundToInt(fleft);
    
                int terrainXmax = right - left; // the rightmost edge of the terrain
                int terrainZmax = top - bottom; // the topmost edge of the terrain 
    
                float[,] shapeHeights = TerrainMain.terrainData.GetHeights(left, bottom, terrainXmax, terrainZmax);
    
                Vector2 point; //Create a point Vector2 point to match the shape
    
                /* Loop through all points in the rectangle surrounding the shape */
                for (int i = 0; i < terrainZmax; i++)
                {
                    point.y = i + bottom; //Add off set to the element so it matches the position of the line
                    for (int j = 0; j < terrainXmax; j++)
                    {
                        point.x = j + left; //Add off set to the element so it matches the position of the line
                        if (InsidePolygon(point, bottom))
                        {
                            shapeHeights[i, j] = height; // set the height value to the terrain vertex
                        }
                    }
                }
    
                //SetHeights to change the terrain height.
                TerrainMain.terrainData.SetHeightsDelayLOD(left, bottom, shapeHeights);
                TerrainMain.ApplyDelayedHeightmapModification();
            }
        }
    
        //Checks if the given vertex is inside the the shape.
        bool InsidePolygon(Vector2 p, int terrainZmax)
        {
            // Assign the points that define the outline of the shape
            Vector3[] positions = new Vector3[line.positionCount];
            line.GetPositions(positions);
    
            int count = 0;
            Vector2 p1, p2;
            int n = positions.Length;
    
            // Find the lines that define the shape
            for (int i = 0; i < n; i++)
            {
                p1.y = positions[i].z;// - p.y;
                p1.x = positions[i].x;// - p.x;
                if (i != n - 1)
                {
                    p2.y = positions[(i + 1)].z;// - p.y;
                    p2.x = positions[(i + 1)].x;// - p.x;
                }
                else
                {
                    p2.y = positions[0].z;// - p.y;
                    p2.x = positions[0].x;// - p.x;
                }
    
                // check if the given point p intersects with the lines that form the outline of the shape.
                if (LinesIntersect(p1, p2, p, terrainZmax))
                {
                    count++;
                }
            }
    
            // the point is inside the shape when the number of line intersections is an odd number
            if (count % 2 == 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    
        // Function that checks if two lines intersect with each other
        bool LinesIntersect(Vector2 A, Vector2 B, Vector2 C, int terrainZmax)
        {
            Vector2 D = new Vector2(C.x, terrainZmax);
            Vector2 CmP = new Vector2(C.x - A.x, C.y - A.y);
            Vector2 r = new Vector2(B.x - A.x, B.y - A.y);
            Vector2 s = new Vector2(D.x - C.x, D.y - C.y);
    
            float CmPxr = CmP.x * r.y - CmP.y * r.x;
            float CmPxs = CmP.x * s.y - CmP.y * s.x;
            float rxs = r.x * s.y - r.y * s.x;
    
            if (CmPxr == 0f)
            {
                // Lines are collinear, and so intersect if they have any overlap
    
                return ((C.x - A.x < 0f) != (C.x - B.x < 0f))
                    || ((C.y - A.y < 0f) != (C.y - B.y < 0f));
            }
    
            if (rxs == 0f)
                return false; // Lines are parallel.
    
            float rxsr = 1f / rxs;
            float t = CmPxs * rxsr;
            float u = CmPxr * rxsr;
    
            return (t >= 0f) && (t <= 1f) && (u >= 0f) && (u <= 1f);
        }
    
    }
    

    使用的方法是一次填充一行形状 - “射线投射方法” . 事实证明,只有当给定的形状作为很多方面时,这种方法才开始占用更多的资源 . (形状的一侧是连接形状轮廓中两个点的线 . )当我发布这个问题时,我的线条渲染器有134个点定义线条 . 这也意味着形状具有需要通过光线投射检查的相同数量的边 . 当我将点数缩小到42时,方法变得足够快,而且形状几乎没有丢失任何细节 . 此外,我计划使用一些方法使轮廓更平滑,因此可以用更少的点来定义形状 .

    简而言之,您需要以下步骤才能获得结果:

    • 创建形状轮廓;

    • 找到标记形状周围边界框的4个点;

    • 开始射线投射盒子;

    • 检查光线与形状边相交的次数 . 奇数的点位于形状内:

    • 将属性分配给在形状中找到的所有点 .

相关问题