You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何获取顺序正确的顶点世界坐标?

省份边界渲染异常示意图

Unity中获取顺序正确的省份边界顶点世界坐标

我有一些代表省份边界的顶点,希望在Unity中获取它们顺序正确的世界坐标。目前用以下代码能获取顶点世界坐标,但顺序不正确,通过LineRenderer渲染时无法正确呈现边界。

[SerializeField] private MeshFilter _mesh;
[SerializeField] private LineRenderer _lineRenderer;

[Serializable]
public class Border
{
    public Mesh borderMesh;
    public BorderProvince[] sharedBetween = new BorderProvince[2];
    public bool isACountryBorder;
}

[SerializeField] private List<Border> _borders;

private List<Vector3> _drawnBorder = new List<Vector3>();

[ContextMenu("Update Border Drawer")]
private void UpdateBorderDrawer()
{
    if (_borders.Count == 0 || _lineRenderer == null) return;

    _drawnBorder = new List<Vector3>();

    Matrix4x4 localToWorld = transform.localToWorldMatrix;

    foreach (Border border in _borders)
    {
        if (border.sharedBetween[0].ID != border.sharedBetween[1].ID)
        {
            border.isACountryBorder = true;
            for (int i = 0; i < border.borderMesh.vertices.Length; i++)
            {
                _drawnBorder.Add(localToWorld.MultiplyPoint3x4(border.borderMesh.vertices[i]));
            }
        }
    }

    _lineRenderer.positionCount = _drawnBorder.Count;
    _lineRenderer.SetPositions(_drawnBorder.ToArray());
}

解决思路及方案

Mesh的顶点数组本身是无序的,它的顺序基于三角面索引排列,而非边界的连续路径顺序。要得到正确的边界顶点顺序,需从Mesh的拓扑结构中重建连续路径:

  1. 提取边界边并构建连续路径
    遍历Mesh三角面索引,收集仅出现一次的边界边(内部边会被两个三角面共享),再通过邻接关系拼接出连续的顶点路径:

    private List<Vector3> GetOrderedBorderVertices(Mesh mesh, Matrix4x4 localToWorld)
    {
        List<Vector3> orderedVertices = new List<Vector3>();
        if (mesh == null) return orderedVertices;
    
        // 统计所有边的出现次数
        Dictionary<(int, int), int> edgeCount = new Dictionary<(int, int), int>();
        int[] triangles = mesh.triangles;
    
        for (int i = 0; i < triangles.Length; i += 3)
        {
            AddEdge(triangles[i], triangles[i+1], edgeCount);
            AddEdge(triangles[i+1], triangles[i+2], edgeCount);
            AddEdge(triangles[i+2], triangles[i], edgeCount);
        }
    
        // 筛选出仅出现一次的边界边
        List<(int, int)> borderEdges = edgeCount.Where(kv => kv.Value == 1).Select(kv => kv.Key).ToList();
        if (borderEdges.Count == 0) return orderedVertices;
    
        // 构建顶点邻接表
        Dictionary<int, List<int>> adjacency = new Dictionary<int, List<int>>();
        foreach (var edge in borderEdges)
        {
            if (!adjacency.ContainsKey(edge.Item1)) adjacency[edge.Item1] = new List<int>();
            adjacency[edge.Item1].Add(edge.Item2);
            if (!adjacency.ContainsKey(edge.Item2)) adjacency[edge.Item2] = new List<int>();
            adjacency[edge.Item2].Add(edge.Item1);
        }
    
        // 拼接连续路径
        int currentVertex = borderEdges[0].Item1;
        int previousVertex = -1;
        orderedVertices.Add(localToWorld.MultiplyPoint3x4(mesh.vertices[currentVertex]));
    
        while (orderedVertices.Count < borderEdges.Count)
        {
            foreach (int nextVertex in adjacency[currentVertex])
            {
                if (nextVertex != previousVertex)
                {
                    orderedVertices.Add(localToWorld.MultiplyPoint3x4(mesh.vertices[nextVertex]));
                    previousVertex = currentVertex;
                    currentVertex = nextVertex;
                    break;
                }
            }
        }
    
        return orderedVertices;
    }
    
    private void AddEdge(int a, int b, Dictionary<(int, int), int> edgeCount)
    {
        // 按索引大小排序,确保边的唯一性
        var edge = a < b ? (a, b) : (b, a);
        edgeCount[edge] = edgeCount.ContainsKey(edge) ? edgeCount[edge] + 1 : 1;
    }
    
  2. 修改原更新方法
    将原代码中直接添加顶点的逻辑替换为上述有序顶点获取方法:

    [ContextMenu("Update Border Drawer")]
    private void UpdateBorderDrawer()
    {
        if (_borders.Count == 0 || _lineRenderer == null) return;
    
        _drawnBorder = new List<Vector3>();
    
        Matrix4x4 localToWorld = transform.localToWorldMatrix;
    
        foreach (Border border in _borders)
        {
            if (border.sharedBetween[0].ID != border.sharedBetween[1].ID)
            {
                border.isACountryBorder = true;
                // 获取有序顶点并添加到列表
                var orderedVertices = GetOrderedBorderVertices(border.borderMesh, localToWorld);
                _drawnBorder.AddRange(orderedVertices);
            }
        }
    
        _lineRenderer.positionCount = _drawnBorder.Count;
        _lineRenderer.SetPositions(_drawnBorder.ToArray());
    }
    
  3. 额外注意事项

    • 若Border Mesh包含多个独立边界线段,需拆分处理每个路径
    • 可在编辑器中提前预处理顶点顺序,减少运行时计算开销

内容的提问来源于stack exchange,提问作者Adam Brown

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:01:07