网格寻路 NavMesh 实现

This commit is contained in:
kercylan98 2023-06-19 20:17:40 +08:00
parent 61cf79d172
commit 686ab9de3a
3 changed files with 120 additions and 2 deletions

View File

@ -0,0 +1,83 @@
package navmesh
import (
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/geometry"
)
type funnel[V generic.SignedNumber] struct {
path []geometry.Point[V]
portals [][2]geometry.Point[V]
}
func (slf *funnel[V]) push(point1, point2 geometry.Point[V]) {
slf.portals = append(slf.portals, [2]geometry.Point[V]{point1, point2})
}
func (slf *funnel[V]) pushSingle(point geometry.Point[V]) {
slf.portals = append(slf.portals, [2]geometry.Point[V]{point, point})
}
func (slf *funnel[V]) stringPull() []geometry.Point[V] {
var (
portals = slf.portals
points []geometry.Point[V]
apexIndex = 0
leftIndex = 0
rightIndex = 0
portalApex = portals[0][0]
portalLeft = portals[0][0]
portalRight = portals[0][1]
)
points = append(points, portalApex)
for i := 1; i < len(portals); i++ {
lr := portals[i]
left, right := lr[0], lr[1]
if geometry.CalcTriangleTwiceArea(portalApex, portalRight, right) <= V(0) {
if portalApex.Equal(portalRight) || geometry.CalcTriangleTwiceArea(portalApex, portalLeft, right) > V(0) {
portalRight = right
rightIndex = i
} else {
points = append(points, portalLeft)
portalApex = portalLeft
apexIndex = leftIndex
portalLeft = portalApex
portalRight = portalApex
leftIndex = apexIndex
rightIndex = apexIndex
i = apexIndex
continue
}
}
if geometry.CalcTriangleTwiceArea(portalApex, portalLeft, left) >= V(0) {
if portalApex.Equal(portalLeft) || geometry.CalcTriangleTwiceArea(portalApex, portalRight, left) < V(0) {
portalLeft = left
leftIndex = i
} else {
points = append(points, portalRight)
portalApex = portalRight
apexIndex = rightIndex
portalLeft = portalApex
portalRight = portalApex
leftIndex = apexIndex
rightIndex = apexIndex
i = apexIndex
continue
}
}
}
if len(points) == 0 || !points[len(points)-1].Equal(portals[len(portals)-1][0]) {
points = append(points, portals[len(portals)-1][0])
}
slf.path = points
return slf.path
}

View File

@ -13,7 +13,7 @@ func NewNavMesh[V generic.SignedNumber](shapes []geometry.Shape[V], meshShrinkAm
meshShrinkAmount: meshShrinkAmount,
}
for i, shape := range shapes {
nm.meshShapes[i] = newShape(shape)
nm.meshShapes[i] = newShape(i, shape)
}
nm.generateLink()
return nm
@ -129,6 +129,39 @@ func (slf *NavMesh[V]) FindPath(start, end geometry.Point[V]) (result []geometry
}
path = append([]*shape[V]{startShape}, path...)
funnel := new(funnel[V])
funnel.pushSingle(start)
for i := 0; i < len(path)-1; i++ {
current := path[i]
next := path[i+1]
var portal geometry.Line[V]
var find bool
for i := 0; i < len(current.links); i++ {
if current.links[i].id == next.id {
portal = current.portals[i]
find = true
}
}
if !find {
panic("not found portal")
}
funnel.push(portal.GetStart(), portal.GetEnd())
}
funnel.pushSingle(end)
funnel.stringPull()
var lastPoint geometry.Point[V]
for i, point := range funnel.path {
var np = point.Copy()
if i == 0 || !np.Equal(lastPoint) {
result = append(result, np)
}
lastPoint = np
}
return result
}
func (slf *NavMesh[V]) generateLink() {

View File

@ -5,8 +5,9 @@ import (
"github.com/kercylan98/minotaur/utils/geometry"
)
func newShape[V generic.SignedNumber](s geometry.Shape[V]) *shape[V] {
func newShape[V generic.SignedNumber](id int, s geometry.Shape[V]) *shape[V] {
return &shape[V]{
id: id,
Shape: s,
centroid: geometry.CalcRectangleCentroid(s),
boundingRadius: geometry.CalcBoundingRadius(s),
@ -15,6 +16,7 @@ func newShape[V generic.SignedNumber](s geometry.Shape[V]) *shape[V] {
}
type shape[V generic.SignedNumber] struct {
id int
geometry.Shape[V]
links []*shape[V]
portals []geometry.Line[V]