🐛 navmesh 无法正确寻路问题处理,增加测试用例 navmesh_example_test.go
This commit is contained in:
parent
07246aee7b
commit
f3998420bb
4
go.mod
4
go.mod
|
@ -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
7
go.sum
|
@ -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=
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// =================================
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue