6-14 河流笔直穿过地图单元的三角剖分

  在上一章中,我们将有河流穿过和没河流穿过地地图单元进行了区分,并且创建了一个方法,专门对有河流穿过的地图单元进行构建。在这一章中,我们先分析最简单的情况:河流笔直穿过一个地图单元。并实现这种情况的构建。
  要构建一个笔直的河道,就需要把汇聚到地图单元中心的点,延伸成一条与河道宽度相同的直线。通过观察现在的三角剖分可以发现,如果我们只改变当前河流经过的扇形区域,对其进行拉伸或缩放,那其他两侧相邻的扇形区域就一定会产生破面或重面的情况。所以我们不仅要对当前扇形区域内的顶点进行计算,还会涉及到其两侧相邻的扇形区域内的顶点。
  之前我们用5个顶点,将地图单元的每一条边分城了4等份,河道宽度为二分之一边长。而且通过之前的章节我们已经知道,地图单元的外接圆直径与边长相等。如果要实现河流在地图单元中心也保持这个宽度,那么需要将其两侧的扇形区域中,之前的顶点与地图单元重点重合,现在要变为距中心点四分之一距离处。左右两个各四分之一外接圆半径长度,两个组成二分之一外接圆半径长度,刚好跟河道宽度相同。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{
//河流宽度为二分之一cell边长,又已知边长与外接圆半径(cell外径outerRadius)相同
//为了保持河道在cell中央的时候不会变形,且没有破面等现象产生
//所以要将当前河流穿过区域左右两侧的外径,之前顶点与中心重合,现在变为各自距离中心四分之一处
Vector3 centerL = center + HexMetrics.GetFirstSolidCorner(direction.Previous()) * 0.25f;
Vector3 centerR = center + HexMetrics.GetSecondSolidCorner(direction.Next()) * 0.25f;
}

  我们计算出了两个新的顶点坐标,根据这两个坐标,使用EdgeVertices结构体来计算其余3个顶点的坐标,并将它们全都保存下来。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//根据两侧新的顶点位置,计算出其余顶点的位置
EdgeVertices m = new EdgeVertices(
Vector3.Lerp(centerL, e.v1, 0.5f),
Vector3.Lerp(centerR, e.v5, 0.5f)
);
}

  接着,我们修改中间顶点的高度,使其下降成为河道。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//将河道中心的顶点高度下降
m.v3.y = center.y = e.v3.y;
}

  然后,我们先使用TriangulateEdgeStrip方法来构建连接区域。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//通过计算后的顶点构建连接区域
TriangulateEdgeStrip(m, cell.Color, e, cell.Color);
}

  我们观察构建的结果,发现好像河道还是向内压缩了。河道左右两边的顶点还是在向中间靠拢。结合下图、运行效果与TriangulateEdgeStrip方法来看。我们理想中的构建方式,应该是河道宽度保持不变,而河道左右两侧的平面,越接近中心位置就越窄。也就是下图中所表示的情况。而我们现在因为使用了TriangulateEdgeStrip方法来计算中间的三个顶点。导致这五个顶点是等距分布在梯形的中位线上的,结果也就是上图运行效果的样子。

  所以在这里,我们需要重载TriangulateEdgeStrip方法,使其按照新的方式来计算梯形中位线上的5个顶点。代码如下:

EdgeVertices.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public struct EdgeVertices
{


/// <summary>
/// 有河流经过一条边时,三角形变为梯形,提醒区域中位线的顶点分布不是平均的
/// 参考图 http://magi-melchiorl.gitee.io/pages/Pics/Hexmap/6-14-2.png
/// </summary>
/// <param name="corner1">梯形区域第一个顶点</param>
/// <param name="corner2">提醒区域最后一个顶点</param>
/// <param name="outerStep"></param>
public EdgeVertices(Vector3 corner1, Vector3 corner2, float outerStep)
{
//注意,这里不再是等距分布一条直线上的5个顶点
//而是按照左右较窄,中间宽度为四分之一外径,这样分布的
v1 = corner1;
v2 = Vector3.Lerp(corner1, corner2, outerStep);
v3 = Vector3.Lerp(corner1, corner2, 0.5f);
v4 = Vector3.Lerp(corner1, corner2, 1f - outerStep);
v5 = corner2;
}
}

  现在,我们就可以回到构建河道连接区域的方法中,调用TriangulateEdgeStrip方法的重载。这里要注意我们计算梯形区域中位线上五个顶点的位置中间顶点,及其两侧的顶点计算方式不变,因为河道的宽度不变。主要是最外侧两个顶点的位置需要重新计算。这里观察上图我们发现,中位线是底边长度的四分之三,也就是外径长度的四分之三。而两侧顶点各偏移了八分之一外径长,也就是六分之一的中位线长。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//这里使用新的顶点计算方式,关键是两侧顶点的偏移量
//参考图 http://magi-melchiorl.gitee.io/pages/Pics/Hexmap/6-14-2.png
//注意梯形中位线部分,中间河道为 1/4+1/4,只看左侧顶点偏移,为1/4的一半,也就是1/8
//并且中位线是底边长的3/4,以中位线为计算基础,左右两个顶点其实各偏移了中位线的6/1
//可以这么理解: 1/4 + 1/4 + (1/8 +1/8) 是中位线宽度,其中的1/4其实是中位线的1/3、而一侧偏移量是1/8,也就是中位线的1/6
EdgeVertices m = new EdgeVertices(
Vector3.Lerp(centerL, e.v1, 0.5f),
Vector3.Lerp(centerR, e.v5, 0.5f),
1f / 6f
);


}

  这样,梯形区域的一部分就已经可以正常的构建出来了。如下图:

  接下来我们将构建剩下的部分。梯形的顶边上只有三个顶点,无法用之前的方法进行顶点计算。这里我们手动添加顶点进行构建。首先创建河道两侧的三角形面片。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//之前构建的是梯形中位线到底边的部分
//这里构建中位线到顶边的部分
//由于之前所有方法均不适用于这里,所以手动添加顶点
//首先构建河道两侧三角形区域
AddTriangle(centerL, m.v1, m.v2);
AddTriangleColor(cell.Color);
AddTriangle(centerR, m.v4, m.v5);
AddTriangleColor(cell.Color);
}

  通过观察运行结果,河道两边的三角形区域正确构建了出来。接下来构建中间河道的四边形部分。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
private void TriangulateWithRiver(HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e)
{


//之前构建的是梯形中位线到底边的部分
//这里构建中位线到顶边的部分
//由于之前所有方法均不适用于这里,所以手动添加顶点
//首先构建河道两侧三角形区域
AddTriangle(centerL, m.v1, m.v2);
AddTriangleColor(cell.Color);
AddTriangle(centerR, m.v4, m.v5);
AddTriangleColor(cell.Color);
}

  我们之前没有使用一个颜色值作为参数构建四边形区域的方法,这里我们重载AddQuadColor方法。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HexMesh : MonoBehaviour
{


/// <summary>
/// 使用一个颜色值构建四边形区域,河道在cell中心时候用到
/// </summary>
/// <param name="color">cell自身的颜色值</param>
private void AddQuadColor(Color color)
{
colors.Add(color);
colors.Add(color);
colors.Add(color);
colors.Add(color);
}
}

  至此,我们就完成了集中河流组合中最简单的一种:河流笔直穿过一个地图单元,这种情况下的地图单元构建。接下来的章节中,我们先来分析河流起点和终点应该如何进行三角构建,并实现。

Github代码