🐛 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

4
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/nats-io/nats.go v1.25.0
github.com/panjf2000/gnet v1.6.6
github.com/smartystreets/goconvey v1.8.0
github.com/sony/sonyflake v1.1.0
github.com/tealeg/xlsx v1.0.5
github.com/tidwall/gjson v1.14.4
@ -46,7 +47,6 @@ require (
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/smartystreets/assertions v1.13.1 // indirect
github.com/smartystreets/goconvey v1.8.0 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
@ -59,11 +59,9 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect

7
go.sum
View File

@ -24,8 +24,6 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@ -72,7 +70,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
@ -210,8 +207,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -264,8 +259,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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:

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
}

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()

View File

@ -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
// =================================
}

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]

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)

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()