Skip to content

Commit ebbd322

Browse files
committed
Dynamic interpolated ESP, splines, EntityESP
1 parent 3503e64 commit ebbd322

19 files changed

+2651
-667
lines changed

src/main/kotlin/com/lambda/graphics/RenderMain.kt

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,82 @@
1717

1818
package com.lambda.graphics
1919

20+
import com.lambda.Lambda.mc
2021
import com.lambda.event.EventFlow.post
2122
import com.lambda.event.events.RenderEvent
2223
import com.lambda.event.events.TickEvent
2324
import com.lambda.event.listener.SafeListener.Companion.listen
2425
import com.lambda.graphics.gl.Matrices
2526
import com.lambda.graphics.gl.Matrices.resetMatrices
26-
import com.lambda.graphics.mc.LambdaRenderPipelines
2727
import com.lambda.graphics.mc.TransientRegionESP
28+
import net.minecraft.util.math.Vec3d
2829
import org.joml.Matrix4f
30+
import org.joml.Vector2f
31+
import org.joml.Vector4f
2932

3033
object RenderMain {
31-
val StaticESP = TransientRegionESP("Static")
32-
val DynamicESP = TransientRegionESP("Dynamic")
34+
@JvmStatic
35+
val StaticESP = TransientRegionESP("Static", true)
36+
37+
@JvmStatic
38+
val DynamicESP = TransientRegionESP("Dynamic", false)
3339

3440
val projectionMatrix = Matrix4f()
3541
val modelViewMatrix
3642
get() = Matrices.peek()
3743
val projModel: Matrix4f
3844
get() = Matrix4f(projectionMatrix).mul(modelViewMatrix)
3945

46+
/**
47+
* Project a world position to screen coordinates. Returns null if the position is behind the
48+
* camera or off-screen.
49+
*
50+
* @param worldPos The world position to project
51+
* @return Screen coordinates (x, y) in pixels, or null if not visible
52+
*/
53+
fun worldToScreen(worldPos: Vec3d): Vector2f? {
54+
val camera = mc.gameRenderer?.camera ?: return null
55+
val cameraPos = camera.pos
56+
57+
// Camera-relative position
58+
val relX = (worldPos.x - cameraPos.x).toFloat()
59+
val relY = (worldPos.y - cameraPos.y).toFloat()
60+
val relZ = (worldPos.z - cameraPos.z).toFloat()
61+
62+
// Apply projection * modelview matrix
63+
val vec = Vector4f(relX, relY, relZ, 1f)
64+
projModel.transform(vec)
65+
66+
// Behind camera check
67+
if (vec.w <= 0) return null
68+
69+
// Perspective divide to get NDC
70+
val ndcX = vec.x / vec.w
71+
val ndcY = vec.y / vec.w
72+
val ndcZ = vec.z / vec.w
73+
74+
// Off-screen check (NDC is -1 to 1)
75+
if (ndcZ < -1 || ndcZ > 1) return null
76+
77+
// NDC to screen coordinates (Y is flipped in screen space)
78+
val window = mc.window
79+
val screenX = (ndcX + 1f) * 0.5f * window.framebufferWidth
80+
val screenY = (1f - ndcY) * 0.5f * window.framebufferHeight
81+
82+
return Vector2f(screenX, screenY)
83+
}
84+
85+
/** Check if a world position is visible on screen. */
86+
fun isOnScreen(worldPos: Vec3d): Boolean = worldToScreen(worldPos) != null
87+
4088
@JvmStatic
4189
fun render3D(positionMatrix: Matrix4f, projMatrix: Matrix4f) {
4290
resetMatrices(positionMatrix)
4391
projectionMatrix.set(projMatrix)
4492

4593
// Render transient ESPs using the new pipeline
46-
StaticESP.render(false) // Depth tested
47-
DynamicESP.render(true) // Through walls
94+
StaticESP.render() // Uses internal depthTest flag (true)
95+
DynamicESP.render() // Uses internal depthTest flag (false)
4896

4997
RenderEvent.Render.post()
5098
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2025 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.graphics.esp
19+
20+
import com.lambda.graphics.mc.ChunkedRegionESP
21+
import com.lambda.module.Module
22+
23+
@DslMarker
24+
annotation class EspDsl
25+
26+
fun Module.chunkedEsp(
27+
name: String,
28+
depthTest: Boolean = false,
29+
update: ShapeScope.(net.minecraft.world.World, com.lambda.util.world.FastVector) -> Unit
30+
): ChunkedRegionESP {
31+
return ChunkedRegionESP(this, name, depthTest, update)
32+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2025 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.graphics.esp
19+
20+
import com.lambda.Lambda.mc
21+
import com.lambda.graphics.mc.LambdaRenderPipelines
22+
import com.lambda.graphics.mc.RegionRenderer
23+
import com.lambda.graphics.mc.RenderRegion
24+
import com.lambda.util.extension.tickDelta
25+
import com.mojang.blaze3d.systems.RenderSystem
26+
import java.util.concurrent.ConcurrentHashMap
27+
import kotlin.math.floor
28+
import org.joml.Matrix4f
29+
import org.joml.Vector3f
30+
import org.joml.Vector4f
31+
32+
/**
33+
* Base class for region-based ESP systems. Provides unified rendering logic and region management.
34+
*/
35+
abstract class RegionESP(val name: String, val depthTest: Boolean) {
36+
protected val renderers = ConcurrentHashMap<Long, RegionRenderer>()
37+
38+
/** Get or create a ShapeScope for a specific world position. */
39+
open fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) {}
40+
41+
/** Upload collected geometry to GPU. Must be called on main thread. */
42+
open fun upload() {}
43+
44+
/** Clear all geometry data. */
45+
abstract fun clear()
46+
47+
/** Close and release all GPU resources. */
48+
open fun close() {
49+
renderers.values.forEach { it.close() }
50+
renderers.clear()
51+
clear()
52+
}
53+
54+
/**
55+
* Render all active regions.
56+
* @param tickDelta Progress within current tick (used for interpolation)
57+
*/
58+
open fun render(tickDelta: Float = mc.tickDelta) {
59+
val camera = mc.gameRenderer?.camera ?: return
60+
val cameraPos = camera.pos
61+
62+
val activeRenderers = renderers.values.filter { it.hasData() }
63+
if (activeRenderers.isEmpty()) return
64+
65+
val modelViewMatrix = com.lambda.graphics.RenderMain.modelViewMatrix
66+
val transforms = activeRenderers.map { renderer ->
67+
val offset = renderer.region.computeCameraRelativeOffset(cameraPos)
68+
val modelView = Matrix4f(modelViewMatrix).translate(offset)
69+
70+
val dynamicTransform = RenderSystem.getDynamicUniforms()
71+
.write(
72+
modelView,
73+
Vector4f(1f, 1f, 1f, 1f),
74+
Vector3f(0f, 0f, 0f),
75+
Matrix4f()
76+
)
77+
renderer to dynamicTransform
78+
}
79+
80+
// Render Faces
81+
RegionRenderer.createRenderPass("$name Faces")?.use { pass ->
82+
val pipeline =
83+
if (depthTest) LambdaRenderPipelines.ESP_QUADS
84+
else LambdaRenderPipelines.ESP_QUADS_THROUGH
85+
pass.setPipeline(pipeline)
86+
RenderSystem.bindDefaultUniforms(pass)
87+
transforms.forEach { (renderer, transform) ->
88+
pass.setUniform("DynamicTransforms", transform)
89+
renderer.renderFaces(pass)
90+
}
91+
}
92+
93+
// Render Edges
94+
RegionRenderer.createRenderPass("$name Edges")?.use { pass ->
95+
val pipeline =
96+
if (depthTest) LambdaRenderPipelines.ESP_LINES
97+
else LambdaRenderPipelines.ESP_LINES_THROUGH
98+
pass.setPipeline(pipeline)
99+
RenderSystem.bindDefaultUniforms(pass)
100+
transforms.forEach { (renderer, transform) ->
101+
pass.setUniform("DynamicTransforms", transform)
102+
renderer.renderEdges(pass)
103+
}
104+
}
105+
}
106+
107+
/**
108+
* Compute a unique key for a region based on its coordinates. Prevents collisions between
109+
* regions at different Y levels.
110+
*/
111+
protected fun getRegionKey(x: Double, y: Double, z: Double): Long {
112+
val rx = (RenderRegion.REGION_SIZE * floor(x / RenderRegion.REGION_SIZE)).toInt()
113+
val ry = (RenderRegion.REGION_SIZE * floor(y / RenderRegion.REGION_SIZE)).toInt()
114+
val rz = (RenderRegion.REGION_SIZE * floor(z / RenderRegion.REGION_SIZE)).toInt()
115+
116+
return getRegionKey(rx, ry, rz)
117+
}
118+
119+
protected fun getRegionKey(rx: Int, ry: Int, rz: Int): Long {
120+
// 20 bits for X, 20 bits for Z, 24 bits for Y (total 64)
121+
// This supports +- 500k blocks in X/Z and full Y range
122+
return (rx.toLong() and 0xFFFFF) or
123+
((rz.toLong() and 0xFFFFF) shl 20) or
124+
((ry.toLong() and 0xFFFFFF) shl 40)
125+
}
126+
}

0 commit comments

Comments
 (0)