diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 9681c93af..91c4dfb72 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -28,7 +28,6 @@ import com.lambda.graphics.gl.Matrices.resetMatrices import com.lambda.graphics.renderer.esp.global.DynamicESP import com.lambda.graphics.renderer.esp.global.StaticESP import com.lambda.module.modules.client.GuiSettings -import com.lambda.util.Communication.info import com.lambda.util.math.Vec2d import com.mojang.blaze3d.systems.RenderSystem.getProjectionMatrix import org.joml.Matrix4f diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt index 28f7b4872..0b48e97d4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt @@ -36,6 +36,7 @@ abstract class Buffer( * Edge case to handle vertex arrays */ val isVertexArray: Boolean = false, + val validate: Boolean = true ) { /** * Specifies how the buffers are used @@ -105,7 +106,19 @@ abstract class Buffer( /** * Index of the current buffer. */ - var index: Int = 0; private set + private var index: Int = 0; private set(value) { + if (field == value) return + field = value + id = bufferIds[value] + } + + /** + * ID of the current buffer. + */ + var id: Int = 0; get() { + if (field == 0) field = bufferIds[0] + return field + } private set /** * List of all the buffers. @@ -113,19 +126,23 @@ abstract class Buffer( private val bufferIds = IntArray(buffers) /** - * Binds the buffer id to the [target]. + * Execute the [block] in a bound context */ - open fun bind(id: Int) = glBindBuffer(target, id) + fun bind(block: Buffer.() -> Unit) { + bind() + block(this) + bind(0) + } /** - * Binds current the buffer [index] to the [target]. + * Binds the buffer id to the [target]. */ - fun bind() = bind(bufferAt(index)) + open fun bind(id: Int) = glBindBuffer(target, id) /** - * Returns the id of the buffer based on the index. + * Binds current the buffer [index] to the [target]. */ - fun bufferAt(index: Int) = bufferIds[index] + fun bind() = bind(id) /** * Swaps the buffer [index] if [buffers] is greater than 1. @@ -317,6 +334,8 @@ abstract class Buffer( abstract fun upload(data: ByteBuffer, offset: Long) private fun validate() { + if (!validate) return + check(usage in GL_STREAM_DRAW..GL_DYNAMIC_COPY) { "Usage is invalid, refer to the documentation table." } @@ -359,7 +378,7 @@ abstract class Buffer( var lastIbo = 0 var prevIbo = 0 - fun createPipelineBuffer(bufferTarget: Int) = object : Buffer(buffers = 1) { + fun createPipelineBuffer(bufferTarget: Int) = object : Buffer(buffers = 1, validate = false) { override val target: Int = bufferTarget override val usage: Int = GL_STATIC_DRAW diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt index 468225cb7..f9e8c0ee4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/frame/FrameBuffer.kt @@ -27,14 +27,14 @@ import org.lwjgl.opengl.GL30C.* import java.nio.IntBuffer open class FrameBuffer( - protected var width: Int = 1, - protected var height: Int = 1, + var width: Int = 1, + var height: Int = 1, private val depth: Boolean = false ) { - private val fbo = glGenFramebuffers() + val fbo = glGenFramebuffers() - private val colorAttachment = glGenTextures() - private val depthAttachment by lazy(::glGenTextures) + val colorAttachment = glGenTextures() + val depthAttachment by lazy(::glGenTextures) private val clearMask = if (!depth) GL_COLOR_BUFFER_BIT else GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT @@ -57,8 +57,18 @@ open class FrameBuffer( return this } - private fun update() { - if (width == lastWidth && height == lastWidth) { + fun bind() { + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + } + + fun updateScreenSized() { + width = mc.framebuffer.viewportWidth + height = mc.framebuffer.viewportHeight + update() + } + + fun update() { + if (width == lastWidth && height == lastHeight) { glClear(clearMask) return } @@ -114,5 +124,9 @@ open class FrameBuffer( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) } + + fun unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, mc.framebuffer.fbo) + } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt index 179f9477e..02a6187f5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/pixel/PixelBuffer.kt @@ -18,9 +18,7 @@ package com.lambda.graphics.buffer.pixel import com.lambda.graphics.buffer.Buffer -import com.lambda.graphics.gl.putTo import com.lambda.graphics.texture.Texture -import com.lambda.threading.runSafeGameScheduled import com.lambda.util.math.MathUtils.toInt import org.lwjgl.opengl.GL45C.* import java.nio.ByteBuffer diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt index f494bdab6..a931f50fb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexArray.kt @@ -20,33 +20,55 @@ package com.lambda.graphics.buffer.vertex import com.lambda.graphics.buffer.Buffer import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.pipeline.PersistentBuffer import net.minecraft.client.render.BufferRenderer import org.lwjgl.opengl.GL30C.* import org.lwjgl.opengl.GL32C.glDrawElementsBaseVertex import java.nio.ByteBuffer class VertexArray( - private val vertexMode: VertexMode, - private val attributes: VertexAttrib.Group + val vertexMode: VertexMode, + val attributes: VertexAttrib.Group ) : Buffer(isVertexArray = true) { override val usage: Int = -1 override val target: Int = -1 override val access: Int = -1 - fun render( + private var linkedVBO: PersistentBuffer? = null + + fun renderIndices( + ibo: PersistentBuffer + ) = linkedVBO?.let { vbo -> + renderInternal( + indicesSize = ibo.byteBuffer.bytesPut - ibo.uploadOffset, + indicesPointer = ibo.byteBuffer.pointer + ibo.uploadOffset, + verticesOffset = vbo.uploadOffset + ) + } ?: throw IllegalStateException("Unable to use vertex array without having a VBO linked to it.") + + private fun renderInternal( indicesSize: Long, indicesPointer: Long, - verticesOffset: Int - ) { - bind() - glDrawElementsBaseVertex( - vertexMode.mode, - indicesSize.toInt() / UInt.SIZE_BYTES, - GL_UNSIGNED_INT, - indicesPointer, - verticesOffset / attributes.stride - ) - bind(0) + verticesOffset: Long + ) = bind { + glDrawElementsBaseVertex( + vertexMode.mode, + indicesSize.toInt() / Int.SIZE_BYTES, + GL_UNSIGNED_INT, + indicesPointer, + verticesOffset.toInt() / attributes.stride, + ) + } + + fun linkVbo(vbo: PersistentBuffer, block: VertexArray.() -> Unit = { }) { + linkedVBO = vbo + + bind { + vbo.use { + attributes.link() + block(this@VertexArray) + } + } } override fun map(size: Long, offset: Long, block: (ByteBuffer) -> Unit) = throw UnsupportedOperationException() diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt index bac651b5f..90c95d55e 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexAttrib.kt @@ -21,36 +21,34 @@ import org.lwjgl.opengl.GL11C.GL_FLOAT import org.lwjgl.opengl.GL11C.GL_UNSIGNED_BYTE import org.lwjgl.opengl.GL20C.glEnableVertexAttribArray import org.lwjgl.opengl.GL20C.glVertexAttribPointer -import org.lwjgl.opengl.GL33.glVertexAttribDivisor sealed class VertexAttrib( private val componentCount: Int, componentSize: Int, private val normalized: Boolean, - private val single: Boolean, private val type: Int ) { open class Float( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(1, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(1, 4, normalized, GL_FLOAT) { companion object : Float() } open class Vec2( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(2, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(2, 4, normalized, GL_FLOAT) { companion object : Vec2() } open class Vec3( - normalized: Boolean = false, single: Boolean = false - ) : VertexAttrib(3, 4, normalized, single, GL_FLOAT) { + normalized: Boolean = false + ) : VertexAttrib(3, 4, normalized, GL_FLOAT) { companion object : Vec3() } open class Color( - normalized: Boolean = true, single: Boolean = false - ) : VertexAttrib(4, 1, normalized, single, GL_UNSIGNED_BYTE) { + normalized: Boolean = true + ) : VertexAttrib(4, 1, normalized, GL_UNSIGNED_BYTE) { companion object : Color() } @@ -59,7 +57,6 @@ sealed class VertexAttrib( fun link(index: Int, pointer: Long, stride: Int) { glEnableVertexAttribArray(index) glVertexAttribPointer(index, componentCount, type, normalized, stride, pointer) - if (single) glVertexAttribDivisor(index, 1) } @Suppress("ClassName") @@ -70,15 +67,7 @@ sealed class VertexAttrib( // GUI object FONT : Group( - Vec3, Vec2, Color - ) - - object RECT : Group( - Vec3, Vec2, Color - ) - - object RECT_OUTLINE : Group( - Vec3, Vec2, Float, Color + Vec2, Vec2, Color ) // WORLD @@ -94,6 +83,14 @@ sealed class VertexAttrib( Vec3, Vec2, Color ) + object LINE : Group( + Vec2, Float, Color + ) + + object MULTILINE : Group( + Vec2, Float, Float, Float, Color + ) + val stride = attributes.sumOf { attribute -> attribute.size } diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt index ddcefd46e..df44bceeb 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt @@ -88,7 +88,7 @@ object GlStateUtils { field.set(flag) } - private fun blend(flag: Boolean) { + fun blend(flag: Boolean) { if (flag) { glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt index ee8d3fbfc..351e6ee0a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt @@ -18,35 +18,36 @@ package com.lambda.graphics.pipeline import com.lambda.graphics.buffer.Buffer.Companion.createPipelineBuffer +import com.lambda.graphics.buffer.DynamicByteBuffer import com.lambda.graphics.buffer.DynamicByteBuffer.Companion.dynamicByteBuffer import com.lambda.graphics.gl.kibibyte +import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil.memCopy -import java.nio.ByteBuffer /** * Represents a persistent dynamic coherent buffer for fast opengl rendering purposes */ class PersistentBuffer( - target: Int, stride: Int + target: Int, stride: Int, initialSize: Int = 1.kibibyte ) { /** * Resizable byte buffer that stores all data used last frame */ - val byteBuffer = dynamicByteBuffer(stride * 1.kibibyte) + val byteBuffer = dynamicByteBuffer(stride * initialSize) /** * Data that has passed through the buffer within previous frame */ private val snapshot = dynamicByteBuffer(1) - private var cacheSize = 0 + private var snapshotData = 0L /** * Represents a gpu-side buffer */ - private val glBuffer = createPipelineBuffer(target) + val glBuffer = createPipelineBuffer(target) private var glSize = 0 - var uploadOffset = 0 + var uploadOffset = 0L fun upload() { val dataStart = byteBuffer.pointer + uploadOffset @@ -58,31 +59,24 @@ class PersistentBuffer( glBuffer.allocate(byteBuffer.data) snapshot.realloc(byteBuffer.capacity) - cacheSize = 0 + snapshotData = 0 return + } - /* TODO: - Cache data in range min(snapshot.capacity, byteBuffer.bytesPut) - and force upload after byteBuffer.bytesPut - */ - } else if (cacheSize > 0 && snapshot.capacity >= byteBuffer.bytesPut) { - // TODO: precise compare-mapping to minimize uploaded data - // Split data by chunks of modified regions and upload them only - // Might be useful in cases when position updates but uv/color/etc doesn't - // Might be not... - if (memcmp(snapshot.data, byteBuffer.data, uploadOffset, dataCount.toInt())) return + if (snapshotData > 0 && snapshot.capacity >= byteBuffer.bytesPut) { + if (memcmp(snapshot, byteBuffer, uploadOffset, dataCount)) return } - glBuffer.update(uploadOffset.toLong(), dataCount, dataStart) + glBuffer.update(uploadOffset, dataCount, dataStart) } fun end() { - uploadOffset = byteBuffer.bytesPut.toInt() + uploadOffset = byteBuffer.bytesPut } fun sync() { memCopy(byteBuffer.pointer, snapshot.pointer, byteBuffer.bytesPut) - cacheSize = byteBuffer.bytesPut.toInt() + snapshotData = byteBuffer.bytesPut byteBuffer.resetPosition() uploadOffset = 0 @@ -92,21 +86,34 @@ class PersistentBuffer( snapshot.resetPosition() byteBuffer.resetPosition() uploadOffset = 0 - cacheSize = 0 + snapshotData = 0 } - fun use(block: () -> Unit) { - glBuffer.bind() - block() - glBuffer.bind(0) - } + fun use(block: () -> Unit) = glBuffer.bind { block() } + + private fun memcmp(a: DynamicByteBuffer, b: DynamicByteBuffer, position: Long, size: Long): Boolean { + if (a.capacity != b.capacity) return false + + val end = position + size + var head = position + + // Process the aligned bytes in chunks of 8 until we've reached the end + while (head + 8 <= end) { + val first = MemoryUtil.memGetLong(a.pointer + head) + val second = MemoryUtil.memGetLong(b.pointer + head) + if (first != second) return false - private fun memcmp(a: ByteBuffer, b: ByteBuffer, pointer: Int, size: Int): Boolean { - for (i in pointer..<(pointer + size)) { - if (a[i] != b[i]) { - return false - } + head += 8 } + + while (head < end) { + val first = MemoryUtil.memGetByte(a.pointer + head) + val second = MemoryUtil.memGetByte(b.pointer + head) + if (first != second) return false + + head++ + } + return true } } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt index aec8b8c12..676e27fff 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt @@ -29,8 +29,8 @@ import org.joml.Vector4d class VertexBuilder( private val direct: VertexPipeline? = null ) { - private val vertices by lazy { mutableListOf() } - private val indices by lazy { mutableListOf() } + val vertices by lazy { mutableListOf() } + val indices by lazy { mutableListOf() } private var verticesCounter = 0 @@ -90,6 +90,24 @@ class VertexBuilder( this.indices += index2 } + /** + * Adds triangle indices using 3 vertices + */ + fun buildTriangle( + index1: Int, index2: Int, index3: Int + ) { + direct?.let { + it.indices.putInt(index1) + it.indices.putInt(index2) + it.indices.putInt(index3) + return + } + + this.indices += index1 + this.indices += index2 + this.indices += index3 + } + /** * Creates a collection of indices from varargs * @return List of provided indices for element array buffer @@ -97,9 +115,8 @@ class VertexBuilder( fun collect(vararg indices: Int) = indices - fun use(block: VertexBuilder.() -> Unit) { + fun use(block: VertexBuilder.() -> Unit) = apply(block) - } /** * Creates a new vertex with specified attributes @@ -120,14 +137,19 @@ class VertexBuilder( "Builder is already associated with a rendering pipeline. Cannot upload data again." } - /* Upload vertices */ + uploadVertices(pipeline.vertices) + uploadIndices(pipeline.indices) + } + + fun uploadVertices(buffer: DynamicByteBuffer) { vertices.forEach { attribute -> - attribute.upload(pipeline.vertices) + attribute.upload(buffer) } + } - /* Upload indices */ + fun uploadIndices(buffer: DynamicByteBuffer) { indices.forEach { - pipeline.indices.putInt(it) + buffer.putInt(it) } } @@ -264,4 +286,4 @@ class VertexBuilder( var color: java.awt.Color ) : Attribute({ putColor(color) }) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt index 31c1c3bf5..bccfd7fc4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -19,6 +19,7 @@ package com.lambda.graphics.pipeline import com.lambda.graphics.buffer.vertex.VertexArray import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib.Vec2 import com.lambda.graphics.buffer.vertex.attributes.VertexMode import org.lwjgl.opengl.GL32C.* @@ -38,8 +39,13 @@ class VertexPipeline( private val attributes: VertexAttrib.Group ) { private val vao = VertexArray(vertexMode, attributes) + private val vbo = PersistentBuffer(GL_ARRAY_BUFFER, attributes.stride) - private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, 4) + private val ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, Int.SIZE_BYTES) + + init { + vao.linkVbo(vbo) + } /** * Direct access to the vertex buffer's underlying byte storage @@ -55,11 +61,7 @@ class VertexPipeline( * Submits a draw call to the GPU using currently uploaded data * Binds VAO and issues glDrawElementsBaseVertex command */ - fun render() = vao.render( - indicesSize = indices.bytesPut - ibo.uploadOffset, - indicesPointer = indices.pointer + ibo.uploadOffset, - verticesOffset = vbo.uploadOffset - ) + fun render() = vao.renderIndices(ibo) /** * Builds and renders data constructed by [VertexBuilder] @@ -144,9 +146,18 @@ class VertexPipeline( ibo.clear() } - init { - vao.bind() - vbo.use(attributes::link) - vao.bind(0) + companion object { + private val UI_RECT = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group(Vec2)).apply { + upload { + buildQuad( + vertex { vec2(0.0, 0.0) }, + vertex { vec2(0.0, 1.0) }, + vertex { vec2(1.0, 1.0) }, + vertex { vec2(1.0, 0.0) } + ) + } + } + + fun renderStaticRect() = UI_RECT.render() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt index 540420854..d327c529a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt @@ -48,6 +48,8 @@ class DynamicAABB { return null } + fun center() = curr?.center ?: prev?.center ?: throw NullPointerException("No box is set") + companion object { val Entity.dynamicBox get() = DynamicAABB().apply { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt new file mode 100644 index 000000000..05ed8aac1 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPLineBuilders.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.renderer.esp.builders + +import com.lambda.Lambda.mc +import com.lambda.graphics.RenderMain +import com.lambda.graphics.renderer.esp.DynamicAABB +import com.lambda.graphics.renderer.esp.impl.DynamicESPRenderer +import com.lambda.graphics.renderer.gui.LineRenderer +import com.lambda.util.math.minus +import net.minecraft.util.math.Vec3d +import java.awt.Color +import org.joml.Vector2d +import org.joml.Vector4f + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param points List of 3D points to connect with lines + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLine( + points: List, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + if (points.size < 2) return + + // Project 3D points to 2D screen coordinates + val screenPoints = points.mapNotNull { point -> + val screenPos = project3DTo2D(point) ?: return@mapNotNull null + LineRenderer.Point(Vector2d(screenPos.x, screenPos.y), color) + } + + if (screenPoints.size < 2) return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + screenPoints.forEach { point -> + point(point.pos, point.color) + } + } + } +} + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param start Starting point of the line + * @param end Ending point of the line + * @param startColor Color at the start of the line + * @param endColor Color at the end of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLine( + start: Vec3d, + end: Vec3d, + startColor: Color, + endColor: Color = startColor, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + // Project 3D points to 2D screen coordinates + val startScreen = project3DTo2D(start) ?: return + val endScreen = project3DTo2D(end) ?: return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + point(Vector2d(startScreen.x, startScreen.y), startColor) + point(Vector2d(endScreen.x, endScreen.y), endColor) + } + } +} + +/** + * Draws a line between two dynamic boxes in 3D space using the LineRenderer. + * + * @param box1 First dynamic box + * @param box2 Second dynamic box + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun DynamicESPRenderer.drawLineBetweenBoxes( + box1: DynamicAABB, + box2: DynamicAABB, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + drawLine(box1.center(), box2.center(), color, color, width, dashiness, dashPeriod) +} + +/** + * Projects a 3D point to 2D screen coordinates. + * Returns null if the point is behind the camera or outside the screen. + */ +private fun project3DTo2D(point: Vec3d): Vector2d? { + val transformedPoint = point - mc.gameRenderer.camera.pos + + // Create a 4D vector from the 3D point + val vec4 = Vector4f( + transformedPoint.x.toFloat(), + transformedPoint.y.toFloat(), + transformedPoint.z.toFloat(), + 1f + ) + + // Transform the point using the projection-model matrix + RenderMain.projModel.transform(vec4) + + // Check if the point is behind the camera + if (vec4.w <= 0f) return null + + // Perform perspective division + vec4.div(vec4.w) + + // Convert from normalized device coordinates (-1 to 1) to screen coordinates (0 to screen width/height) + val screenX = (vec4.x * 0.5f + 0.5f) * RenderMain.screenSize.x.toFloat() + val screenY = (1f - (vec4.y * 0.5f + 0.5f)) * RenderMain.screenSize.y.toFloat() + + // Check if the point is outside the screen + if (screenX < 0 || screenX > RenderMain.screenSize.x.toFloat() || screenY < 0 || screenY > RenderMain.screenSize.y.toFloat()) { + return null + } + + return Vector2d(screenX.toDouble(), screenY.toDouble()) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt new file mode 100644 index 000000000..7ec06c868 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPLineBuilders.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.renderer.esp.builders + +import com.lambda.graphics.RenderMain +import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer +import com.lambda.graphics.renderer.gui.LineRenderer +import net.minecraft.util.math.Vec3d +import java.awt.Color +import org.joml.Vector2d +import org.joml.Vector4f + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param points List of 3D points to connect with lines + * @param color Color of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun StaticESPRenderer.drawLine( + points: List, + color: Color, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + if (points.size < 2) return + + // Project 3D points to 2D screen coordinates + val screenPoints = points.mapNotNull { point -> + val screenPos = project3DTo2D(point) ?: return@mapNotNull null + LineRenderer.Point(Vector2d(screenPos.x, screenPos.y), color) + } + + if (screenPoints.size < 2) return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + screenPoints.forEach { point -> + point(point.pos, point.color) + } + } + } +} + +/** + * Draws a line in 3D space using the LineRenderer. + * + * @param start Starting point of the line + * @param end Ending point of the line + * @param startColor Color at the start of the line + * @param endColor Color at the end of the line + * @param width Width of the line + * @param dashiness Dashiness of the line (1.0 = solid, 0.0 = fully dashed) + * @param dashPeriod Period of the dashes + */ +fun StaticESPRenderer.drawLine( + start: Vec3d, + end: Vec3d, + startColor: Color, + endColor: Color = startColor, + width: Double = 1.0, + dashiness: Double = 1.0, + dashPeriod: Double = 1.0 +) { + // Project 3D points to 2D screen coordinates + val startScreen = project3DTo2D(start) ?: return + val endScreen = project3DTo2D(end) ?: return + + // Draw the line using LineRenderer + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(width) { + point(Vector2d(startScreen.x, startScreen.y), startColor) + point(Vector2d(endScreen.x, endScreen.y), endColor) + } + } +} + +/** + * Projects a 3D point to 2D screen coordinates. + * Returns null if the point is behind the camera or outside the screen. + */ +private fun project3DTo2D(point: Vec3d): Vector2d? { + // Create a 4D vector from the 3D point + val vec4 = Vector4f( + point.x.toFloat(), + point.y.toFloat(), + point.z.toFloat(), + 1f + ) + + // Transform the point using the projection-model matrix + RenderMain.projModel.transform(vec4) + + // Check if the point is behind the camera + if (vec4.w <= 0f) return null + + // Perform perspective division + vec4.div(vec4.w) + + // Convert from normalized device coordinates (-1 to 1) to screen coordinates (0 to screen width/height) + val screenX = (vec4.x * 0.5f + 0.5f) * RenderMain.screenSize.x.toFloat() + val screenY = (1f - (vec4.y * 0.5f + 0.5f)) * RenderMain.screenSize.y.toFloat() + + // Check if the point is outside the screen + if (screenX < 0 || screenX > RenderMain.screenSize.x.toFloat() || screenY < 0 || screenY > RenderMain.screenSize.y.toFloat()) { + return null + } + + return Vector2d(screenX.toDouble(), screenY.toDouble()) +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt deleted file mode 100644 index 755031514..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.RenderMain -import com.lambda.graphics.pipeline.VertexPipeline -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.shader.Shader -import com.lambda.module.modules.client.GuiSettings -import com.lambda.module.modules.client.GuiSettings.primaryColor -import com.lambda.module.modules.client.GuiSettings.secondaryColor -import com.lambda.module.modules.client.RenderSettings -import com.lambda.util.math.MathUtils.toInt -import com.lambda.util.math.Vec2d -import org.lwjgl.glfw.GLFW - -open class AbstractGUIRenderer( - attribGroup: VertexAttrib.Group, - val shader: Shader -) { - private val pipeline = VertexPipeline(VertexMode.TRIANGLES, attribGroup) - private var memoryMapping = true - - init { - listen(alwaysListen = true) { - memoryMapping = RenderSettings.useMemoryMapping - } - - listen(alwaysListen = true) { - if (memoryMapping) pipeline.sync() - } - } - - fun render( - shade: Boolean = false, - block: VertexPipeline.(Shader) -> Unit - ) { - shader.use() - - block(pipeline, shader) - - shader["u_Shade"] = shade.toInt().toDouble() - if (shade) { - shader["u_ShadeTime"] = GLFW.glfwGetTime() * GuiSettings.colorSpeed * 5.0 - shader["u_ShadeColor1"] = primaryColor - shader["u_ShadeColor2"] = secondaryColor - - shader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) - } - - pipeline.apply { - render() - if (memoryMapping) end() else clear() - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt new file mode 100644 index 000000000..fd831a9ca --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/BlurRenderer.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.renderer.gui + +import com.lambda.Lambda.mc +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.buffer.frame.FrameBuffer +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.GlStateUtils +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader.Companion.shader +import com.lambda.graphics.texture.TextureUtils.bindTexture +import com.lambda.util.math.Rect +import com.lambda.util.math.Vec2d +import org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER +import org.lwjgl.opengl.GL30C.glBindFramebuffer + +// ToDo: Improve blur +// Round Corners +// Boost brightness +// BlurRect layout impl +object BlurRenderer { + private val fbo1 = FrameBuffer() + private val fbo2 = FrameBuffer() + private val base get() = mc.framebuffer + + private val hShader = shader("post/gaussian_h") + private val vShader = shader("post/gaussian_v") + + fun blur(rect: Rect, iterations: Int) { + if (iterations <= 0) return + + val i = iterations.toDouble() + val texelSize = Vec2d(1.0 / base.viewportWidth, 1.0 / base.viewportHeight) + val (pos1, pos2) = rect.leftTop to rect.rightBottom + + hShader.use() + hShader["u_TexelSize"] = texelSize + hShader["u_Position1"] = pos1 + hShader["u_Position2"] = pos2 + + vShader.use() + vShader["u_TexelSize"] = texelSize + vShader["u_Position1"] = pos1 + vShader["u_Position2"] = pos2 + + GlStateUtils.blend(false) + render(null, fbo1, false, i - 1.0, i) + + repeat(iterations - 1) { + render(fbo1, fbo2, true , i - it, i - it) + render(fbo2, fbo1, false, i-it-1, i - it) + } + + render(fbo1, null, true) + GlStateUtils.blend(true) + } + + private fun render( + color: FrameBuffer?, + frameBuffer: FrameBuffer?, + vertical: Boolean, + extendX: Double = 0.0, + extendY: Double = 0.0 + ) { + color?.bindColorTexture() ?: bindTexture(base.colorAttachment) + + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer?.fbo ?: base.fbo) + frameBuffer?.updateScreenSized() + + (if (vertical) vShader else hShader).let { shader -> + shader.use() + shader["u_Extend"] = Vec2d(extendX, extendY) + // if (vertical) shader["u_IsFinal"] = frameBuffer == null + } + + VertexPipeline.renderStaticRect() + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt index 53ee181d5..7a63cc715 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt @@ -17,33 +17,49 @@ package com.lambda.graphics.renderer.gui +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.buffer.vertex.VertexArray import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.pipeline.VertexBuilder -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.gl.Matrices +import com.lambda.graphics.renderer.gui.font.CachedString +import com.lambda.graphics.renderer.gui.font.CachedString.Companion.SCALE_FACTOR +import com.lambda.graphics.renderer.gui.font.CachedString.Companion.getStringWidth import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height +import com.lambda.graphics.shader.Shader.Companion.shadeUniforms import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.graphics.texture.TextureOwner.bind import com.lambda.module.modules.client.ClickGui -import com.lambda.module.modules.client.LambdaMoji import com.lambda.module.modules.client.RenderSettings -import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d -import com.lambda.util.math.a -import com.lambda.util.math.setAlpha +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import org.lwjgl.opengl.GL15.glDeleteBuffers +import org.lwjgl.opengl.GL30 import java.awt.Color +import java.util.function.Function /** * Renders text and emoji glyphs using a shader-based font rendering system. * This class handles text and emoji rendering, shadow effects, and text scaling. */ -object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("renderer/font")) { - private val chars get() = RenderSettings.textFont - private val emojis get() = RenderSettings.emojiFont +object FontRenderer { + val vao by lazy { VertexArray(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) } + private val stringCache = Object2ObjectOpenHashMap, CachedString>() + private val shader by lazy { shader("renderer/font") } - private val shadowShift get() = RenderSettings.shadowShift * 10.0 - private val baselineOffset get() = RenderSettings.baselineOffset * 2.0f - 16f - private val gap get() = RenderSettings.gap * 0.5f - 0.8f + val chars get() = RenderSettings.textFont + val emojis get() = RenderSettings.emojiFont + + init { + listen(alwaysListen = true) { + invalidate() + } + + listen(alwaysListen = true) { + invalidate() + } + } /** * Renders a text string at a specified position with configurable color, scale, shadow, and emoji parsing @@ -53,7 +69,6 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * @param color The color of the text. * @param scale The scale factor of the text. * @param shadow Whether to render a shadow for the text. - * @param parseEmoji Whether to parse and render emojis in the text. */ fun drawString( text: String, @@ -61,90 +76,27 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende color: Color = Color.WHITE, scale: Double = ClickGui.fontScale, shadow: Boolean = true, - parseEmoji: Boolean = LambdaMoji.isEnabled - ) = render { - shader["u_FontTexture"] = 0 - shader["u_EmojiTexture"] = 1 - shader["u_SDFMin"] = RenderSettings.sdfMin - shader["u_SDFMax"] = RenderSettings.sdfMax - - bind(chars, emojis) - - upload { - processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ -> - buildGlyph(char, position, pos1, pos2, col) - } - } - } + shade: Boolean = false + ) = Matrices.push { + translate(position.x, position.y) + scale(scale, scale) - /** - * Renders a single glyph at the specified position with the given scale and color - * - * @param glyph The glyph information - * @param position The rendering position where the glyph will be drawn - * @param color The color of the glyph - * @param scale The scale factor of the glyph - */ - fun drawGlyph( - glyph: GlyphInfo, - position: Vec2d, - color: Color = Color.WHITE, - scale: Double = ClickGui.fontScale, - ) = render { + shader.use() + shader.shadeUniforms(shade) shader["u_FontTexture"] = 0 shader["u_EmojiTexture"] = 1 shader["u_SDFMin"] = RenderSettings.sdfMin shader["u_SDFMax"] = RenderSettings.sdfMax + shader["u_Color"] = color bind(chars, emojis) - val actualScale = getScaleFactor(scale) - val scaledSize = glyph.size * actualScale - - val posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - val pos1 = Vec2d(0.0, posY) * actualScale - val pos2 = pos1 + scaledSize + val string = stringCache.computeIfAbsent(text to shadow, Function, CachedString> { + CachedString(it.first, it.second) + }) - upload { - buildGlyph(glyph, position, pos1, pos2, color) - } - } - - /** - * Renders a single glyph at a given position. - * - * @param glyph The glyph information to render. - * @param origin The position to start from - * @param pos1 The starting position of the glyph. - * @param pos2 The end position of the glyph - * @param color The color of the glyph. - */ - private fun VertexBuilder.buildGlyph( - glyph: GlyphInfo, - origin: Vec2d = Vec2d.ZERO, - pos1: Vec2d, - pos2: Vec2d, - color: Color, - ) { - val x1 = pos1.x + origin.x - val y1 = pos1.y + origin.y - val x2 = pos2.x + origin.x - val y2 = pos2.y + origin.y - - buildQuad( - vertex { - vec3m(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(color) - }, - vertex { - vec3m(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(color) - }, - vertex { - vec3m(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(color) - }, - vertex { - vec3m(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(color) - } - ) + vao.linkVbo(string.vbo) + vao.renderIndices(string.ibo) } /** @@ -152,23 +104,12 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * * @param text The text to measure. * @param scale The scale factor for the width calculation. - * @param parseEmoji Whether to include emojis in the width calculation. * @return The width of the text at the specified scale. */ - fun getWidth( - text: String, - scale: Double = ClickGui.fontScale, - parseEmoji: Boolean = LambdaMoji.isEnabled, - ): Double { - var width = 0.0 - var gaps = -1 - - processText(text, scale = scale, parseEmoji = parseEmoji) { char, _, _, _, isShadow -> - if (isShadow) return@processText - width += char.width; gaps++ - } - - return (width + gaps.coerceAtLeast(0) * gap) * getScaleFactor(scale) + fun getWidth(text: String, scale: Double = 1.0): Double { + // Todo: separate width cache? + val cache = stringCache[text to true] ?: stringCache[text to false] + return (cache?.cachedWidth ?: getStringWidth(text)) * scale } /** @@ -180,110 +121,17 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("rende * @param scale The scale factor for the height calculation. * @return The height of the text at the specified scale. */ - fun getHeight(scale: Double = 1.0) = chars.height * getScaleFactor(scale) * 0.7 - - /** - * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph - * - * @param text The text to iterate over. - * @param color The color of the text. - * @param scale The scale of the text. - * @param shadow Whether to render a shadow. - * @param parseEmoji Whether to parse and include emojis. - * @param block The function to apply to each character or emoji glyph. - */ - private fun processText( - text: String, - color: Color = Color.WHITE, - scale: Double = 1.0, - shadow: Boolean = RenderSettings.shadow, - parseEmoji: Boolean = LambdaMoji.isEnabled, - block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit - ) { - val actualScale = getScaleFactor(scale) - val scaledGap = gap * actualScale - - val shadowColor = getShadowColor(color) - val emojiColor = color.setAlpha(color.a) - - var posX = 0.0 - var posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - - fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) { - if (info == null) return - - val scaledSize = info.size * actualScale - val pos1 = Vec2d(posX, posY) + shadowShift * actualScale * isShadow.toInt() - val pos2 = pos1 + scaledSize - - block(info, pos1, pos2, color, isShadow) - if (!isShadow) posX += scaledSize.x + scaledGap + fun getHeight(scale: Double = 1.0) = + chars.height * 0.7 * SCALE_FACTOR * scale + + fun invalidate() { + stringCache.values.forEach { + glDeleteBuffers(intArrayOf( + it.vbo.glBuffer.id, + it.ibo.glBuffer.id + )) } - val parsed = if (parseEmoji) emojis.parse(text) else mutableListOf() - - fun processTextSection(section: String, hasEmojis: Boolean) { - if (section.isEmpty()) return - if (!parseEmoji || parsed.isEmpty() || !hasEmojis) { - // Draw simple characters if no emojis are present - section.forEach { char -> - // Logic for control characters - when (char) { - '\n', '\r' -> { posX = 0.0; posY += chars.height * actualScale; return@forEach } - } - - val glyph = chars[char] ?: return@forEach - - if (shadow && RenderSettings.shadow) drawGlyph(glyph, shadowColor, true) - drawGlyph(glyph, color) - } - } else { - // Only compute the first parsed emoji to avoid duplication - // This is important in order to keep the parsed ranges valid - // If you do not this, you will get out of bounds positions - // due to slicing - val emoji = parsed.removeFirstOrNull() ?: return - - // Iterate the emojis from left to right - val start = section.indexOf(emoji) - val end = start + emoji.length - - val preEmojiText = section.substring(0, start) - val postEmojiText = section.substring(end) - - // Draw the text without emoji - processTextSection(preEmojiText, hasEmojis = false) - - // Draw the emoji - drawGlyph(emojis[emoji], emojiColor) - - // Process the rest of the text after the emoji - processTextSection(postEmojiText, hasEmojis = true) - } - } - - // Start processing the full text - processTextSection(text, hasEmojis = parsed.isNotEmpty()) + stringCache.clear() } - - /** - * Calculates the scale factor for the text based on the provided scale. - * - * @param scale The base scale factor. - * @return The adjusted scale factor. - */ - fun getScaleFactor(scale: Double): Double = scale * 8.5 / chars.height - - /** - * Calculates the shadow color by adjusting the brightness of the input color. - * - * @param color The original color. - * @return The modified shadow color. - */ - fun getShadowColor(color: Color): Color = Color( - (color.red * RenderSettings.shadowBrightness).toInt(), - (color.green * RenderSettings.shadowBrightness).toInt(), - (color.blue * RenderSettings.shadowBrightness).toInt(), - color.alpha - ) } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt new file mode 100644 index 000000000..59c06bd74 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/LineRenderer.kt @@ -0,0 +1,240 @@ +package com.lambda.graphics.renderer.gui + +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import com.lambda.graphics.pipeline.VertexBuilder +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader.Companion.shader +import org.joml.Vector2d +import java.awt.Color +import kotlin.math.* + +object LineRenderer { + private val pipelineSingle = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.LINE) + private val shaderSingle = shader("renderer/line") + + private val pipelineMulti = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.MULTILINE) + private val shaderMulti = shader("renderer/multiline") + + fun lines( + dashiness: Double = 1.0, + dashPeriod: Double = 1.0, + batching: Boolean = false, + block: MultiLineBuilder.() -> Unit + ): VertexPipeline { + val lineBuilder = MultiLineBuilder().apply(block) + + if (batching) { + shaderSingle.use() + shaderSingle["u_Dashiness"] = dashiness + shaderSingle["u_DashPeriod"] = dashPeriod * 100.0 + + pipelineSingle.upload { + lineBuilder.lines.forEach { data -> + shaderSingle["u_Width"] = data.width + putLines(this, data, false) + } + } + + return pipelineSingle + } + + shaderMulti.use() + shaderMulti["u_Dashiness"] = dashiness + shaderMulti["u_DashPeriod"] = dashPeriod * 100.0 + + lineBuilder.lines.forEach { data -> + shaderMulti["u_Length"] = -1.0 + + pipelineMulti.upload { + putLines(this, data, true) + } + } + + return pipelineMulti + } + + private fun putLines( + builder: VertexBuilder, + data: MultiLineBuilder.LineData, + putDistance: Boolean + ) { + val points = data.points + val width = data.width + + var dist = 0.0 + val lines = Array(points.size - 1) { i -> + val p1 = points[i] + val p2 = points[i + 1] + + var normalX = p1.pos.y - p2.pos.y + var normalY = p2.pos.x - p1.pos.x + + val length = sqrt(normalX * normalX + normalY * normalY) + normalX /= length + normalY /= length + + val normal = Vector2d(normalX, normalY) + val halfWidth = width / 2.0 + val p11 = Vector2d(p1.pos.x + normal.x * halfWidth, p1.pos.y + normal.y * halfWidth) + val p12 = Vector2d(p1.pos.x - normal.x * halfWidth, p1.pos.y - normal.y * halfWidth) + val p21 = Vector2d(p2.pos.x + normal.x * halfWidth, p2.pos.y + normal.y * halfWidth) + val p22 = Vector2d(p2.pos.x - normal.x * halfWidth, p2.pos.y - normal.y * halfWidth) + + dist += length + Line(p1, p2, length, dist, p11, p12, p21, p22) + } + + val joints = mutableListOf Int, () -> Int, () -> Int>>() + repeat(lines.size - 1) { i -> + val current = lines[i] + val next = lines[i + 1] + + findIntersection( + current.p11, current.p21, + next.p21, next.p11 + )?.also { + current.p21 = it + next.p11 = it + next.connection = false + + joints.add(Triple(current::p22i, current::p21i, next::p12i)) + } ?: findIntersection( + current.p12, current.p22, + next.p22, next.p12 + )?.also { + current.p22 = it + next.p12 = it + next.connection = true + + joints.add(Triple(current::p21i, next::p11i, current::p22i)) + } + } + + // Render lines + builder.use { + lines.forEachIndexed { i, line -> + val prev = lines.getOrNull(i - 1) + + line.p11i = vertex { + vec2(line.p11.x, line.p11.y) + if (putDistance) float(prev?.distance ?: 0.0) + if (putDistance) float(width) + float(1.0) + color(line.point1.color) + } + + line.p12i = vertex { + vec2(line.p12.x, line.p12.y) + if (putDistance) float(prev?.distance ?: 0.0) + if (putDistance) float(width) + float(0.0) + color(line.point1.color) + } + + line.p21i = vertex { + vec2(line.p21.x, line.p21.y) + if (putDistance) float(line.distance) + if (putDistance) float(width) + float(1.0) + color(line.point2.color) + } + + line.p22i = vertex { + vec2(line.p22.x, line.p22.y) + if (putDistance) float(line.distance) + if (putDistance) float(width) + float(0.0) + color(line.point2.color) + } + + buildQuad(line.p11i, line.p21i, line.p22i, line.p12i) + } + + joints.forEach { joint -> + buildTriangle(joint.first(), joint.second(), joint.third()) + } + } + } + + private fun findIntersection( + firstP1: Vector2d, + firstP2: Vector2d, + secondP1: Vector2d, + secondP2: Vector2d, + ): Vector2d? { + val denominator = (firstP1.x - firstP2.x) * (secondP1.y - secondP2.y) - + (firstP1.y - firstP2.y) * (secondP1.x - secondP2.x) + + if (abs(denominator) < 1e-9) return null + + val det1 = firstP1.x * firstP2.y - firstP1.y * firstP2.x + val det2 = secondP1.x * secondP2.y - secondP1.y * secondP2.x + + val x = (det1 * (secondP1.x - secondP2.x) - (firstP1.x - firstP2.x) * det2) / denominator + val y = (det1 * (secondP1.y - secondP2.y) - (firstP1.y - firstP2.y) * det2) / denominator + + fun isPointOnSegment(x: Double, y: Double, p1: Vector2d, p2: Vector2d): Boolean { + val crossProduct = (p2.x - p1.x) * (y - p1.y) - (p2.y - p1.y) * (x - p1.x) + if (abs(crossProduct) > 1e-9) return false + + val dotProduct = (x - p1.x) * (p2.x - p1.x) + (y - p1.y) * (p2.y - p1.y) + if (dotProduct < 0) return false + + val squaredLength = (p2.x - p1.x).pow(2) + (p2.y - p1.y).pow(2) + if (dotProduct > squaredLength) return false + + return true + } + + if (!isPointOnSegment(x, y, firstP1, firstP2)) return null + if (!isPointOnSegment(x, y, secondP1, secondP2)) return null + return Vector2d(x, y) + } + + data class Point( + val pos: Vector2d, + val color: Color + ) + + private data class Line( + val point1: Point, + val point2: Point, + val length: Double, + var distance: Double, + var p11: Vector2d, + var p12: Vector2d, + var p21: Vector2d, + var p22: Vector2d + ) { + + var p11i = 0 + var p12i = 0 + var p21i = 0 + var p22i = 0 + var connection: Boolean? = null + } + + class MultiLineBuilder{ + val lines = mutableListOf() + + fun line(width: Double, block: LineBuilder.() -> Unit) { + lines.add(LineData(width).apply { + LineBuilder(this).apply(block) + }) + } + + class LineBuilder( + private val data: LineData + ) { + fun point(position: Vector2d, color: Color) = + data.points.add(Point(position, color)) + } + + class LineData( + val width: Double + ) { + val points = mutableListOf() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt index 1956d957d..9d41467f7 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt @@ -17,17 +17,18 @@ package com.lambda.graphics.renderer.gui -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.pipeline.VertexPipeline +import com.lambda.graphics.shader.Shader +import com.lambda.graphics.shader.Shader.Companion.shadeUniforms import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.util.math.Rect -import com.lambda.util.math.Vec2d import java.awt.Color import kotlin.math.min object RectRenderer { - private val filled = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_filled")) - private val outline = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_outline")) - private val glow = AbstractGUIRenderer(VertexAttrib.Group.RECT, shader("renderer/rect_glow")) + private val filled = shader("renderer/rect_filled") + private val outline = shader("renderer/rect_outline") + private val glow = shader("renderer/rect_glow") fun filledRect( rect: Rect, @@ -40,7 +41,8 @@ object RectRenderer { rightBottom: Color = Color.WHITE, leftBottom: Color = Color.WHITE, shade: Boolean = false, - ) = filled.putRect( + ) = putRect( + filled, rect, 0.0, shade, @@ -69,9 +71,10 @@ object RectRenderer { ) { if (width < 0.01) return - outline.putRect( + putRect( + outline, rect, - width * 0.5, + width * 0.25, shade, leftTopRadius, rightTopRadius, @@ -115,14 +118,15 @@ object RectRenderer { this.coerceAtMost(maxRadius) .coerceAtLeast(0.0) - glow.shader.use() - glow.shader["u_InnerRectWidth"] = innerSpread.coerceAtLeast(1.0) - glow.shader["u_InnerRoundLeftTop"] = leftTopInnerRadius .coerceAtLeast(leftTopOuterRadius) .clampRadius() - glow.shader["u_InnerRoundLeftBottom"] = leftBottomInnerRadius .coerceAtLeast(leftBottomOuterRadius) .clampRadius() - glow.shader["u_InnerRoundRightBottom"] = rightBottomInnerRadius.coerceAtLeast(rightBottomOuterRadius).clampRadius() - glow.shader["u_InnerRoundRightTop"] = rightTopInnerRadius .coerceAtLeast(rightTopOuterRadius) .clampRadius() + glow.use() + glow["u_InnerRectWidth"] = innerSpread.coerceAtLeast(1.0) + glow["u_InnerRoundLeftTop"] = leftTopInnerRadius .coerceAtLeast(leftTopOuterRadius) .clampRadius() + glow["u_InnerRoundLeftBottom"] = leftBottomInnerRadius .coerceAtLeast(leftBottomOuterRadius) .clampRadius() + glow["u_InnerRoundRightBottom"] = rightBottomInnerRadius.coerceAtLeast(rightBottomOuterRadius).clampRadius() + glow["u_InnerRoundRightTop"] = rightTopInnerRadius .coerceAtLeast(rightTopOuterRadius) .clampRadius() - glow.putRect( + putRect( + glow, rect, outerSpread.coerceAtLeast(1.0), shade, @@ -137,7 +141,8 @@ object RectRenderer { ) } - private fun AbstractGUIRenderer.putRect( + private fun putRect( + shader: Shader, rect: Rect, expandIn: Double, shade: Boolean, @@ -149,36 +154,29 @@ object RectRenderer { rightTop: Color, rightBottom: Color, leftBottom: Color, - ) = render(shade) { shader -> - val pos1 = rect.leftTop - val pos2 = rect.rightBottom + ) { + if (leftTop.alpha + rightTop.alpha + leftBottom.alpha + rightBottom.alpha == 0) return + + shader.use() + shader.shadeUniforms(shade) val expand = expandIn.coerceAtLeast(0.0) + 1 - val smoothing = 0.5 - val p1 = pos1 - expand - smoothing - val p2 = pos2 + expand + smoothing + val pos1 = rect.leftTop + val pos2 = rect.rightBottom val size = pos2 - pos1 val halfSize = size * 0.5 val maxRadius = min(halfSize.x, halfSize.y) - val uv1 = Vec2d( - -expand / size.x, - -expand / size.y - ) - - val uv2 = Vec2d( - 1.0 + (expand / size.x), - 1.0 + (expand / size.y) - ) - fun Double.clampRadius() = this.coerceAtMost(maxRadius) .coerceAtLeast(0.0) // Size of the rectangle + shader["u_Pos"] = pos1 shader["u_Size"] = size + shader["u_Expand"] = expand // Round radius shader["u_RoundLeftTop"] = leftTopRadius.clampRadius() @@ -186,24 +184,15 @@ object RectRenderer { shader["u_RoundRightBottom"] = rightBottomRadius.clampRadius() shader["u_RoundRightTop"] = rightTopRadius.clampRadius() + // Color + shader["u_ColorLeftTop"] = leftTop + shader["u_ColorLeftBottom"] = leftBottom + shader["u_ColorRightBottom"] = rightBottom + shader["u_ColorRightTop"] = rightTop + // For glow & outline only shader["u_RectWidth"] = expandIn - upload { - buildQuad( - vertex { - vec3m(p1.x, p1.y, 0.0).vec2(uv1.x, uv1.y).color(leftTop) - }, - vertex { - vec3m(p1.x, p2.y, 0.0).vec2(uv1.x, uv2.y).color(leftBottom) - }, - vertex { - vec3m(p2.x, p2.y, 0.0).vec2(uv2.x, uv2.y).color(rightBottom) - }, - vertex { - vec3m(p2.x, p1.y, 0.0).vec2(uv2.x, uv1.y).color(rightTop) - } - ) - } + VertexPipeline.renderStaticRect() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt new file mode 100644 index 000000000..e7c8ca914 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/CachedString.kt @@ -0,0 +1,186 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.renderer.gui.font + +import com.lambda.graphics.pipeline.PersistentBuffer +import com.lambda.graphics.pipeline.VertexBuilder +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.chars +import com.lambda.graphics.renderer.gui.FontRenderer.emojis +import com.lambda.graphics.renderer.gui.FontRenderer.getHeight +import com.lambda.graphics.renderer.gui.font.core.GlyphInfo +import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height +import com.lambda.module.modules.client.LambdaMoji +import com.lambda.module.modules.client.RenderSettings +import com.lambda.module.modules.client.RenderSettings.baselineOffset +import com.lambda.module.modules.client.RenderSettings.gap +import com.lambda.module.modules.client.RenderSettings.shadowShift +import com.lambda.util.math.MathUtils.toInt +import com.lambda.util.math.Vec2d +import org.lwjgl.opengl.GL32C.GL_ARRAY_BUFFER +import org.lwjgl.opengl.GL32C.GL_ELEMENT_ARRAY_BUFFER +import java.awt.Color + +// ToDo: use different attribute set based on the string simplicity +class CachedString( + string: String, + shadow: Boolean +) { + val vbo: PersistentBuffer + val ibo: PersistentBuffer + + val cachedWidth = getStringWidth(string) + + init { + /* Build vertices and indices */ + val builder = VertexBuilder().use { + processText(string, shadow) { glyph, pos1, pos2, c, _ -> + val x1 = pos1.x + val y1 = pos1.y + val x2 = pos2.x + val y2 = pos2.y + + buildQuad( + vertex { + vec2(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(c) + }, + vertex { + vec2(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(c) + }, + vertex { + vec2(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(c) + }, + vertex { + vec2(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(c) + } + ) + } + } + + /* Create gl buffers and upload */ + val vao = FontRenderer.vao + vbo = PersistentBuffer(GL_ARRAY_BUFFER, vao.attributes.stride, builder.vertices.size) + ibo = PersistentBuffer(GL_ELEMENT_ARRAY_BUFFER, Int.SIZE_BYTES, builder.indices.size) + vao.linkVbo(vbo) + + builder.uploadVertices(vbo.byteBuffer) + vbo.upload() + + builder.uploadIndices(ibo.byteBuffer) + ibo.upload() + } + + companion object { + val SCALE_FACTOR get() = 8.5 / chars.height + + /** + * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph + * + * @param text The text to iterate over. + * @param shadow Whether to render a shadow. + * @param block The function to apply to each character or emoji glyph. + */ + fun processText( + text: String, + shadow: Boolean, + block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit + ) { + val scaledGap = gap * SCALE_FACTOR + + var posX = 0.0 + var posY = baselineOffset * SCALE_FACTOR - getHeight() * 0.5 + + fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) { + if (info == null) return + + val scaledSize = info.size * SCALE_FACTOR + val pos1 = Vec2d(posX, posY) + shadowShift * SCALE_FACTOR * isShadow.toInt() + val pos2 = pos1 + scaledSize + + block(info, pos1, pos2, color, isShadow) + if (!isShadow) posX += scaledSize.x + scaledGap + } + + val parsed = if (LambdaMoji.isEnabled) emojis.parse(text) else mutableListOf() + + fun processTextSection(section: String, hasEmojis: Boolean) { + if (section.isEmpty()) return + if (!LambdaMoji.isEnabled || parsed.isEmpty() || !hasEmojis) { + // Draw simple characters if no emojis are present + section.forEach { char -> + // Logic for control characters + when (char) { + '\n', '\r' -> { posX = 0.0; posY += chars.height * SCALE_FACTOR; return@forEach } + } + + val glyph = chars[char] ?: return@forEach + + // ToDo: Implement colorcodes or remove color from attributes + val color = Color.WHITE + val shadowColor = Color( + (color.red * RenderSettings.shadowBrightness).toInt(), + (color.green * RenderSettings.shadowBrightness).toInt(), + (color.blue * RenderSettings.shadowBrightness).toInt(), + color.alpha + ) + + if (shadow) drawGlyph(glyph, shadowColor, true) + drawGlyph(glyph, color) + } + } else { + // Only compute the first parsed emoji to avoid duplication + // This is important in order to keep the parsed ranges valid + // If you do not this, you will get out of bounds positions + // due to slicing + val emoji = parsed.removeFirstOrNull() ?: return + + // Iterate the emojis from left to right + val start = section.indexOf(emoji) + val end = start + emoji.length + + val preEmojiText = section.substring(0, start) + val postEmojiText = section.substring(end) + + // Draw the text without emoji + processTextSection(preEmojiText, hasEmojis = false) + + // Draw the emoji + drawGlyph(emojis[emoji], Color.WHITE) + + // Process the rest of the text after the emoji + processTextSection(postEmojiText, hasEmojis = true) + } + } + + // Start processing the full text + processTextSection(text, hasEmojis = parsed.isNotEmpty()) + } + + fun getStringWidth(string: String): Double { + var width = 0.0 + var gaps = -1 + + processText(string, false) { char, _, _, _, _ -> + width += char.width; gaps++ + } + + return (width + gaps.coerceAtLeast(0) * gap) * SCALE_FACTOR + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt index 8b42dfd93..17a390f8a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt @@ -21,11 +21,17 @@ import com.lambda.graphics.RenderMain import com.lambda.graphics.shader.ShaderUtils.createShaderProgram import com.lambda.graphics.shader.ShaderUtils.loadShader import com.lambda.graphics.shader.ShaderUtils.uniformMatrix +import com.lambda.module.modules.client.GuiSettings +import com.lambda.module.modules.client.GuiSettings.primaryColor +import com.lambda.module.modules.client.GuiSettings.secondaryColor +import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.util.math.Vec3d import org.joml.Matrix4f +import org.lwjgl.glfw.GLFW import org.lwjgl.opengl.GL20C.* import java.awt.Color @@ -49,13 +55,9 @@ class Shader private constructor(name: String) { } private fun loc(name: String) = - if (uniformCache.containsKey(name)) - uniformCache.getInt(name) - else - glGetUniformLocation(id, name).let { location -> - uniformCache.put(name, location) - location - } + uniformCache.getOrPut(name) { + glGetUniformLocation(id, name) + } operator fun set(name: String, v: Boolean) = glUniform1i(loc(name), if (v) 1 else 0) @@ -85,11 +87,26 @@ class Shader private constructor(name: String) { uniformMatrix(loc(name), mat) companion object { - private val shaderCache = hashMapOf() + private val shaderCache = Object2ObjectOpenHashMap() fun shader(path: String) = shaderCache.getOrPut(path) { Shader(path) } + + fun Shader.shadeUniforms(shade: Boolean) { + val shader = this + + shader["u_Shade"] = shade.toInt().toDouble() + if (shade) { + shader["u_ShadeTime"] = GLFW.glfwGetTime() * GuiSettings.colorSpeed * 5.0 + shader["u_ShadeColor1"] = primaryColor + shader["u_ShadeColor2"] = secondaryColor + + val size = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + //if (this is FontRenderer) size *= 5.0 + shader["u_ShadeSize"] = size + } + } } } diff --git a/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt b/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt deleted file mode 100644 index 785f3e826..000000000 --- a/common/src/main/kotlin/com/lambda/gui/component/DockingRect.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2024 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.gui.component - -import com.lambda.module.modules.client.ClickGui -import com.lambda.util.math.MathUtils.roundToStep -import com.lambda.util.math.Rect -import com.lambda.util.math.Vec2d -import com.lambda.util.math.coerceIn - -abstract class DockingRect { - abstract var relativePos: Vec2d - protected abstract val width: Double - protected abstract val height: Double - protected val size get() = Vec2d(width, height) - - val rect get() = Rect.basedOn(position, size) - open val dockingBase get() = rect.center - - open val autoDocking = false - open val allowHAlign = true - open val allowVAlign = true - - open var dockingH = HAlign.LEFT; set(to) { - val from = field - field = to - - val delta = to.multiplier - from.multiplier - relativePos += Vec2d.RIGHT * delta * (size.x - screenSize.x) - } - - open var dockingV = VAlign.TOP; set(to) { - val from = field - field = to - - val delta = to.multiplier - from.multiplier - relativePos += Vec2d.BOTTOM * delta * (size.y - screenSize.y) - } - - var screenSize: Vec2d = Vec2d.ZERO - - var position: Vec2d - get() = relativeToAbs(relativePos) - .coerceIn(0.0, screenSize.x - size.x, 0.0, screenSize.y - size.y) - set(value) { - relativePos = absToRelative(value.roundToStep(ClickGui.dockingGridSize)) - if (autoDocking) autoDocking() - } - - private val dockingOffset get() = - (screenSize - size) * Vec2d(dockingH.multiplier, dockingV.multiplier) - - private fun relativeToAbs(posIn: Vec2d) = posIn + dockingOffset - private fun absToRelative(posIn: Vec2d) = posIn - dockingOffset - - fun autoDocking() { - val screenCenterX = (screenSize.x * 0.3333)..(screenSize.x * 0.6666) - val screenCenterY = (screenSize.y * 0.3333)..(screenSize.y * 0.6666) - - val drawableCenter = dockingBase - - dockingH = if (allowHAlign) { - when { - drawableCenter.x < screenCenterX.start -> HAlign.LEFT - drawableCenter.x > screenCenterX.endInclusive -> HAlign.RIGHT - else -> HAlign.CENTER - } - } else HAlign.LEFT - - dockingV = if (allowVAlign) { - when { - drawableCenter.y < screenCenterY.start -> VAlign.TOP - drawableCenter.y > screenCenterY.endInclusive -> VAlign.BOTTOM - else -> VAlign.CENTER - } - } else VAlign.TOP - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt b/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt new file mode 100644 index 000000000..7f7563a4f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/Line.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.gui.component.core + +import com.lambda.graphics.renderer.gui.LineRenderer +import com.lambda.gui.component.layout.Layout +import org.joml.Vector2d +import java.awt.Color + +class Line( + owner: Layout +) : LineLayout(owner) { + init { + onRender { + if (points.size >= 2) { + LineRenderer.lines( + dashiness = dashiness, + dashPeriod = dashPeriod, + batching = false + ) { + line(lineWidth) { + points.forEach { point -> + point(point.position, point.color) + } + } + } + } + } + } + + companion object { + /** + * Creates a [Line] component - layout-based line representation + */ + @UIBuilder + fun Layout.line( + block: Line.() -> Unit = {} + ) = Line(this).apply(children::add).apply(block) + + /** + * Adds a [Line] behind given [layout] + */ + @UIBuilder + fun Layout.lineBehind( + layout: Layout, + block: Line.() -> Unit = {} + ) = Line(this).insertLayout(this, layout, false).apply(block) + + /** + * Adds a [Line] over given [layout] + */ + @UIBuilder + fun Layout.lineOver( + layout: Layout, + block: Line.() -> Unit = {} + ) = Line(this).insertLayout(this, layout, true).apply(block) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt b/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt new file mode 100644 index 000000000..185e3c713 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/LineLayout.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.gui.component.core + +import com.lambda.gui.component.layout.Layout +import org.joml.Vector2d +import java.awt.Color + +abstract class LineLayout( + owner: Layout +) : Layout(owner) { + @UIRenderPr0p3rty var lineWidth = 1.0 + @UIRenderPr0p3rty var dashiness = 1.0 + @UIRenderPr0p3rty var dashPeriod = 1.0 + @UIRenderPr0p3rty var shade = false + + val points = mutableListOf() + + init { + properties.interactionPassthrough = true + } + + fun addPoint(position: Vector2d, color: Color = Color.WHITE) { + points.add(Point(position, color)) + } + + fun clearPoints() { + points.clear() + } + + data class Point( + val position: Vector2d, + val color: Color + ) +} diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt b/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt index 86cbfc6c5..399838df1 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/core/TextField.kt @@ -30,8 +30,9 @@ class TextField( owner: Layout, ) : Layout(owner) { @UIRenderPr0p3rty var text = "" - @UIRenderPr0p3rty var color: Color = Color.WHITE @UIRenderPr0p3rty var scale = 1.0 + + @UIRenderPr0p3rty var color: Color = Color.WHITE @UIRenderPr0p3rty var shadow = true @UIRenderPr0p3rty var textHAlignment = HAlign.LEFT diff --git a/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt b/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt index c6c4da26d..8d80e8842 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt @@ -370,28 +370,37 @@ open class Layout( // Update children children.forEach { child -> - if (e is GuiEvent.Render) return@forEach - if (e is GuiEvent.Update && !updateChildren) return@forEach - - if (e is GuiEvent.MouseClick) { - val newAction = if (child.isHovered) e.action else Mouse.Action.Release - - val newEvent = GuiEvent.MouseClick(e.button, newAction, e.mouse) - child.onEvent(newEvent) - return@forEach + when(e) { + is GuiEvent.Render -> return@forEach + is GuiEvent.Update -> { + if (!updateChildren) return@forEach + } + is GuiEvent.MouseMove -> { + if (!isHovered && !isPressed) return@forEach + } + is GuiEvent.MouseClick -> { + val newAction = if (child.isHovered) e.action else Mouse.Action.Release + val newEvent = GuiEvent.MouseClick(e.button, newAction, e.mouse) + child.onEvent(newEvent) + return@forEach + } + else -> {} } child.onEvent(e) } - if (e is GuiEvent.Render) { - val block = { - renderActions.forEach { it(this) } - children.forEach { it.onEvent(e) } - } + when (e) { + is GuiEvent.Render -> { + val block = { + renderActions.forEach { it(this) } + children.forEach { it.onEvent(e) } + } - if (!properties.scissor) block() - else ScissorAdapter.scissor(scissorRect, block) + if (!properties.scissor) block() + else ScissorAdapter.scissor(scissorRect, block) + } + else -> {} } } diff --git a/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt b/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt index 791de159c..5733771fd 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/window/Window.kt @@ -165,7 +165,7 @@ open class Window( init { position = initialPosition - properties.clampPosition = owner is RootLayout + //properties.clampPosition = owner is RootLayout onUpdate { // Update it here @@ -275,7 +275,7 @@ open class Window( /** * [Disabled] -> No ability to minimize the window * [Relative] -> Animation follows the height of the component ( animation(0.0, height) ) (height change is animated) - * [Absolute] -> Animation does not depend on the height ( animation(0.0, 1.0) * height ) (height change instantly affects the height) + * [Absolute] -> Animation does not depend on the height ( animation(0.0, 1.0) * height ) (height change instantly affects the output height) */ enum class Minimizing { Disabled, @@ -316,14 +316,14 @@ open class Window( minimizing: Minimizing = Minimizing.Relative, resizable: Boolean = true, autoResize: AutoResize = AutoResize.Disabled, - block: WindowContent.() -> Unit = {} + block: WindowContent.(Window) -> Unit = {} ) = Window( this, title, position, size, draggable, scrollable, minimizing, resizable, autoResize ).apply(children::add).apply { - block(this.content) + block(this.content, this) } private const val RESIZE_RANGE = 5.0 diff --git a/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt b/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt index ad997f854..7c89d7895 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/window/WindowContent.kt @@ -29,7 +29,7 @@ import kotlin.math.abs class WindowContent( owner: Window, - scrollable: Boolean + var scrollable: Boolean ) : Layout(owner) { private val window = owner private val animation = animationTicker(false) @@ -40,6 +40,8 @@ class WindowContent( private var renderScrollOffset by animation.exp(0.7) { scrollOffset + rubberbandDelta } + var freeScroll = false + override val scissorRect: Rect get() = Rect(window.titleBar.leftBottom, window.rightBottom) @@ -93,9 +95,11 @@ class WindowContent( dwheel = 0.0 val prevOffset = scrollOffset - scrollOffset = scrollOffset.coerceAtLeast( - owner.targetHeight - height - ).coerceAtMost(0.0) + if (!freeScroll) { + scrollOffset = scrollOffset.coerceAtLeast( + owner.targetHeight - height + ).coerceAtMost(0.0) + } rubberbandDelta += prevOffset - scrollOffset rubberbandDelta *= 0.5 @@ -104,8 +108,10 @@ class WindowContent( animation.tick() } - onMouseScroll { delta -> - dwheel += delta * 10.0 + owner.onMouseScroll { delta -> + if (owner.autoResize.enabled) return@onMouseScroll + if (!scrollable) return@onMouseScroll + dwheel += delta * 15.0 } } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt index 8d204562c..4c440aea1 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/ModuleWindow.kt @@ -17,6 +17,7 @@ package com.lambda.gui.impl.clickgui +import com.lambda.graphics.renderer.gui.BlurRenderer import com.lambda.module.tag.ModuleTag import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout @@ -25,6 +26,7 @@ import com.lambda.gui.component.window.WindowContent import com.lambda.gui.impl.clickgui.module.ModuleLayout.Companion.backgroundTint import com.lambda.gui.impl.clickgui.module.ModuleLayout.Companion.moduleLayout import com.lambda.module.ModuleRegistry +import com.lambda.module.modules.client.ClickGui import com.lambda.util.math.Vec2d class ModuleWindow( @@ -49,6 +51,16 @@ class ModuleWindow( onWindowExpand { minimize() } onWindowMinimize { minimize() } + + layoutBehind(titleBar) { + onUpdate { + rect = this@ModuleWindow.rect + } + + onRender { + BlurRenderer.blur(rect, ClickGui.blurStrength) + } + } } companion object { diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt index b22ee6c95..711eac25a 100644 --- a/common/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt @@ -22,6 +22,7 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.animation.AnimationTicker +import com.lambda.graphics.renderer.gui.BlurRenderer import com.lambda.gui.component.core.FilledRect.Companion.rect import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.core.OutlineRect.Companion.outline @@ -53,6 +54,17 @@ abstract class HudModule( Layout(null).apply { if (!background) return@apply + layout { + onUpdate { + rect = this@apply.rect + } + + onRender { + if (!ClickGui.hudBlur) return@onRender + BlurRenderer.blur(rect, ClickGui.blurStrength) + } + } + rect { onUpdate { position = this@apply.position diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt index 3c7ccacbe..f9fab5b81 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.client import com.lambda.Lambda.mc +import com.lambda.graphics.RenderMain import com.lambda.gui.LambdaScreen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -27,6 +28,8 @@ import com.lambda.gui.component.VAlign import com.lambda.gui.component.core.FilledRect.Companion.rect import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.layout.Layout +import com.lambda.gui.component.window.Window +import com.lambda.gui.component.window.Window.Companion.window import com.lambda.gui.impl.clickgui.ModuleWindow.Companion.moduleWindow import com.lambda.gui.impl.clickgui.core.AnimatedChild.Companion.animatedBackground import com.lambda.gui.impl.clickgui.module.setting.settings.UnitButton.Companion.unitButton @@ -49,21 +52,24 @@ object ClickGui : Module( val settingsHeight by setting("Settings Height", 16.0, 10.0..25.0, 0.1) val padding by setting("Padding", 1.0, 1.0..6.0, 0.1) val listStep by setting("List Step", 1.0, 0.0..6.0, 0.1) - val autoResize by setting("Auto Resize", false) + val autoResize by setting("Auto Resize", true) val roundRadius by setting("Round Radius", 3.0, 0.0..10.0, 0.1) + val blurStrength by setting("Blur Strength", 2, 0..10, 1) + val hudBlur by setting("HUD Blur", true) { blurStrength > 0 } + val backgroundTint by setting("Background Tint", Color.BLACK.setAlpha(0.4)) val backgroundCornerTint by setting("Background Corner Tint", Color.WHITE.setAlpha(0.4)) - val cornerTintWidth by setting("Corner Tint Width", 100.0, 1.0..500.0, 0.1) + val cornerTintWidth by setting("Corner Tint Width", 1.0, 1.0..500.0, 0.1) val cornerTintShade by setting("Corner Tint Shade", true) - val titleBackgroundColor by setting("Title Background Color", Color(80, 80, 80)) + val titleBackgroundColor by setting("Title Background Color", Color(80, 80, 80, 180)) val backgroundColor by setting("Background Color", titleBackgroundColor) val backgroundShade by setting("Background Shade", true) val outline by setting("Outline", true) - val outlineWidth by setting("Outline Width", 0.5, 0.5..5.0, 0.1) { outline } + val outlineWidth by setting("Outline Width", 1.0, 0.5..5.0, 0.1) { outline } val outlineColor by setting("Outline Color", Color.WHITE) { outline } val outlineShade by setting("Outline Shade", true) { outline } @@ -74,9 +80,8 @@ object ClickGui : Module( val fontScale by setting("Font Scale", 1.0, 0.5..2.0, 0.1) val fontOffset by setting("Font Offset", 4.0, 0.0..5.0, 0.1) - val dockingGridSize by setting("Docking Grid Size", 1.0, 0.1..10.0, 0.1) - val moduleEnabledColor by setting("Module Enabled Color", Color.WHITE.setAlpha(0.4)) + val moduleEnabledColor by setting("Module Enabled Color", Color.WHITE.setAlpha(0.6)) val moduleDisabledColor by setting("Module Disabled Color", Color.WHITE.setAlpha(0.0)) val moduleHoverAccent by setting("Module Hover Accent", 0.15, 0.0..0.3, 0.01) val moduleOpenAccent by setting("Module Open Accent", 0.3, 0.0..0.5, 0.01) @@ -115,8 +120,35 @@ object ClickGui : Module( var x = 10.0 val y = x - ModuleTag.defaults.forEach { tag -> - x += moduleWindow(tag, Vec2d(x, y)).width + 5 + window( // wrap module windows within a scrollable window + draggable = false, + minimizing = Window.Minimizing.Disabled + ) { window -> + window.use { + titleBar.onUpdate { + height = 0.0 + } + + onUpdate { + position = Vec2d.ZERO + size = RenderMain.screenSize + + content.freeScroll = true + content.scrollable = ClickGui.autoResize + } + + listOf( + titleBar.textField, + titleBarBackground, + contentBackground, + outlineRect, + glowRect + ).forEach(Layout::destroy) + } + + ModuleTag.defaults.forEach { tag -> + x += moduleWindow(tag, Vec2d(x, y)).width + 5 + } } switchButton("HUD", ::HUD) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt index 8ce70690d..cf54bab9f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt @@ -20,9 +20,8 @@ package com.lambda.module.modules.client import com.lambda.Lambda.mc import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.renderer.gui.FontRenderer.drawGlyph -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.drawString import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.Vec2d @@ -39,16 +38,20 @@ object LambdaMoji : Module( ) { val suggestions by setting("Chat Suggestions", true) - private val renderQueue = mutableListOf>() + private val renderQueue = mutableListOf>() init { listen { - renderQueue.forEach { (glyph, position, color) -> - drawGlyph(glyph, position, color) + renderQueue.forEach { (string, position, color) -> + drawString(string, position, color) } renderQueue.clear() } + + onToggle { + FontRenderer.invalidate() + } } // FixMe: Doesn't render properly when the chat scale is modified @@ -77,8 +80,7 @@ object LambdaMoji : Module( else -> Color(255, 255, 255, (color shr 24 and 0xFF)) } - val glyph = RenderSettings.emojiFont[emoji] ?: return@forEach - renderQueue.add(Triple(glyph, Vec2d(x + width, y), trueColor)) + renderQueue.add(Triple(emoji, Vec2d(x + width, y), trueColor)) // Replace the emoji with whitespaces depending on the player's settings raw = raw.replaceFirst(emoji, " ") diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt index b7f228fb2..67bcc46eb 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt @@ -17,6 +17,7 @@ package com.lambda.module.modules.client +import com.lambda.graphics.renderer.gui.FontRenderer import com.lambda.graphics.renderer.gui.font.core.LambdaEmoji import com.lambda.graphics.renderer.gui.font.core.LambdaFont import com.lambda.module.Module @@ -30,17 +31,34 @@ object RenderSettings : Module( ) { private val page by setting("Page", Page.Font) - // General - val useMemoryMapping by setting("Use Memory Mapping", true) { page == Page.General} - // Font - val textFont by setting("Text Font", LambdaFont.FiraSansRegular) { page == Page.Font } - val emojiFont by setting("Emoji Font", LambdaEmoji.Twemoji) { page == Page.Font } - val shadow by setting("Shadow", true) { page == Page.Font } - val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { page == Page.Font && shadow } - val shadowShift by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { page == Page.Font && shadow } - val gap by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font } - val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + val textFont by setting("Text Font", LambdaFont.FiraSansRegular) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val emojiFont by setting("Emoji Font", LambdaEmoji.Twemoji) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val shadowShift get() = shadowShift0 * 10.0 + private val shadowShift0 by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val gap get() = gap0 * 0.5f - 0.8f + private val gap0 by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + + val baselineOffset get() = baselineOffset0 * 2.0f - 16f + private val baselineOffset0 by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font }.onValueSet { _, _ -> + FontRenderer.invalidate() + } + val highlightColor by setting("Text Highlight Color", Color(214, 55, 87), visibility = { page == Page.Font }) val sdfMin by setting("SDF Min", 0.4, 0.0..1.0, 0.01, visibility = { page == Page.Font }) val sdfMax by setting("SDF Max", 1.0, 0.0..1.0, 0.01, visibility = { page == Page.Font }) @@ -52,7 +70,6 @@ object RenderSettings : Module( val outlineWidth by setting("Outline Width", 1.0, 0.1..5.0, 0.1, "Width of block outlines", unit = "px") { page == Page.ESP } private enum class Page { - General, Font, ESP, } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 01c626dcc..c3cfa78ef 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -498,7 +498,11 @@ object CrystalAura : Module( } fun buildDebug() { - withVertexTransform(buildWorldProjection(blockPos.crystalPosition, 0.4, Matrices.ProjRotationMode.TO_CAMERA)) { + Matrices.push { + peek().mul( + buildWorldProjection(blockPos.crystalPosition, 0.4, Matrices.ProjRotationMode.TO_CAMERA) + ) + val lines = arrayOf( "Decision: ${actionType.name} ${priority.roundToStep(0.01)}", "", diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index dc52b3bea..4723dfd50 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -20,15 +20,33 @@ package com.lambda.module.modules.debug import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DynamicAABB.Companion.dynamicBox +import com.lambda.graphics.renderer.esp.builders.drawLine +import com.lambda.graphics.renderer.esp.builders.drawLineBetweenBoxes import com.lambda.graphics.renderer.esp.builders.ofBox +import com.lambda.graphics.renderer.gui.LineRenderer +import net.minecraft.util.math.Vec3d import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.setAlpha import com.lambda.util.world.entitySearch import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Box +import org.joml.Vector2d import java.awt.Color +/** + * RenderTest module demonstrates different rendering techniques: + * 1. Drawing boxes around entities using DynamicESP + * 2. Drawing a box at the player's position using StaticESP + * 3. Drawing lines in 3D space: + * - Lines between entities + * - A 3D star shape around the player + * - A continuous line connecting multiple points + * 4. Drawing 2D lines on the screen (a triangle and a square) + * + * Enable the module and toggle "Draw Lines" to see the line examples. + * Adjust line width, dashiness, and dash period to see different line styles. + */ object RenderTest : Module( name = "Render:shrimp:Test:canned_food:", description = "RenderTest", @@ -41,19 +59,70 @@ object RenderTest : Module( private val test31 by setting("Holla huh 1", true, visibility = { !test1 }) private val test32 by setting("Holla buh 2", true, visibility = { !test1 }) + private val drawLines by setting("Draw Lines", true) + private val lineWidth by setting("Line Width", 2.0, 0.5..5.0, 0.5, visibility = ::drawLines) + private val dashiness by setting("Dashiness", 1.0, 0.0..1.0, 0.1, visibility = ::drawLines) + private val dashPeriod by setting("Dash Period", 1.0, 0.5..5.0, 0.5, visibility = ::drawLines) + private val outlineColor = Color(100, 150, 255).setAlpha(0.5) private val filledColor = outlineColor.setAlpha(0.2) + private val lineColor = Color(255, 100, 100).setAlpha(0.8) init { - listen { - entitySearch(8.0) - .forEach { entity -> - it.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) - } + listen { event -> + val entities = entitySearch(8.0).sortedBy { it.distanceTo(player) } + + // Draw boxes around entities + entities.forEach { entity -> + event.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) + } + + entities.firstOrNull()?.let { first -> + // Example of drawing lines between entities using the new drawLineBetweenBoxes function + event.renderer.drawLineBetweenBoxes( + player.dynamicBox, + first.dynamicBox, + lineColor, + lineWidth, + dashiness, + dashPeriod + ) + } } listen { + // Draw a box at player position it.renderer.ofBox(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + + if (drawLines) { + val renderer = it.renderer + val pos = player.pos + val size = 1.0 + + // Define the points of the star + val center = pos + val top = Vec3d(center.x, center.y + size, center.z) + val bottom = Vec3d(center.x, center.y - size, center.z) + val left = Vec3d(center.x - size, center.y, center.z) + val right = Vec3d(center.x + size, center.y, center.z) + val front = Vec3d(center.x, center.y, center.z + size) + val back = Vec3d(center.x, center.y, center.z - size) + + // Connect the points to form a 3D star using the new drawLine function + renderer.drawLine(top, left, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, right, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, front, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + renderer.drawLine(top, back, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + + renderer.drawLine(bottom, left, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, right, Color.RED, Color.GREEN, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, front, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + renderer.drawLine(bottom, back, Color.RED, Color.BLUE, lineWidth, dashiness, dashPeriod) + + // Example of drawing a line connecting multiple points + val points = listOf(top, right, front, bottom, left, back, top) + renderer.drawLine(points, lineColor, lineWidth, dashiness, dashPeriod) + } } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index 006241337..82dc92555 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -68,6 +68,7 @@ object BlockESP : Module( init { onToggle { if (barrier) mc.worldRenderer.reload() + esp.rebuild() } } @@ -77,8 +78,8 @@ object BlockESP : Module( if (state.block !in blocks) return@newChunkedESP val sides = if (mesh) { - buildSideMesh(position) { - world.getBlockState(it).block in blocks + buildSideMesh(position) { neighbour -> + world.getBlockState(neighbour).block in blocks } } else DirectionMask.ALL diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl new file mode 100644 index 000000000..3b7f5509d --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_h.glsl @@ -0,0 +1,29 @@ +attributes { + vec2 uv; +}; + +#include "gaussian" + +void fragment() { + vec4 sum = texture(u_Texture, v_TexCoord) * WEIGHT_BASE; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_0 * u_TexelSize.x, 0.0)) * WEIGHT_0; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_0 * u_TexelSize.x, 0.0)) * WEIGHT_0; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_1 * u_TexelSize.x, 0.0)) * WEIGHT_1; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_1 * u_TexelSize.x, 0.0)) * WEIGHT_1; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_2 * u_TexelSize.x, 0.0)) * WEIGHT_2; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_2 * u_TexelSize.x, 0.0)) * WEIGHT_2; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_3 * u_TexelSize.x, 0.0)) * WEIGHT_3; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_3 * u_TexelSize.x, 0.0)) * WEIGHT_3; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_4 * u_TexelSize.x, 0.0)) * WEIGHT_4; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_4 * u_TexelSize.x, 0.0)) * WEIGHT_4; + + sum += texture(u_Texture, v_TexCoord + vec2(OFFSET_5 * u_TexelSize.x, 0.0)) * WEIGHT_5; + sum += texture(u_Texture, v_TexCoord - vec2(OFFSET_5 * u_TexelSize.x, 0.0)) * WEIGHT_5; + + color = sum; +}# diff --git a/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl new file mode 100644 index 000000000..d0e2becbe --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/post/gaussian_v.glsl @@ -0,0 +1,29 @@ +attributes { + vec2 uv; +}; + +#include "gaussian" + +void fragment() { + vec4 sum = texture(u_Texture, v_TexCoord) * WEIGHT_BASE; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_0 * u_TexelSize.y)) * WEIGHT_0; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_0 * u_TexelSize.y)) * WEIGHT_0; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_1 * u_TexelSize.y)) * WEIGHT_1; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_1 * u_TexelSize.y)) * WEIGHT_1; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_2 * u_TexelSize.y)) * WEIGHT_2; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_2 * u_TexelSize.y)) * WEIGHT_2; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_3 * u_TexelSize.y)) * WEIGHT_3; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_3 * u_TexelSize.y)) * WEIGHT_3; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_4 * u_TexelSize.y)) * WEIGHT_4; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_4 * u_TexelSize.y)) * WEIGHT_4; + + sum += texture(u_Texture, v_TexCoord + vec2(0.0, OFFSET_5 * u_TexelSize.y)) * WEIGHT_5; + sum += texture(u_Texture, v_TexCoord - vec2(0.0, OFFSET_5 * u_TexelSize.y)) * WEIGHT_5; + + color = sum; +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl index 5d4abd1dc..0572a9dba 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl @@ -1,5 +1,5 @@ attributes { - vec4 pos; + vec2 pos; vec2 uv; vec4 color; }; @@ -7,16 +7,21 @@ attributes { uniforms { sampler2D u_FontTexture; # fragment sampler2D u_EmojiTexture; # fragment - float u_SDFMin; # fragment - float u_SDFMax; # fragment + + float u_SDFMin; # fragment + float u_SDFMax; # fragment + + vec4 u_Color; # vertex }; export { - vec2 v_TexCoord; # uv - vec4 v_Color; # color + core gl_Position; # u_ProjModel * vec4(pos, 0.0, 1.0) + vec2 v_TexCoord; # uv + vec4 v_Color; # color * u_Color }; #include "sdf" +#include "shade" void fragment() { bool isEmoji = v_TexCoord.x < 0.0; @@ -28,5 +33,5 @@ void fragment() { } float sdf = sdf(texture(u_FontTexture, v_TexCoord).r, u_SDFMin, u_SDFMax); - color = vec4(1.0, 1.0, 1.0, sdf) * v_Color; -}# \ No newline at end of file + color = vec4(1.0, 1.0, 1.0, sdf) * shade * v_Color; +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl new file mode 100644 index 000000000..8c11d8c52 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/line.glsl @@ -0,0 +1,27 @@ +attributes { + vec4 pos; + float alpha; + vec4 color; +}; + +uniforms { + float u_Width; # fragment + float u_Dashiness; # fragment + float u_DashPeriod; # fragment +}; + +export { + vec4 v_Color; # color + float v_Alpha; # alpha +}; + +#define PI 3.1415926535 +#define SMOOTHING 100 + +void fragment() { + float smoothing = min(u_Width, SMOOTHING); + float dist = abs(v_Alpha - 0.5) * 2 * u_Width; + float alpha = smoothstep(u_Width, u_Width - smoothing, dist); + + color = v_Color * vec4(1.0, 1.0, 1.0, alpha); +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl new file mode 100644 index 000000000..6c1f007c5 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/multiline.glsl @@ -0,0 +1,44 @@ +attributes { + vec4 pos; + float distance; + float width; + float alpha; + vec4 color; +}; + +uniforms { + float u_Length; # fragment + float u_Dashiness; # fragment + float u_DashPeriod; # fragment +}; + +export { + vec4 v_Color; # color + float v_Distance; # distance + float v_Width; # width + float v_Alpha; # alpha +}; + +#define PI 3.1415926535 +#define SMOOTHING 100 + +void fragment() { + float smoothing = min(v_Width, SMOOTHING); + float dist = abs(v_Alpha - 0.5) * 2 * v_Width; + float alpha = smoothstep(v_Width, v_Width - smoothing, dist); + + if (u_Length > 0.0) { + float startFade = smoothstep(0.0, smoothing * 0.5, v_Distance); + float endFade = smoothstep(u_Length, u_Length - smoothing * 0.5, v_Distance); + alpha *= min(startFade, endFade); + } + + if (u_Dashiness < 0.98) { + float dashSmoothing = smoothing / u_DashPeriod; + float dashPos = fract(v_Distance / u_DashPeriod); + float dashed = smoothstep(0.0, dashSmoothing, dashPos) * (1.0 - smoothstep(u_Dashiness, u_Dashiness + dashSmoothing, dashPos)); + alpha *= mix(dashed, 1.0, max(min((u_Dashiness - 0.9) * 10.0, 1.0), 0.0)); + } + + color = v_Color * vec4(1.0, 1.0, 1.0, alpha); +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl index 7ee5ec204..b658c56a3 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl @@ -1,13 +1,13 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float alpha = 1 - smoothstep(-SMOOTHING, SMOOTHING, distance); color = v_Color * vec4(1.0, 1.0, 1.0, alpha) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl index 094f9c373..2a0c1e962 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl @@ -1,7 +1,5 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { @@ -17,6 +15,8 @@ uniforms { #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float innerDistance = signedDistance(vec4( u_InnerRoundRightBottom, @@ -37,4 +37,4 @@ void fragment() { smoothstep(0.0, u_RectWidth, bloomDistance); color = v_Color * vec4(1.0, 1.0, 1.0, bloomAlpha * glowAlpha) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl index d27ac5873..3ea1edb97 100644 --- a/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl @@ -1,7 +1,5 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { @@ -11,8 +9,10 @@ uniforms { #include "rect" void fragment() { + if (v_Color.a == 0.0) discard; + float distance = signedDistance(); float innerAlpha = smoothstep(-u_RectWidth - SMOOTHING, -u_RectWidth + SMOOTHING, distance); float outerAlpha = 1 - smoothstep(u_RectWidth - SMOOTHING, u_RectWidth + SMOOTHING, distance); color = v_Color * vec4(1.0, 1.0, 1.0, min(innerAlpha, outerAlpha)) * shade + noise; -}# \ No newline at end of file +}# diff --git a/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl new file mode 100644 index 000000000..ada98e79a --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/gaussian.glsl @@ -0,0 +1,33 @@ +attributes { + vec2 uv; +}; + +uniforms { + sampler2D u_Texture; # fragment + vec2 u_TexelSize; # global + vec2 u_Extend; # vertex + + vec2 u_Position1; # vertex + vec2 u_Position2; # vertex +}; + +export { + core gl_Position; # u_ProjModel * vec4(mix(u_Position1, u_Position2, uv), 0.0, 1.0) + vec4(u_TexelSize * u_Extend * vec2(uv.x - 0.5, 0.5 - uv.y) * 2 * 12, 0.0, 0.0) + vec2 v_TexCoord; # gl_Position.xy * 0.5 + 0.5 +}; + +#define WEIGHT_BASE 0.09950225481157 + +#define WEIGHT_0 0.18446050802858 +#define WEIGHT_1 0.13614942259252 +#define WEIGHT_2 0.07862968075756 +#define WEIGHT_3 0.03538335634090 +#define WEIGHT_4 0.01232869558916 +#define WEIGHT_5 0.00329720928547 + +#define OFFSET_0 1.47692307692307 +#define OFFSET_1 3.44615384615384 +#define OFFSET_2 5.41538461538461 +#define OFFSET_3 7.38461538461538 +#define OFFSET_4 9.35384615384615 +#define OFFSET_5 11.3230769230769 diff --git a/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl index 4fe1b2220..ed1469b07 100644 --- a/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl +++ b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl @@ -1,21 +1,29 @@ attributes { - vec4 pos; vec2 uv; - vec4 color; }; uniforms { - vec2 u_Size; # fragment - float u_RoundLeftTop; # fragment float u_RoundLeftBottom; # fragment float u_RoundRightBottom; # fragment float u_RoundRightTop; # fragment + + vec4 u_ColorLeftTop; # vertex + vec4 u_ColorLeftBottom; # vertex + vec4 u_ColorRightBottom; # vertex + vec4 u_ColorRightTop; # vertex + + vec2 u_Pos; # vertex + vec2 u_Size; # global + + float u_Expand; # vertex }; export { - vec2 v_TexCoord; # uv - vec4 v_Color; # color + vec2 v_TexCoord; # mix(-u_Expand / u_Size, 1.0 + (u_Expand / u_Size), uv) + core gl_Position; # u_ProjModel * vec4(mix(u_Pos - 0.3, u_Pos + u_Size + 0.3, v_TexCoord), 0.0, 1.0) + + vec4 v_Color; # mix(mix(u_ColorLeftTop, u_ColorRightTop, uv.x), mix(u_ColorLeftBottom, u_ColorRightBottom, uv.x), uv.y) }; #include "shade"