✨ 网格寻路 NavMesh 实现
This commit is contained in:
parent
61cf79d172
commit
686ab9de3a
|
@ -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
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ func NewNavMesh[V generic.SignedNumber](shapes []geometry.Shape[V], meshShrinkAm
|
||||||
meshShrinkAmount: meshShrinkAmount,
|
meshShrinkAmount: meshShrinkAmount,
|
||||||
}
|
}
|
||||||
for i, shape := range shapes {
|
for i, shape := range shapes {
|
||||||
nm.meshShapes[i] = newShape(shape)
|
nm.meshShapes[i] = newShape(i, shape)
|
||||||
}
|
}
|
||||||
nm.generateLink()
|
nm.generateLink()
|
||||||
return nm
|
return nm
|
||||||
|
@ -129,6 +129,39 @@ func (slf *NavMesh[V]) FindPath(start, end geometry.Point[V]) (result []geometry
|
||||||
}
|
}
|
||||||
|
|
||||||
path = append([]*shape[V]{startShape}, path...)
|
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() {
|
func (slf *NavMesh[V]) generateLink() {
|
||||||
|
|
|
@ -5,8 +5,9 @@ import (
|
||||||
"github.com/kercylan98/minotaur/utils/geometry"
|
"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]{
|
return &shape[V]{
|
||||||
|
id: id,
|
||||||
Shape: s,
|
Shape: s,
|
||||||
centroid: geometry.CalcRectangleCentroid(s),
|
centroid: geometry.CalcRectangleCentroid(s),
|
||||||
boundingRadius: geometry.CalcBoundingRadius(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 {
|
type shape[V generic.SignedNumber] struct {
|
||||||
|
id int
|
||||||
geometry.Shape[V]
|
geometry.Shape[V]
|
||||||
links []*shape[V]
|
links []*shape[V]
|
||||||
portals []geometry.Line[V]
|
portals []geometry.Line[V]
|
||||||
|
|
Loading…
Reference in New Issue