From 997bf1bf7a796a8c50af6a64fc057d0094e9829e Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:17:22 +0200 Subject: [PATCH] added select near look in Graph Editor Co-authored-by: nea --- .../config/features/dev/GraphConfig.java | 5 ++ .../at/hannibal2/skyhanni/test/GraphEditor.kt | 24 ++++++ .../at/hannibal2/skyhanni/utils/LorenzVec.kt | 3 + .../hannibal2/skyhanni/utils/RaycastUtils.kt | 73 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/RaycastUtils.kt diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/GraphConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/GraphConfig.java index acf9ac67d9e0..dda1ddb1e50c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/dev/GraphConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/GraphConfig.java @@ -32,6 +32,11 @@ public class GraphConfig { @ConfigEditorKeybind(defaultKey = -98) // Middle Mouse public int selectKey = -98; + @Expose + @ConfigOption(name = "Select near look", desc = "Select the node closest to where you are looking.") + @ConfigEditorKeybind(defaultKey = Keyboard.KEY_NONE) + public int selectRaycastKey = Keyboard.KEY_NONE; + @Expose @ConfigOption(name = "Connect Key", desc = "Connect the nearest node with the active node. If the nodes are already connected removes the connection.") @ConfigEditorKeybind(defaultKey = Keyboard.KEY_C) diff --git a/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt b/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt index 16c350b14f31..5a6c7e62f000 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt @@ -26,6 +26,7 @@ import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.OSUtils +import at.hannibal2.skyhanni.utils.RaycastUtils import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine_nea import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled @@ -127,6 +128,7 @@ object GraphEditor { if (!inEditMode && !inTextMode) { add("§ePlace: §6${KeyboardManager.getKeyName(config.placeKey)}") add("§eSelect: §6${KeyboardManager.getKeyName(config.selectKey)}") + add("§eSelect (Look): §6${KeyboardManager.getKeyName(config.selectRaycastKey)}") add("§eConnect: §6${KeyboardManager.getKeyName(config.connectKey)}") add("§eTest: §6${KeyboardManager.getKeyName(config.dijkstraKey)}") add("§eVision: §6${KeyboardManager.getKeyName(config.throughBlocksKey)}") @@ -346,6 +348,28 @@ object GraphEditor { closedNode } } + if (config.selectRaycastKey.isKeyClicked()) { + val playerRay = RaycastUtils.createPlayerLookDirectionRay() + var minimumDistance = Double.MAX_VALUE + var minimumNode: GraphingNode? = null + for (node in nodes) { + val nodeCenterPosition = node.position.add(0.5, 0.5, 0.5) + val distance = RaycastUtils.findDistanceToRay(playerRay, nodeCenterPosition) + if (distance > minimumDistance) { + continue + } + if (minimumDistance > 1.0) { + minimumNode = node + minimumDistance = distance + continue + } + if (minimumNode == null || minimumNode.position.distanceSqToPlayer() > node.position.distanceSqToPlayer()) { + minimumNode = node + minimumDistance = distance + } + } + activeNode = minimumNode + } if (activeNode != closedNode && config.connectKey.isKeyClicked()) { val edge = getEdgeIndex(activeNode, closedNode) if (edge == null) { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt index 51bb8b72cd81..a573ed78daeb 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt @@ -9,6 +9,7 @@ import net.minecraft.util.BlockPos import net.minecraft.util.Rotations import net.minecraft.util.Vec3 import kotlin.math.abs +import kotlin.math.absoluteValue import kotlin.math.acos import kotlin.math.cos import kotlin.math.max @@ -126,6 +127,8 @@ data class LorenzVec( fun lengthSquared(): Double = x * x + y * y + z * z fun length(): Double = sqrt(this.lengthSquared()) + fun isNormalized(tolerance: Double = 0.01) = (lengthSquared() - 1.0).absoluteValue < tolerance + fun isZero(): Boolean = x == 0.0 && y == 0.0 && z == 0.0 fun clone(): LorenzVec = LorenzVec(x, y, z) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RaycastUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RaycastUtils.kt new file mode 100644 index 000000000000..d5aafdcd3c8c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/RaycastUtils.kt @@ -0,0 +1,73 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.client.Minecraft + +object RaycastUtils { + + data class Ray( + val origin: LorenzVec, + val direction: LorenzVec, + ) { + init { + require(direction.isNormalized()) + } + } + + data class Plane( + val origin: LorenzVec, + val normal: LorenzVec, + ) { + init { + require(normal.isNormalized()) + } + } + + fun createPlayerLookDirectionRay(): Ray { + return Ray( + LocationUtils.playerEyeLocation(), + Minecraft.getMinecraft().thePlayer.lookVec.toLorenzVec() + ) + } + + /** + * Create a plane that contains [point] and is orthogonal to [ray]. + */ + fun createOrthogonalPlaneToRayAtPoint( + ray: Ray, + point: LorenzVec, + ): Plane { + return Plane(point, ray.direction) + } + + /** + * Intersect a plane (of any orientation) with a ray. The ray and plane may not be parallel to each other. + */ + fun intersectPlaneWithRay(plane: Plane, ray: Ray): LorenzVec { +// require(plane.normal.dotProduct(ray.direction).absoluteValue != 0.0) + val intersectionPointDistanceAlongRay = + (plane.normal.dotProduct(plane.origin) - plane.normal.dotProduct(ray.origin)) / plane.normal.dotProduct(ray.direction) + return ray.origin + ray.direction.scale(intersectionPointDistanceAlongRay) + } + + /** + * Finds the distance between the given ray and the point. If the point is behind the ray origin (according to the ray's direction), + * returns [Double.MAX_VALUE] instead. + */ + fun findDistanceToRay(ray: Ray, point: LorenzVec): Double { + val plane = createOrthogonalPlaneToRayAtPoint(ray, point) + val intersectionPoint = intersectPlaneWithRay(plane, ray) + if ((intersectionPoint - ray.origin).dotProduct(ray.direction) < 0) return Double.MAX_VALUE + return intersectionPoint.distance(point) + } + + inline fun createDistanceToRayEstimator(ray: Ray, crossinline position: (T) -> LorenzVec): (T) -> Double { + return { + findDistanceToRay(ray, position(it)) + } + } + + fun List.findClosestPointToRay(ray: Ray, positionExtractor: (T) -> LorenzVec): T? { + return minByOrNull(createDistanceToRayEstimator(ray, positionExtractor)) + } + +}