🐛 navmesh 无法正确寻路问题处理,增加测试用例 navmesh_example_test.go

This commit is contained in:
kercylan98
2023-06-20 15:22:53 +08:00
parent 07246aee7b
commit f3998420bb
9 changed files with 169 additions and 24 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ func GetDirectionNextWithCoordinateArray[V generic.SignedNumber](direction Direc
}
// GetDirectionNextWithPos 获取位置在特定宽度和特定方向上的下一个位置
// - 需要注意的是,在左右方向时,当下一个位置不在游戏区域内时,将会返回上一行的末位置或下一行的首位置
// - 需要注意的是,在左右方向时,当下一个位置不在矩形区域内时,将会返回上一行的末位置或下一行的首位置
func GetDirectionNextWithPos[V generic.SignedNumber](direction Direction, width, pos V) V {
switch direction {
case DirectionUp:
+30 -8
View File
@@ -14,9 +14,29 @@ func NewLine[V generic.SignedNumber](start, end Point[V]) Line[V] {
return Line[V]{start, end}
}
// NewLineCap 创建一根包含数据的线段
func NewLineCap[V generic.SignedNumber, Data any](start, end Point[V], data Data) LineCap[V, Data] {
return LineCap[V, Data]{NewLine(start, end), data}
}
// NewLineCapWithLine 通过已有线段创建一根包含数据的线段
func NewLineCapWithLine[V generic.SignedNumber, Data any](line Line[V], data Data) LineCap[V, Data] {
return LineCap[V, Data]{line, data}
}
// Line 通过两个点表示一根线段
type Line[V generic.SignedNumber] [2]Point[V]
// LineCap 可以包含一份额外数据的线段
type LineCap[V generic.SignedNumber, Data any] struct {
Line[V]
Data Data
}
func (slf *LineCap[V, Data]) GetData() Data {
return slf.Data
}
// GetPoints 获取该线段的两个点
func (slf Line[V]) GetPoints() [2]Point[V] {
return slf
@@ -92,11 +112,13 @@ func CalcLineIsCollinear[V generic.SignedNumber](line1, line2 Line[V], tolerance
// CalcLineIsOverlap 通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段
func CalcLineIsOverlap[V generic.SignedNumber](line1, line2 Line[V]) (line Line[V], overlap bool) {
var shapes = []Shape[V]{
{line1.GetStart(), line1.GetEnd(), line1.GetStart()},
{line1.GetStart(), line1.GetEnd(), line1.GetEnd()},
{line2.GetStart(), line2.GetEnd(), line2.GetStart()},
{line2.GetStart(), line2.GetEnd(), line2.GetEnd()},
l1ps, l1pe := NewPointCapWithPoint(line1.GetStart(), true), NewPointCapWithPoint(line1.GetEnd(), true)
l2ps, l2pe := NewPointCapWithPoint(line2.GetStart(), false), NewPointCapWithPoint(line2.GetEnd(), false)
var shapes = [][]PointCap[V, bool]{
{l1ps, l1pe, l1ps},
{l1ps, l1pe, l1pe},
{l2ps, l2pe, l2ps},
{l2ps, l2pe, l2pe},
}
sort.Slice(shapes, func(i, j int) bool {
a, b := shapes[i], shapes[j]
@@ -109,10 +131,10 @@ func CalcLineIsOverlap[V generic.SignedNumber](line1, line2 Line[V]) (line Line[
}
})
notOverlap := shapes[0][0].Equal(shapes[1][0]) && shapes[0][1].Equal(shapes[1][1])
singlePointOverlap := shapes[1][2].Equal(shapes[2][2])
notOverlap := shapes[1][0].GetData() == shapes[2][0].GetData()
singlePointOverlap := shapes[1][2].Equal(shapes[2][2].Point)
if notOverlap || singlePointOverlap {
return line, false
}
return NewLine(shapes[1][2], shapes[2][2]), true
return NewLine(shapes[1][2].Point, shapes[2][2].Point), true
}
+5 -3
View File
@@ -1,9 +1,9 @@
package navmesh
import (
"github.com/kercylan98/minotaur/utils/astar"
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/geometry"
"github.com/kercylan98/minotaur/utils/geometry/astar"
"github.com/kercylan98/minotaur/utils/maths"
)
@@ -178,6 +178,9 @@ func (slf *NavMesh[V]) FindPath(start, end geometry.Point[V]) (result []geometry
for i := 0; i < len(path)-1; i++ {
current := path[i]
next := path[i+1]
if current.id == next.id {
continue
}
var portal geometry.Line[V]
var find bool
@@ -208,8 +211,7 @@ func (slf *NavMesh[V]) FindPath(start, end geometry.Point[V]) (result []geometry
}
func (slf *NavMesh[V]) generateLink() {
refer := len(slf.meshShapes)
for i := 0; i < refer; i++ {
for i := 0; i < len(slf.meshShapes); i++ {
shapePkg := slf.meshShapes[i]
shapeCentroid := shapePkg.Centroid()
shapeBoundingRadius := shapePkg.BoundingRadius()
@@ -0,0 +1,119 @@
package navmesh_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/geometry"
"github.com/kercylan98/minotaur/utils/geometry/navmesh"
"github.com/kercylan98/minotaur/utils/maths"
)
func ExampleNavMesh_FindPath() {
fp := geometry.FloorPlan{
"=================================",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"X X",
"=================================",
}
var walkable []geometry.Shape[int]
walkable = append(walkable,
geometry.NewShape(
geometry.NewPoint(5, 5),
geometry.NewPoint(15, 5),
geometry.NewPoint(15, 15),
geometry.NewPoint(5, 15),
),
geometry.NewShape(
geometry.NewPoint(15, 5),
geometry.NewPoint(25, 5),
geometry.NewPoint(25, 15),
geometry.NewPoint(15, 15),
),
geometry.NewShape(
geometry.NewPoint(15, 15),
geometry.NewPoint(25, 15),
geometry.NewPoint(25, 25),
geometry.NewPoint(15, 25),
),
)
for _, shape := range walkable {
for _, edge := range shape.Edges() {
sx, bx := maths.MinMax(edge.GetStart().GetX(), edge.GetEnd().GetX())
sy, by := maths.MinMax(edge.GetStart().GetY(), edge.GetEnd().GetY())
for x := sx; x <= bx; x++ {
for y := sy; y <= by; y++ {
fp.Put(geometry.NewPoint[int](int(x), int(y)), '+')
}
}
}
}
nm := navmesh.NewNavMesh(walkable, 0)
path := nm.FindPath(
geometry.NewPoint(6, 6),
geometry.NewPoint(18, 24),
)
for _, point := range path {
fp.Put(geometry.NewPoint(point.GetX(), point.GetY()), 'G')
}
fmt.Println(fp)
// Output:
// =================================
// X X
// X X
// X X
// X X
// X +++++++++++++++++++++ X
// X +G + + X
// X + + + X
// X + + + X
// X + + + X
// X + + + X
// X + + + X
// X + + + X
// X + + + X
// X + + + X
// X ++++++++++G++++++++++ X
// X + + X
// X + + X
// X + + X
// X + + X
// X + + X
// X + + X
// X + + X
// X + + X
// X + G + X
// X +++++++++++ X
// X X
// X X
// =================================
}
+8
View File
@@ -74,6 +74,14 @@ func NewPointCapWithData[V generic.SignedNumber, D any](x, y V, data D) PointCap
}
}
// NewPointCapWithPoint 通过设置数据的方式创建一个由已有坐标组成的点,这个点具有一个数据容量
func NewPointCapWithPoint[V generic.SignedNumber, D any](point Point[V], data D) PointCap[V, D] {
return PointCap[V, D]{
Point: point,
Data: data,
}
}
// PointCap 表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量
type PointCap[V generic.SignedNumber, D any] struct {
Point[V]
+4 -1
View File
@@ -328,7 +328,10 @@ func GetRectangleFullPos[V generic.SignedNumber](width, height V) (result []V) {
func CalcRectangleCentroid[V generic.SignedNumber](shape Shape[V]) Point[V] {
x, y := V(0), V(0)
length := V(shape.PointCount())
for _, point := range shape.Points() {
x += point.GetX()
y += point.GetY()
}
x /= length
y /= length
return NewPoint(x, y)
+1 -1
View File
@@ -71,7 +71,7 @@ func (slf Shape[V]) PointCount() int {
func (slf Shape[V]) Contains(point Point[V]) bool {
x, y := point.GetXY()
inside := false
for i, j := -1, len(slf)-1; i < len(slf); j, i = i, i+1 {
for i, j := 0, len(slf)-1; i < len(slf); i, j = i+1, i {
ix := slf[i].GetX()
iy := slf[i].GetY()
jx := slf[j].GetX()