Skip to content

Commit fc26de3

Browse files
committed
Pathing command, detailed graph renderer, graph invalidation
1 parent 86d17d5 commit fc26de3

File tree

9 files changed

+160
-66
lines changed

9 files changed

+160
-66
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.command.commands
19+
20+
import com.lambda.brigadier.argument.integer
21+
import com.lambda.brigadier.argument.literal
22+
import com.lambda.brigadier.argument.value
23+
import com.lambda.brigadier.execute
24+
import com.lambda.brigadier.required
25+
import com.lambda.command.LambdaCommand
26+
import com.lambda.module.modules.movement.Pathfinder
27+
import com.lambda.util.extension.CommandBuilder
28+
import com.lambda.util.world.fastVectorOf
29+
30+
object PathCommand : LambdaCommand(
31+
name = "path",
32+
usage = "path <markDirty>",
33+
description = "Move through world"
34+
) {
35+
override fun CommandBuilder.create() {
36+
required(literal("markDirty")) {
37+
required(integer("X", -30000000, 30000000)) { x ->
38+
required(integer("Y", -64, 255)) { y ->
39+
required(integer("Z", -30000000, 30000000)) { z ->
40+
execute {
41+
val dirty = fastVectorOf(x().value(), y().value(), z().value())
42+
Pathfinder.graph.markDirty(dirty)
43+
Pathfinder.graph.updateDirtyNode(dirty)
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}

common/src/main/kotlin/com/lambda/module/modules/movement/Pathfinder.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package com.lambda.module.modules.movement
1919

20-
import com.lambda.Lambda
2120
import com.lambda.context.SafeContext
2221
import com.lambda.event.events.MovementEvent
2322
import com.lambda.event.events.RenderEvent
@@ -77,7 +76,7 @@ object Pathfinder : Module(
7776
private val pathing = PathingSettings(this)
7877

7978
private val target: FastVector get() = targetPos.toFastVec()
80-
private val graph = LazyGraph { origin ->
79+
val graph = LazyGraph { origin ->
8180
runSafe {
8281
moveOptions(origin, ::heuristic, pathing).associate { it.pos to it.cost }
8382
} ?: emptyMap()
@@ -174,7 +173,7 @@ object Pathfinder : Module(
174173
if (pathing.renderCoarsePath) coarsePath.render(event.renderer, Color.YELLOW)
175174
if (pathing.renderRefinedPath) refinedPath.render(event.renderer, Color.GREEN)
176175
if (pathing.renderGoal) event.renderer.buildFilled(Box(target.toBlockPos()), Color.PINK.setAlpha(0.25))
177-
if (pathing.renderGraph) graph.render(event.renderer)
176+
if (pathing.renderGraph) graph.render(event.renderer, pathing.maxRenderObjects)
178177
}
179178

180179
listen<RenderEvent.World> {
@@ -183,7 +182,7 @@ object Pathfinder : Module(
183182
Matrices.push {
184183
val c = mc.gameRenderer.camera.pos.negate()
185184
translate(c.x, c.y, c.z)
186-
graph.buildDebugInfo()
185+
graph.buildDebugInfoRenderer(pathing.maxRenderObjects)
187186
}
188187
}
189188
}
@@ -242,7 +241,7 @@ object Pathfinder : Module(
242241
val dStar = measureTimeMillis {
243242
dStar.updateStart(start)
244243
dStar.computeShortestPath(pathing.cutoffTimeout)
245-
val nodes = dStar.getPath().map { TraverseMove(it, 0.0, NodeType.OPEN, 0.0, 0.0) }
244+
val nodes = dStar.path.map { TraverseMove(it, 0.0, NodeType.OPEN, 0.0, 0.0) }
246245
long = Path(ArrayDeque(nodes))
247246
}
248247
val short: Path

common/src/main/kotlin/com/lambda/pathing/PathingConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ interface PathingConfig {
4343
val renderRefinedPath: Boolean
4444
val renderGoal: Boolean
4545
val renderGraph: Boolean
46+
val renderWeights: Boolean
47+
val renderPositions: Boolean
48+
val maxRenderObjects: Int
4649
val assumeJesus: Boolean
4750

4851
enum class PathingAlgorithm(override val displayName: String) : NamedEnum {

common/src/main/kotlin/com/lambda/pathing/PathingSettings.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,8 @@ class PathingSettings(
5252
override val renderRefinedPath by c.setting("Render Refined Path", true) { vis() && page == Page.Misc }
5353
override val renderGoal by c.setting("Render Goal", true) { vis() && page == Page.Misc }
5454
override val renderGraph by c.setting("Render Graph", false) { vis() && page == Page.Misc }
55+
override val renderWeights by c.setting("Render Weights", false) { vis() && page == Page.Misc && renderGraph }
56+
override val renderPositions by c.setting("Render Positions", false) { vis() && page == Page.Misc && renderGraph }
57+
override val maxRenderObjects by c.setting("Max Render Objects", 1000, 0..100_000) { vis() && page == Page.Misc && renderGraph }
5558
override val assumeJesus by c.setting("Assume Jesus", false) { vis() && page == Page.Misc }
5659
}

common/src/main/kotlin/com/lambda/pathing/dstar/DStarLite.kt

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ import com.lambda.util.world.FastVector
2121
import kotlin.math.min
2222

2323
/**
24-
* D* Lite Implementation.
24+
* Lazy D* Lite Implementation.
2525
*
2626
* We perform a backward search from the goal to the start, so:
2727
* - rhs(goal) = 0, g(goal) = ∞
28-
* - "start" is the robot's current location from which we want a path *to* the goal
28+
* - "start" is the agent's current location from which we want a path *to* the goal
2929
* - 'km' accumulates the heuristic shift so we don't reorder the entire queue after each move
3030
*
3131
* @param graph The graph on which we plan (with forward + reverse adjacency).
3232
* @param heuristic A consistent (or at least nonnegative) heuristic function h(a,b).
33-
* @param start The robot's current position.
33+
* @param start The agent's current position.
3434
* @param goal The fixed goal vertex.
3535
*/
3636
class DStarLite(
@@ -67,10 +67,10 @@ class DStarLite(
6767
}
6868

6969
private fun g(u: FastVector): Double = gMap[u] ?: INF
70-
private fun setG(u: FastVector, value: Double) { gMap[u] = value }
70+
private fun setG(u: FastVector, g: Double) { gMap[u] = g }
7171

7272
private fun rhs(u: FastVector): Double = rhsMap[u] ?: INF
73-
private fun setRHS(u: FastVector, value: Double) { rhsMap[u] = value }
73+
private fun setRHS(u: FastVector, rhs: Double) { rhsMap[u] = rhs }
7474

7575
/**
7676
* Calculates the key for vertex u.
@@ -147,7 +147,7 @@ class DStarLite(
147147
}
148148

149149
/**
150-
* When the robot moves, update the start.
150+
* When the agent moves, update the start.
151151
* The variable km is increased by h(oldStart, newStart) and
152152
* all vertices in the queue are re-keyed.
153153
*/
@@ -170,35 +170,37 @@ class DStarLite(
170170
* Retrieves a path from start to goal by always choosing the successor
171171
* with the lowest g + cost value. If no path is found, the path stops early.
172172
*/
173-
fun getPath(): List<FastVector> {
174-
val path = mutableListOf<FastVector>()
175-
176-
if (!graph.contains(start)) return path.toList()
177-
178-
var current = start
179-
path.add(current)
180-
while (current != goal) {
181-
val successors = graph.successors(current)
182-
if (successors.isEmpty()) break
183-
var bestNext: FastVector? = null
184-
var bestVal = INF
185-
for ((succ, cost) in successors) {
186-
val candidate = g(succ) + cost
187-
if (candidate < bestVal) {
188-
bestVal = candidate
189-
bestNext = succ
173+
val path: List<FastVector>
174+
get() {
175+
val path = mutableListOf<FastVector>()
176+
177+
if (!graph.contains(start)) return path.toList()
178+
179+
var current = start
180+
path.add(current)
181+
while (current != goal) {
182+
val successors = graph.successors(current)
183+
if (successors.isEmpty()) break
184+
var bestNext: FastVector? = null
185+
var bestVal = INF
186+
for ((succ, cost) in successors) {
187+
val candidate = g(succ) + cost
188+
if (candidate < bestVal) {
189+
bestVal = candidate
190+
bestNext = succ
191+
}
190192
}
193+
// No path
194+
if (bestNext == null) break
195+
current = bestNext
196+
path.add(current)
197+
if (path.size > MAX_PATH_LENGTH) break
191198
}
192-
// No path
193-
if (bestNext == null) break
194-
current = bestNext
195-
path.add(current)
196-
if (path.size > 100_000) break
199+
return path
197200
}
198-
return path
199-
}
200201

201202
companion object {
202203
private const val INF = Double.POSITIVE_INFINITY
204+
private const val MAX_PATH_LENGTH = 100_000
203205
}
204206
}

common/src/main/kotlin/com/lambda/pathing/dstar/Key.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ package com.lambda.pathing.dstar
2222
* They are compared lexicographically.
2323
*/
2424
data class Key(val k1: Double, val k2: Double) : Comparable<Key> {
25-
override fun compareTo(other: Key): Int {
26-
return when {
25+
override fun compareTo(other: Key) =
26+
when {
2727
this.k1 < other.k1 -> -1
2828
this.k1 > other.k1 -> 1
2929
this.k2 < other.k2 -> -1
3030
this.k2 > other.k2 -> 1
3131
else -> 0
3232
}
33-
}
3433
}

common/src/main/kotlin/com/lambda/pathing/dstar/LazyGraph.kt

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,23 @@
1717

1818
package com.lambda.pathing.dstar
1919

20+
import com.lambda.graphics.gl.Matrices
21+
import com.lambda.graphics.gl.Matrices.buildWorldProjection
22+
import com.lambda.graphics.gl.Matrices.withVertexTransform
2023
import com.lambda.graphics.renderer.esp.builders.buildLine
24+
import com.lambda.graphics.renderer.esp.builders.buildOutline
2125
import com.lambda.graphics.renderer.esp.global.StaticESP
26+
import com.lambda.graphics.renderer.gui.FontRenderer
27+
import com.lambda.graphics.renderer.gui.FontRenderer.drawString
28+
import com.lambda.util.math.Vec2d
29+
import com.lambda.util.math.div
30+
import com.lambda.util.math.plus
2231
import com.lambda.util.world.FastVector
32+
import com.lambda.util.world.string
2333
import com.lambda.util.world.toCenterVec3d
24-
import com.lambda.util.world.toVec3d
34+
import net.minecraft.util.math.Box
2535
import java.awt.Color
2636
import java.util.concurrent.ConcurrentHashMap
27-
import java.util.concurrent.ConcurrentMap
2837

2938
/**
3039
* A 3D graph that uses FastVector (a Long) to represent 3D nodes.
@@ -49,29 +58,42 @@ class LazyGraph(
4958
val size get() = successors.size
5059

5160
/** Initializes a node if not already initialized, then returns successors. */
52-
fun successors(u: FastVector) =
53-
successors.computeIfAbsent(u) {
61+
fun successors(u: FastVector): Map<FastVector, Double> {
62+
if (u in dirtyNodes) {
63+
updateDirtyNode(u)
64+
}
65+
return successors.getOrPut(u) {
5466
val neighbors = nodeInitializer(u)
5567
neighbors.forEach { (neighbor, cost) ->
5668
predecessors.computeIfAbsent(neighbor) { hashMapOf() }[u] = cost
5769
}
5870
neighbors.toMutableMap()
5971
}
72+
}
6073

6174
/** Initializes predecessors by ensuring successors of neighboring nodes. */
6275
fun predecessors(u: FastVector): Map<FastVector, Double> {
6376
successors(u)
6477
return predecessors[u] ?: emptyMap()
6578
}
6679

67-
fun markDirty(pos: FastVector) {
68-
dirtyNodes.add(pos)
69-
predecessors[pos]?.keys?.let { pred ->
80+
fun markDirty(u: FastVector) {
81+
dirtyNodes.add(u)
82+
predecessors[u]?.keys?.let { pred ->
7083
dirtyNodes.addAll(pred)
7184
}
7285
}
7386

74-
fun clearDirty() = dirtyNodes.clear()
87+
fun updateDirtyNode(u: FastVector) {
88+
// Force re-initialize node and clear existing edges
89+
val newNeighbors = nodeInitializer(u)
90+
successors[u]?.clear()
91+
newNeighbors.forEach { (v, cost) ->
92+
predecessors.getOrPut(v) { mutableMapOf() }[u] = cost
93+
}
94+
successors[u] = newNeighbors.toMutableMap()
95+
dirtyNodes.remove(u)
96+
}
7597

7698
/** Returns the cost of the edge from u to v (or ∞ if none exists) */
7799
fun cost(u: FastVector, v: FastVector): Double = successors(u)[v] ?: Double.POSITIVE_INFINITY
@@ -83,27 +105,40 @@ class LazyGraph(
83105
predecessors.clear()
84106
}
85107

86-
fun render(renderer: StaticESP) {
87-
successors.entries.take(1000).forEach { (origin, neighbors) ->
88-
neighbors.forEach { (neighbor, cost) ->
108+
fun render(renderer: StaticESP, maxElements: Int = 1000) {
109+
successors.entries.take(maxElements).forEach { (origin, neighbors) ->
110+
neighbors.forEach { (neighbor, _) ->
89111
renderer.buildLine(origin.toCenterVec3d(), neighbor.toCenterVec3d(), Color.PINK)
90112
}
91113
}
114+
dirtyNodes.take(maxElements).forEach { node ->
115+
renderer.buildOutline(
116+
Box.of(node.toCenterVec3d(), 0.3, 0.3, 0.3),
117+
Color.RED
118+
)
119+
}
92120
}
93121

94-
fun buildDebugInfo() {
95-
// val projection = buildWorldProjection(blockPos, 0.4, Matrices.ProjRotationMode.TO_CAMERA)
96-
// withVertexTransform(projection) {
97-
// val lines = arrayOf(
98-
// ""
99-
// )
100-
//
101-
// var height = -0.5 * lines.size * (FontRenderer.getHeight() + 2)
102-
//
103-
// lines.forEach {
104-
// drawString(it, Vec2d(-FontRenderer.getWidth(it) * 0.5, height))
105-
// height += FontRenderer.getHeight() + 2
106-
// }
107-
// }
122+
fun buildDebugInfoRenderer(maxElements: Int = 1000) {
123+
successors.entries.take(maxElements).forEach { (v, u) ->
124+
val mode = Matrices.ProjRotationMode.TO_CAMERA
125+
val scale = 0.4
126+
val pos = v.toCenterVec3d()
127+
val nodeProjection = buildWorldProjection(pos, scale, mode)
128+
withVertexTransform(nodeProjection) {
129+
val msg = v.string
130+
drawString(msg, Vec2d(-FontRenderer.getWidth(msg) * 0.5, 0.0))
131+
}
132+
u.forEach { (neighbor, cost) ->
133+
val centerV = v.toCenterVec3d()
134+
val centerN = neighbor.toCenterVec3d()
135+
val center = (centerV + centerN) / 2.0
136+
val projection = buildWorldProjection(center, scale, mode)
137+
withVertexTransform(projection) {
138+
val msg = "c: %.3f".format(cost)
139+
drawString(msg, Vec2d(-FontRenderer.getWidth(msg) * 0.5, 0.0))
140+
}
141+
}
142+
}
108143
}
109144
}

common/src/main/kotlin/com/lambda/util/world/Position.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,6 @@ internal fun Long.bitSetTo(value: Long, position: Int, length: Int): Long {
275275
val mask = (1L shl length) - 1L
276276
return this and (mask shl position).inv() or (value and mask shl position)
277277
}
278+
279+
val FastVector.string: String
280+
get() = "($x, $y, $z)"

0 commit comments

Comments
 (0)