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

标签 unity-game-engine runtime terrain

我的游戏有一个绘图工具 - 一个循环线渲染器,用作标记来以线的形状操纵地形区域。一旦玩家停止画线,这一切就会在运行时发生。 到目前为止,我已经成功地提升了与线渲染器点的坐标相匹配的地形顶点,但是我在提升落在标记形状内的点时遇到了困难。这是描述我当前拥有的内容的图像:

我尝试使用“多边形填充算法”( 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);
        } 
    }
}

最佳答案

感谢 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 时,该方法变得足够快,而且形状几乎没有丢失任何细节。 此外,我计划使用一些方法使轮廓更平滑,这样就可以用更少的点来定义形状。

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

  1. 创建形状的轮廓;
  2. 找到标记形状周围边界框的 4 个点;
  3. 开始对盒子进行光线转换;
  4. 检查射线与形状的边相交的次数。奇数点位于形状内部:
  5. 将您的属性分配给形状中找到的所有点。

关于unity-game-engine - 如何在运行时操纵地形的形状区域 - Unity 3D,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48935840/

相关文章:

android - 为什么 Android 4.0/4.0.3 中运行时被阻止?

c# - 二维高度图上的基本(假)光线转换

c++ - OpenGL,纹理输出为纯色

map - 从何处获取地形数据-免费和付费?

android - unity OnTriggerExit2D 并不总是有效

c# - Unity如何序列化和反序列化复杂的嵌套json?

performance - 加速嵌套循环;可以向量化吗?

Java-jar错误: could not find or load main class

Android Unity vuforia 游戏分辨率太短

ios - Unity/Videoplayer - 如果我加载下一个视频,游戏有时会崩溃