diff --git a/common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java b/common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java index 170b025e1..9f3cf4c9a 100644 --- a/common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java @@ -40,6 +40,7 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -87,10 +88,22 @@ private void refreshEmojiSuggestion(CallbackInfo ci) { int start = neoLambda$getLastColon(textToCursor); if (start == -1) return; - String emojiString = typing.substring(start + 1); + Matcher emojiMatcher = EMOJI_PATTERN.matcher(textToCursor); + Map emojiKeys = LambdaAtlas.INSTANCE.getKeys(RenderSettings.INSTANCE.getEmojiFont()); + while (emojiMatcher.find()) { + int openingColon = emojiMatcher.start(1); + String key = emojiMatcher.group(2); + int closingColon = emojiMatcher.end(3); + + if (emojiKeys.containsKey(key) && start >= openingColon && start < closingColon) { + // If the colon is part of a previous valid emoji, return + return; + } + } + + String emojiString = textToCursor.substring(start + 1); - Stream results = LambdaAtlas.INSTANCE.getKeys(RenderSettings.INSTANCE.getEmojiFont()) - .keySet().stream() + Stream results = emojiKeys.keySet().stream() .filter(s -> s.startsWith(emojiString)) .map(s -> s + ":"); @@ -105,6 +118,9 @@ private void refreshEmojiSuggestion(CallbackInfo ci) { @Unique private static final Pattern COLON_PATTERN = Pattern.compile("(:[a-zA-Z0-9_]+)"); + @Unique + private static final Pattern EMOJI_PATTERN = Pattern.compile("(:)([a-zA-Z0-9_]+)(:)"); + @Unique private int neoLambda$getLastColon(String input) { if (Strings.isNullOrEmpty(input)) return -1; diff --git a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java index 84f0b9e21..9d5980c7f 100644 --- a/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/VertexBufferMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.graphics.buffer.vertex.ElementBuffer; +import com.lambda.graphics.buffer.Buffer; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gl.VertexBuffer; import net.minecraft.client.render.BufferBuilder; @@ -37,6 +37,6 @@ public class VertexBufferMixin { @Inject(method = "uploadIndexBuffer", at = @At("RETURN")) private void onConfigureIndexBuffer(BufferBuilder.DrawParameters parameters, ByteBuffer vertexBuffer, CallbackInfoReturnable cir) { RenderSystem.ShapeIndexBuffer value = cir.getReturnValue(); - ElementBuffer.lastIbo = value == null ? this.indexBufferId : value.id; + Buffer.lastIbo = value == null ? this.indexBufferId : value.id; } } diff --git a/common/src/main/kotlin/com/lambda/config/Configurable.kt b/common/src/main/kotlin/com/lambda/config/Configurable.kt index 3b6375842..b17e6f5b7 100644 --- a/common/src/main/kotlin/com/lambda/config/Configurable.kt +++ b/common/src/main/kotlin/com/lambda/config/Configurable.kt @@ -37,6 +37,7 @@ import com.lambda.util.KeyCode import com.lambda.util.Nameable import net.minecraft.block.Block import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d import java.awt.Color /** @@ -387,6 +388,40 @@ abstract class Configurable( visibility: () -> Boolean = { true }, ) = ColorSetting(name, defaultValue, description, visibility).register() + /** + * Creates a [Vec3dSetting] with the provided parameters and adds it to the [settings]. + * + * @param name The unique identifier for the setting. + * @param defaultValue The default [Vec3d] value of the setting. + * @param description A brief explanation of the setting's purpose and behavior. + * @param visibility A lambda expression that determines the visibility status of the setting. + * + * @return The created [Vec3dSetting]. + */ + fun setting( + name: String, + defaultValue: Vec3d, + description: String = "", + visibility: () -> Boolean = { true }, + ) = Vec3dSetting(name, defaultValue, description, visibility).register() + + /** + * Creates a [BlockPosSetting] with the provided parameters and adds it to the [settings]. + * + * @param name The unique identifier for the setting. + * @param defaultValue The default [BlockPos.Mutable] value of the setting. + * @param description A brief explanation of the setting's purpose and behavior. + * @param visibility A lambda expression that determines the visibility status of the setting. + * + * @return The created [BlockPosSetting]. + */ + fun setting( + name: String, + defaultValue: BlockPos.Mutable, + description: String = "", + visibility: () -> Boolean = { true }, + ) = BlockPosSetting(name, defaultValue, description, visibility).register() + /** * Creates a [BlockPosSetting] with the provided parameters and adds it to the [settings]. * diff --git a/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt index 6fbeb537e..de926c6c4 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt @@ -43,24 +43,27 @@ class EnumSetting>( description, visibility, ) { - val enumValues: Array = defaultValue.declaringJavaClass.enumConstants - fun next() { - value = enumValues[((value.ordinal + 1) % enumValues.size)] + value = value.enumValues[((value.ordinal + 1) % value.enumValues.size)] } override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) { required(word(name)) { parameter -> suggests { _, builder -> - enumValues.forEach { builder.suggest(it.name.capitalize()) } + value.enumValues.forEach { builder.suggest(it.name.capitalize()) } builder.buildFuture() } executeWithResult { - val newValue = enumValues.find { it.name.equals(parameter().value(), true) } + val newValue = value.enumValues.find { it.name.equals(parameter().value(), true) } ?: return@executeWithResult failure("Invalid value") trySetValue(newValue) return@executeWithResult success() } } } + + companion object { + val > T.enumValues: Array get() = + declaringJavaClass.enumConstants + } } diff --git a/common/src/main/kotlin/com/lambda/config/settings/complex/Vec3dSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/complex/Vec3dSetting.kt new file mode 100644 index 000000000..05901c3a1 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/settings/complex/Vec3dSetting.kt @@ -0,0 +1,54 @@ +/* + * 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.config.settings.complex + +import com.google.gson.reflect.TypeToken +import com.lambda.brigadier.argument.double +import com.lambda.brigadier.argument.integer +import com.lambda.brigadier.argument.value +import com.lambda.brigadier.execute +import com.lambda.brigadier.required +import com.lambda.config.AbstractSetting +import com.lambda.util.extension.CommandBuilder +import net.minecraft.command.CommandRegistryAccess +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d + +class Vec3dSetting( + override val name: String, + defaultValue: Vec3d, + description: String, + visibility: () -> Boolean, +) : AbstractSetting( + defaultValue, + TypeToken.get(Vec3d::class.java).type, + description, + visibility +) { + override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) { + required(double("X", -30000000.0, 30000000.0)) { x -> + required(double("Y", -64.0, 255.0)) { y -> + required(double("Z", -30000000.0, 30000000.0)) { z -> + execute { + trySetValue(Vec3d(x().value(), y().value(), z().value())) + } + } + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index e448cd798..9681c93af 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -20,10 +20,15 @@ package com.lambda.graphics import com.lambda.Lambda.mc import com.lambda.event.EventFlow.post import com.lambda.event.events.RenderEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.gl.GlStateUtils.setupGL import com.lambda.graphics.gl.Matrices 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 @@ -57,6 +62,20 @@ object RenderMain { setupGL { RenderEvent.World().post() + StaticESP.render() + DynamicESP.render() + } + } + + init { + listen { + StaticESP.clear() + RenderEvent.StaticESP().post() + StaticESP.upload() + + DynamicESP.clear() + RenderEvent.DynamicESP().post() + DynamicESP.upload() } } 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 7af4ae588..28f7b4872 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/Buffer.kt @@ -17,10 +17,7 @@ package com.lambda.graphics.buffer -import com.lambda.graphics.gl.bufferBound -import com.lambda.graphics.gl.bufferUsageValid -import com.lambda.graphics.gl.bufferValid -import org.lwjgl.opengl.GL44.* +import org.lwjgl.opengl.GL46.* import java.nio.ByteBuffer abstract class Buffer( @@ -29,15 +26,9 @@ abstract class Buffer( * * | Number of Buffers | Purpose | * |-------------------|---------------------------------------------------------------------------------------------------------------| - * | 1 Buffer | Simple operations like storing vertex data or a single texture. | - * | 2 Buffers | Double buffering for smooth rendering (alternating between writing to one buffer while reading from another). | - * | 3 Buffers | Triple buffering for improved frame rate and reduced screen tearing at the cost of memory usage. | - * - * In double buffering, you have a front buffer (A) and a back buffer (B). While one is displayed (A), the other (B) is being drawn to. After drawing, the buffers are swapped. However, during the swap (often synchronized with vertical retrace to avoid tearing), drawing cannot resume until it completes, potentially causing delays. - * - * In triple buffering, there is a front buffer (A) and two back buffers (B and C). This allows drawing to continue on the second back buffer (C) while waiting for the swap to finish between the front (A) and the first back buffer (B). This reduces idle time and improves frame rates, especially if the app runs slower than the monitor's refresh rate. - * - * Triple buffering helps maintain smoother frame rates, but if your app runs faster than the monitor's refresh rate, it offers little benefit as you eventually still wait for vblank synchronization. + * | 1 Buffer | Simple operations like storing vertex data, reading from the framebuffer, etc. | + * | 2 Buffers | Increase throughput by not having to explicitly sync memory. | + * | 3 Buffers | If the driver run alongside the CPU and GPU, then each must have their own buffer to avoid stalling. | */ val buffers: Int = 1, @@ -49,17 +40,19 @@ abstract class Buffer( /** * Specifies how the buffers are used * - * | Buffer Usage | Description | - * |--------------------------------|-----------------------------------------------------------------| - * | GL_STREAM_DRAW | Data is set once and used a few times for drawing. | - * | GL_STREAM_READ | Data is set once and used a few times for reading. | - * | GL_STREAM_COPY | Data is set once and used a few times for copying. | - * | GL_STATIC_DRAW | Data is set once and used many times for drawing. | - * | GL_STATIC_READ | Data is set once and used many times for reading. | - * | GL_STATIC_COPY | Data is set once and used many times for copying. | - * | GL_DYNAMIC_DRAW | Data is modified repeatedly and used many times for drawing. | - * | GL_DYNAMIC_READ | Data is modified repeatedly and used many times for reading. | - * | GL_DYNAMIC_COPY | Data is modified repeatedly and used many times for copying. | + * | Buffer Usage | Description | + * |----------------------------------|-----------------------------------------------------------------| + * | [GL_STREAM_DRAW] | Data is set once and used a few times for drawing. | + * | [GL_STREAM_READ] | Data is set once and used a few times for reading. | + * | [GL_STREAM_COPY] | Data is set once and used a few times for copying. | + * | [GL_STATIC_DRAW] | Data is set once and used many times for drawing. | + * | [GL_STATIC_READ] | Data is set once and used many times for reading. | + * | [GL_STATIC_COPY] | Data is set once and used many times for copying. | + * | [GL_DYNAMIC_DRAW] | Data is modified repeatedly and used many times for drawing. | + * | [GL_DYNAMIC_READ] | Data is modified repeatedly and used many times for reading. | + * | [GL_DYNAMIC_COPY] | Data is modified repeatedly and used many times for copying. | + * + * @see Buffer object */ abstract val usage: Int @@ -67,22 +60,24 @@ abstract class Buffer( * Specifies the target to which the buffer object is bound which must be one * of the following: * - * | Buffer Binding Target | Purpose | - * |-------------------------------|--------------------------------------| - * | GL_ARRAY_BUFFER | Vertex attributes | - * | GL_ATOMIC_COUNTER_BUFFER | Atomic counter storage | - * | GL_COPY_READ_BUFFER | Buffer copy source | - * | GL_COPY_WRITE_BUFFER | Buffer copy destination | - * | GL_DISPATCH_INDIRECT_BUFFER | Indirect compute dispatch commands | - * | GL_DRAW_INDIRECT_BUFFER | Indirect command arguments | - * | GL_ELEMENT_ARRAY_BUFFER | Vertex array indices | - * | GL_PIXEL_PACK_BUFFER | Pixel read target | - * | GL_PIXEL_UNPACK_BUFFER | Texture data source | - * | GL_QUERY_BUFFER | Query result buffer | - * | GL_SHADER_STORAGE_BUFFER | Read-write storage for shaders | - * | GL_TEXTURE_BUFFER | Texture data buffer | - * | GL_TRANSFORM_FEEDBACK_BUFFER | Transform feedback buffer | - * | GL_UNIFORM_BUFFER | Uniform block storage | + * | Buffer Binding Target | Purpose | + * |---------------------------------|--------------------------------------| + * | [GL_ARRAY_BUFFER] | Vertex attributes | + * | [GL_ATOMIC_COUNTER_BUFFER] | Atomic counter storage | + * | [GL_COPY_READ_BUFFER] | Buffer copy source | + * | [GL_COPY_WRITE_BUFFER] | Buffer copy destination | + * | [GL_DISPATCH_INDIRECT_BUFFER] | Indirect compute dispatch commands | + * | [GL_DRAW_INDIRECT_BUFFER] | Indirect command arguments | + * | [GL_ELEMENT_ARRAY_BUFFER] | Vertex array indices | + * | [GL_PIXEL_PACK_BUFFER] | Pixel read target | + * | [GL_PIXEL_UNPACK_BUFFER] | Texture data source | + * | [GL_QUERY_BUFFER] | Query result buffer | + * | [GL_SHADER_STORAGE_BUFFER] | Read-write storage for shaders | + * | [GL_TEXTURE_BUFFER] | Texture data buffer | + * | [GL_TRANSFORM_FEEDBACK_BUFFER] | Transform feedback buffer | + * | [GL_UNIFORM_BUFFER] | Uniform block storage | + * + * @see Buffer object */ abstract val target: Int @@ -90,82 +85,93 @@ abstract class Buffer( * Specifies a combination of access flags indicating the desired * access to the mapping range and must contain one or more of the following: * - * | Flag | Description | Disclaimer | - * |-------------------------------|-----------------------------------------------------|---------------------------------------------------------------| - * | GL_MAP_READ_BIT | Allows reading buffer data. | Undefined if used without this flag. | - * | GL_MAP_WRITE_BIT | Allows modifying buffer data. | Undefined if used without this flag. | - * | GL_MAP_PERSISTENT_BIT | Enables persistent mapping during GL operations. | Requires proper allocation with GL_MAP_PERSISTENT_BIT. | - * | GL_MAP_COHERENT_BIT | Ensures changes are visible without extra steps. | Without this, explicit sync is needed. | - * | GL_MAP_INVALIDATE_RANGE_BIT | Discards previous contents of the mapped range. | Cannot be used with GL_MAP_READ_BIT. | - * | GL_MAP_INVALIDATE_BUFFER_BIT | Discards previous contents of the entire buffer. | Cannot be used with GL_MAP_READ_BIT. | - * | GL_MAP_FLUSH_EXPLICIT_BIT | Requires explicit flushing of modified sub-ranges. | Only with GL_MAP_WRITE_BIT. Data may be undefined if skipped. | - * | GL_MAP_UNSYNCHRONIZED_BIT | Skips synchronization before mapping. | May cause data corruption if regions overlap. | + * | Flag | Description | Information | + * |--------------------------------|-----------------------------------------------------|-------------------------------------------------------------------------------------------| + * | [GL_MAP_READ_BIT] | Allows reading buffer data. | Buffer must be created with this flag. Undefined if not included in access. | + * | [GL_MAP_WRITE_BIT] | Allows modifying buffer data. | Buffer must be created with this flag. Undefined if not included in access. | + * | [GL_MAP_PERSISTENT_BIT] | Enables persistent mapping during GL operations. | Requires buffer to be created with [GL_MAP_PERSISTENT_BIT]. | + * | [GL_MAP_COHERENT_BIT] | Ensures changes are visible to the GPU. | Requires buffer creation with [GL_MAP_PERSISTENT_BIT] or explicit sync. | + * | [GL_MAP_INVALIDATE_RANGE_BIT] | Discards previous contents of the mapped range. | Cannot be used with [GL_MAP_READ_BIT]. | + * | [GL_MAP_INVALIDATE_BUFFER_BIT] | Discards previous contents of the entire buffer. | Cannot be used with [GL_MAP_READ_BIT]. | + * | [GL_MAP_FLUSH_EXPLICIT_BIT] | Requires explicit flushing of modified sub-ranges. | Only valid with [GL_MAP_WRITE_BIT]. Data may be undefined if flushing is skipped. | + * | [GL_MAP_UNSYNCHRONIZED_BIT] | Skips synchronization before mapping. | May cause data corruption if buffer is accessed concurrently. | + * | [GL_DYNAMIC_STORAGE_BIT] | Allows updates via [glBufferSubData]. | If omitted, [glBufferSubData] will fail. | + * | [GL_CLIENT_STORAGE_BIT] | Hints that the buffer should prefer client storage. | Implementation-dependent optimization. | + * + * @see Buffer object */ abstract val access: Int /** - * Index of the current buffer + * Index of the current buffer. */ var index: Int = 0; private set /** - * List of all the buffers + * List of all the buffers. */ private val bufferIds = IntArray(buffers) /** - * Binds the buffer id to the [target] + * Binds the buffer id to the [target]. */ open fun bind(id: Int) = glBindBuffer(target, id) /** - * Binds current the buffer [index] to the [target] + * Binds current the buffer [index] to the [target]. */ fun bind() = bind(bufferAt(index)) /** - * Returns the id of the buffer based on the index + * Returns the id of the buffer based on the index. */ fun bufferAt(index: Int) = bufferIds[index] /** - * Swaps the buffer [index] if [buffers] is greater than 1 + * Swaps the buffer [index] if [buffers] is greater than 1. */ - fun swap() { - index = (index + 1) % buffers - } + fun swap() { index = (index + 1) % buffers } /** - * Update the current buffer without re-allocating - * Alternative to [map] + * Update the current buffer without re-allocating. + * + * @throws [IllegalArgumentException] if the target or usage is invalid + * + * @see glBufferSubData */ - open fun update( - data: ByteBuffer, - offset: Long, - ): Throwable? { - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferBound(target)) - return IllegalArgumentException("Target is zero bound for glBufferSubData") + open fun update(data: ByteBuffer, offset: Long) { + validate() + bind() glBufferSubData(target, offset, data) + bind(0) + } - return null + /** + * Update the current buffer without re-allocating. + * + * @throws [IllegalArgumentException] if the target or usage is invalid + * + * @see glBufferSubData + */ + open fun update(offset: Long, size: Long, data: Long) { + validate() + + bind() + nglBufferSubData(target, offset, size, data) + bind(0) } /** - * Allocates a region of memory for the buffer - * This function handles the buffer binding + * Allocates each backing buffer with the specified data. * * @param data The data to put in the new allocated buffer + * @throws [IllegalArgumentException] if the target or usage is invalid + * + * @see glBufferData */ - open fun allocate(data: ByteBuffer): Throwable? { - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferUsageValid(usage)) - return IllegalArgumentException("Buffer usage is invalid") + open fun allocate(data: ByteBuffer) { + validate() repeat(buffers) { bind() @@ -174,23 +180,18 @@ abstract class Buffer( } bind(0) - - return null } /** - * Allocates memory for each backing buffer using the specified size - * This function handles the buffer binding + * Allocates memory for each backing buffer of specified size. * * @param size The size of the new buffer - * @return An [IllegalArgumentException] if validation fails; null if the allocation succeeds + * @throws [IllegalArgumentException] if the target or usage is invalid + * + * @see glBufferData */ - open fun allocate(size: Long): Throwable? { - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferUsageValid(usage)) - return IllegalArgumentException("Buffer usage is invalid") + open fun allocate(size: Long) { + validate() repeat(buffers) { bind() @@ -199,137 +200,182 @@ abstract class Buffer( } bind(0) - - return null } /** - * Allocates new storage for the OpenGL buffer using the provided data - * This function cannot be called twice for the same buffer - * This function handles the buffer binding + * Allocates new storage for the OpenGL buffer using the provided data. + * + * This function cannot be called twice for the same buffer. + * + * You cannot update the content of the buffer directly unless you are pinning memory + * or have GL_DYNAMIC_STORAGE_BIT in the access flags. + * + * @throws [IllegalArgumentException] if the target or usage is invalid * - * @return [IllegalArgumentException] for an invalid target or usage; null if storage allocation is successful + * @see glBufferStorage */ - open fun storage(data: ByteBuffer): Throwable? { - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferUsageValid(usage)) - return IllegalArgumentException("Buffer usage is invalid") + open fun storage(data: ByteBuffer) { + validate() repeat(buffers) { bind() - glBufferStorage(target, data, access or GL_DYNAMIC_STORAGE_BIT) + glBufferStorage(target, data, access) swap() } bind(0) - - return null } /** - * Allocates storage for the buffer object - * This function cannot be called twice for the same buffer - * This function handles the buffer binding + * Allocates storage for the buffer object. + * + * This function cannot be called twice for the same buffer. + * + * You cannot update the content of the buffer directly unless you are pinning memory + * or have GL_DYNAMIC_STORAGE_BIT in the access flags. * * @param size The size of the storage buffer - * @return [IllegalArgumentException] if the target or usage is invalid; null if storage allocation succeeds + * @throws [IllegalArgumentException] if the target or usage is invalid + * + * @see glBufferStorage */ - open fun storage(size: Long): Throwable? { - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferUsageValid(usage)) - return IllegalArgumentException("Buffer usage is invalid") + open fun storage(size: Long) { + validate() repeat(buffers) { bind() - glBufferStorage(target, size.coerceAtLeast(0), access or GL_DYNAMIC_STORAGE_BIT) + glBufferStorage(target, size.coerceAtLeast(0), access) swap() } bind(0) - - return null } + // TODO: + // GL_MAP_COHERENT_BIT makes it so changes in the mapped memory are automatically visible to the gpu, no memory barrier and syncing required, but a bit slower + // You still need to swap after each update or you will risk having conflicting reads and writes + // glFlushMappedBufferRange + /** - * Maps a specified region of the buffer's data store into client memory, processes it using the provided lambda, and then unmaps the buffer + * Maps a specified region of the buffer's data store into client memory, processes it using the provided lambda, and then unmaps the buffer. + * + * If [access] contains the `GL_MAP_PERSISTENT_BIT` flag, the buffer will not be unmapped. * * @param size Specifies the length of the range to be mapped. * @param offset Specifies the starting offset within the buffer of the range to be mapped. * @param block Lambda scope with the mapped buffer passed in - * @return [IllegalArgumentException] if there were errors during the validation, mapping or unmapping, null otherwise + * + * @see Direct memory access */ - open fun map( - size: Long, - offset: Long, - block: (ByteBuffer) -> Unit - ): Throwable? { - if ( - offset < 0 || - size < 0 - ) return IllegalArgumentException("Invalid offset or size parameter offset: $offset size: $size") - - if (!bufferValid(target, access)) - return IllegalArgumentException("Target is not valid. Refer to the table in the documentation") - - if (!bufferBound(target)) - return IllegalArgumentException("Target is zero bound for glMapBufferRange") - - if ( - offset + size > glGetBufferParameteri(target, GL_BUFFER_SIZE) - ) return IllegalArgumentException("Out of bound (is the buffer initialized?) $size + $offset > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}") - - if ( - glGetBufferParameteri(target, GL_BUFFER_MAPPED) - == GL_TRUE - ) return IllegalStateException("Buffer is already mapped, something wrong happened") - - if ( - access and GL_MAP_WRITE_BIT == 0 && - access and GL_MAP_READ_BIT == 0 - ) return IllegalArgumentException("Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set") - - if ( - access and GL_MAP_READ_BIT != 0 && - (access and GL_MAP_INVALIDATE_RANGE_BIT == 0 || - access and GL_MAP_INVALIDATE_BUFFER_BIT == 0 || - access and GL_MAP_UNSYNCHRONIZED_BIT == 0 - ) - ) return IllegalArgumentException("GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set.") - - // Map the buffer into the client's memory + open fun map(size: Long, offset: Long, block: (ByteBuffer) -> Unit = {}): ByteBuffer { + validate() + bind() + + check(offset >= 0 || size >= 0) + { "Invalid offset or size parameter offset: $offset size: $size." } + + check(offset + size <= glGetBufferParameteri(target, GL_BUFFER_SIZE)) + { "Out of bound (is the buffer initialized?) $size + $offset > ${glGetBufferParameteri(target, GL_BUFFER_SIZE)}." } + + check(glGetBufferParameteri(target, GL_BUFFER_MAPPED) == GL_FALSE) + { "Buffer is already mapped." } + + check(access and GL_MAP_WRITE_BIT != 0 || access and GL_MAP_READ_BIT != 0) + { "Neither GL_MAP_READ_BIT nor GL_MAP_WRITE_BIT is set." } + + check((access and GL_MAP_READ_BIT != 0 && + (access and GL_MAP_INVALIDATE_RANGE_BIT != 0 || + access and GL_MAP_INVALIDATE_BUFFER_BIT != 0 || + access and GL_MAP_UNSYNCHRONIZED_BIT != 0)) || + access and GL_MAP_WRITE_BIT != 0 + ) + { "GL_MAP_READ_BIT is set and any of GL_MAP_INVALIDATE_RANGE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT or GL_MAP_UNSYNCHRONIZED_BIT is set." } + val sharedRegion = glMapBufferRange(target, offset, size, access) - ?: return IllegalStateException("Failed to map buffer") + ?: throw IllegalStateException("Failed to map buffer.") - // Update data on the shared buffer block(sharedRegion) - // Release the buffer - if (!glUnmapBuffer(target)) - return IllegalStateException("An unknown error occurred due to GPU memory availability of buffer corruption") + if (access and GL_MAP_PERSISTENT_BIT == 0) { + if (!glUnmapBuffer(target)) + throw IllegalStateException("An unknown error occurred due to GPU memory availability of buffer corruption.") + } - return null + bind(0) + + return sharedRegion } /** - * Uploads the specified data to the buffer starting at the given offset + * Uploads the specified data to the buffer starting at the given offset. * - * This abstract function should be implemented to perform the actual data transfer into the buffer + * This abstract function should be implemented to perform the actual data transfer into the buffer. * * @param data Data to set in memory * @param offset The starting offset within the buffer of the range to be mapped - * @return Error encountered during the mapping process */ - abstract fun upload(data: ByteBuffer, offset: Long): Throwable? + abstract fun upload(data: ByteBuffer, offset: Long) + + private fun validate() { + check(usage in GL_STREAM_DRAW..GL_DYNAMIC_COPY) + { "Usage is invalid, refer to the documentation table." } + + check(target in bindingCheckMappings) + { "Target is invalid, refer to the documentation table." } + + check(access and GL_MAP_COHERENT_BIT == 0 || access and GL_MAP_PERSISTENT_BIT != 0) + { "GL_MAP_COHERENT_BIT requires GL_MAP_PERSISTENT_BIT flag." } + + check(access and GL_MAP_PERSISTENT_BIT == 0 || (access and (GL_MAP_READ_BIT or GL_MAP_WRITE_BIT) != 0)) + { "GL_MAP_PERSISTENT_BIT requires GL_MAP_READ_BIT or GL_MAP_WRITE_BIT." } + } init { - // Special edge case for vertex arrays check(buffers > 0) { "Cannot generate less than one buffer" } if (isVertexArray) glGenVertexArrays(bufferIds) // If there are more than 1 buffer you should expect undefined behavior, this is not the way to do it else glGenBuffers(bufferIds) } + + companion object { + val bindingCheckMappings = mapOf( + GL_ARRAY_BUFFER to GL_ARRAY_BUFFER_BINDING, + GL_ATOMIC_COUNTER_BUFFER to GL_ATOMIC_COUNTER_BUFFER_BINDING, + GL_COPY_READ_BUFFER_BINDING to GL_COPY_READ_BUFFER_BINDING, + GL_COPY_WRITE_BUFFER_BINDING to GL_COPY_WRITE_BUFFER_BINDING, + GL_DISPATCH_INDIRECT_BUFFER to GL_DISPATCH_INDIRECT_BUFFER_BINDING, + GL_DRAW_INDIRECT_BUFFER to GL_DRAW_INDIRECT_BUFFER_BINDING, + GL_ELEMENT_ARRAY_BUFFER to GL_ELEMENT_ARRAY_BUFFER_BINDING, + GL_PIXEL_PACK_BUFFER to GL_PIXEL_PACK_BUFFER_BINDING, + GL_PIXEL_UNPACK_BUFFER to GL_PIXEL_UNPACK_BUFFER_BINDING, + GL_QUERY_BUFFER to GL_QUERY_BUFFER_BINDING, + GL_SHADER_STORAGE_BUFFER to GL_SHADER_STORAGE_BUFFER_BINDING, + GL_TEXTURE_BUFFER to GL_TEXTURE_BUFFER_BINDING, + GL_TRANSFORM_FEEDBACK_BUFFER to GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, + GL_UNIFORM_BUFFER to GL_UNIFORM_BUFFER_BINDING, + ) + + @JvmField + var lastIbo = 0 + var prevIbo = 0 + + fun createPipelineBuffer(bufferTarget: Int) = object : Buffer(buffers = 1) { + override val target: Int = bufferTarget + + override val usage: Int = GL_STATIC_DRAW + override val access: Int = GL_MAP_WRITE_BIT + + override fun bind(id: Int) { + if (bufferTarget != GL_ELEMENT_ARRAY_BUFFER) { + super.bind(id) + return + } + + if (id != 0) prevIbo = lastIbo + super.bind(if (id != 0) id else prevIbo) + } + + override fun upload(data: ByteBuffer, offset: Long) = throw UnsupportedOperationException() + } + } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/DynamicByteBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/DynamicByteBuffer.kt new file mode 100644 index 000000000..02ec17360 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/DynamicByteBuffer.kt @@ -0,0 +1,187 @@ +/* + * 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.buffer + +import org.lwjgl.BufferUtils.createByteBuffer +import org.lwjgl.system.MemoryUtil.memAddress0 +import org.lwjgl.system.MemoryUtil.memCopy +import org.lwjgl.system.MemoryUtil.memPutByte +import org.lwjgl.system.MemoryUtil.memPutFloat +import org.lwjgl.system.MemoryUtil.memPutInt +import java.awt.Color +import java.nio.ByteBuffer + +/** + * Dynamically resizable byte buffer designed for efficient vertex building. + * Automatically grows capacity when needed, and provides + * convenience methods for common data types used in vertex attributes. + * + * @property data The underlying [java.nio.ByteBuffer] storing the vertex data + * @property capacity Current maximum capacity of the buffer in bytes + * @property pointer Base memory address of the buffer + * @property position Current write position in memory address space + */ +class DynamicByteBuffer private constructor(initialCapacity: Int) { + var data: ByteBuffer = createByteBuffer(initialCapacity); private set + var capacity = initialCapacity; private set + + var pointer = memAddress0(data); private set + var position = pointer; private set + + /** + * Gets the total number of bytes written to the buffer since last reset + */ + val bytesPut get() = position - pointer + + /** + * Resets the write position to the beginning of the buffer while maintaining current capacity, + * allowing for buffer reuse without reallocation + */ + fun resetPosition() { + position = pointer + } + + /** + * Writes a single byte value at the current position + * @param value The byte value to write + */ + fun putByte(value: Byte) { + require(1) + memPutByte(position, value) + position += 1 + } + + /** + * Writes a 4-byte integer value at the current position + * @param value The integer value to write + */ + fun putInt(value: Int) { + require(4) + memPutInt(position, value) + position += 4 + } + + /** + * Writes a 4-byte floating point value at the current position + * @param value The double-precision value to write (will be converted to float) + */ + fun putFloat(value: Double) { + require(4) + memPutFloat(position, value.toFloat()) + position += 4 + } + + /** + * Writes a 2-component vector as two consecutive 4-byte floats + * @param x X-axis component + * @param y Y-axis component + */ + fun putVec2(x: Double, y: Double) { + require(8) + memPutFloat(position + 0, x.toFloat()) + memPutFloat(position + 4, y.toFloat()) + position += 8 + } + + /** + * Writes a 3-component vector as three consecutive 4-byte floats + * @param x X-axis component + * @param y Y-axis component + * @param z Z-axis component + */ + fun putVec3(x: Double, y: Double, z: Double) { + require(12) + memPutFloat(position + 0, x.toFloat()) + memPutFloat(position + 4, y.toFloat()) + memPutFloat(position + 8, z.toFloat()) + position += 12 + } + + /** + * Writes a color as four consecutive bytes in RGBA format + * @param color The color to write + */ + fun putColor(color: Color) { + require(4) + memPutByte(position + 0, color.red.toByte()) + memPutByte(position + 1, color.green.toByte()) + memPutByte(position + 2, color.blue.toByte()) + memPutByte(position + 3, color.alpha.toByte()) + position += 4 + } + + /** + * Ensures the buffer has enough remaining space for the requested number of bytes. + * Automatically grows the buffer if insufficient space remains. + * @param size Number of bytes required for the next write operation + */ + fun require(size: Int) { + if (capacity - bytesPut > size) return + grow(capacity * 2) + } + + /** + * Increases buffer capacity while preserving existing data. New capacity must be greater than current. + * @param newCapacity New buffer capacity in bytes + * @throws IllegalArgumentException if newCapacity is not greater than current capacity + */ + fun grow(newCapacity: Int) { + check(newCapacity > capacity) { + "Cannot grow buffer beyond its capacity" + } + + val newBuffer = createByteBuffer(newCapacity) + val newPointer = memAddress0(newBuffer) + val offset = position - pointer + + memCopy(pointer, newPointer, offset) + data = newBuffer + + pointer = newPointer + position = newPointer + offset + capacity = newCapacity + } + + /** + * Reallocates the buffer with exact new capacity, resetting position and discarding existing data + * @param newCapacity New buffer capacity in bytes + */ + fun realloc(newCapacity: Int) { + if (newCapacity == capacity) { + resetPosition() + return + } + + val newBuffer = createByteBuffer(newCapacity) + val newPointer = memAddress0(newBuffer) + + data = newBuffer + pointer = newPointer + position = newPointer + capacity = newCapacity + } + + companion object { + /** + * Creates a new DynamicByteBuffer with specified initial capacity + * @param initialCapacity Starting buffer size in bytes + */ + fun dynamicByteBuffer(initialCapacity: Int) = + DynamicByteBuffer(initialCapacity) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt deleted file mode 100644 index 3bb15197e..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/IRenderContext.kt +++ /dev/null @@ -1,52 +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.graphics.buffer - -import java.awt.Color - -interface IRenderContext { - fun vec3(x: Double, y: Double, z: Double): IRenderContext - fun vec2(x: Double, y: Double): IRenderContext - - fun vec3m(x: Double, y: Double, z: Double): IRenderContext - fun vec2m(x: Double, y: Double): IRenderContext - - fun float(v: Double): IRenderContext - fun color(color: Color): IRenderContext - fun end(): Int - - fun putLine(vertex1: Int, vertex2: Int) - fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) - fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) - - fun render() - fun upload() - fun clear() - - fun immediateDraw() { - upload() - render() - clear() - } - - fun grow(amount: Int) - - fun use(block: IRenderContext.() -> Unit) { - block() - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt deleted file mode 100644 index c8be117bf..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/VertexPipeline.kt +++ /dev/null @@ -1,209 +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.graphics.buffer - -import com.lambda.Lambda.LOG -import com.lambda.graphics.buffer.vertex.ElementBuffer -import com.lambda.graphics.buffer.vertex.VertexArray -import com.lambda.graphics.buffer.vertex.VertexBuffer -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.gl.Matrices -import com.lambda.graphics.gl.Memory -import com.lambda.graphics.gl.Memory.address -import com.lambda.graphics.gl.Memory.byteBuffer -import com.lambda.graphics.gl.Memory.int -import com.lambda.graphics.gl.Memory.vector2f -import com.lambda.graphics.gl.Memory.vector3f -import com.lambda.graphics.gl.kibibyte -import org.joml.Vector4d -import org.lwjgl.opengl.GL15C -import org.lwjgl.opengl.GL20C.* -import java.awt.Color - -class VertexPipeline( - private val mode: VertexMode, - attributes: VertexAttrib.Group, - usage: Int = GL_DYNAMIC_DRAW -) : IRenderContext { - private val stride = attributes.stride - private val size = stride * mode.indicesCount - - private val vao = VertexArray() - private val vbo = VertexBuffer(mode, attributes, usage) - private val ebo = ElementBuffer(mode, usage) - - private var vertices = byteBuffer(size * 1.kibibyte) - private var verticesPointer = address(vertices) - private var verticesPosition = verticesPointer - - private var indices = byteBuffer(mode.indicesCount * 2.kibibyte) - private var indicesPointer = address(indices) - private var indicesCount = 0 - private var uploadedIndices = 0 - - private var vertexIndex = 0 - - override fun vec3(x: Double, y: Double, z: Double): VertexPipeline { - verticesPosition += vector3f(verticesPosition, x, y, z) - return this - } - - override fun vec2(x: Double, y: Double): VertexPipeline { - verticesPosition += vector2f(verticesPosition, x, y) - return this - } - - override fun vec3m(x: Double, y: Double, z: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) - vec3(vec.x, vec.y, vec.z) - } ?: vec3(x, y, z) - - return this - } - - override fun vec2m(x: Double, y: Double): IRenderContext { - Matrices.vertexTransformer?.let { mat -> - val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) - vec2(vec.x, vec.y) - } ?: vec2(x, y) - return this - } - - override fun float(v: Double): VertexPipeline { - verticesPosition += Memory.float(verticesPosition, v) - return this - } - - override fun color(color: Color): VertexPipeline { - verticesPosition += Memory.color(verticesPosition, color) - return this - } - - override fun end() = vertexIndex++ - - override fun putLine(vertex1: Int, vertex2: Int) { - growIndices(2) - val p = indicesPointer + indicesCount * 4L - - int(p + 0, vertex1) - int(p + 4, vertex2) - indicesCount += 2 - } - - override fun putTriangle(vertex1: Int, vertex2: Int, vertex3: Int) { - growIndices(3) - val position = indicesPointer + indicesCount * 4L - - int(position + 0, vertex1) - int(position + 4, vertex2) - int(position + 8, vertex3) - indicesCount += 3 - } - - override fun putQuad(vertex1: Int, vertex2: Int, vertex3: Int, vertex4: Int) { - growIndices(6) - val position = indicesPointer + indicesCount * 4L - - int(position + 0, vertex1) - int(position + 4, vertex2) - int(position + 8, vertex3) - int(position + 12, vertex3) - int(position + 16, vertex4) - int(position + 20, vertex1) - indicesCount += 6 - } - - override fun grow(amount: Int) { - val requiredCapacity = (vertexIndex + amount + 1) * size - if (requiredCapacity < vertices.capacity()) return - - val offset = verticesPosition - verticesPointer - val newSize = vertices.capacity().let { it * 2 + it % size } - - val newVertices = byteBuffer(newSize) - Memory.copy(address(vertices), address(newVertices), offset) - - vertices = newVertices - verticesPointer = address(vertices) - verticesPosition = verticesPointer + offset - } - - private fun growIndices(amount: Int) { - val requiredCapacity = (indicesCount + amount) * 4 - if (requiredCapacity < indices.capacity()) return - - val newSize = indices.capacity().let { it * 2 + it % (mode.indicesCount * 4) } - val newIndices = byteBuffer(newSize) - - Memory.copy(address(indices), address(newIndices), indicesCount * 4L) - - indices = newIndices - indicesPointer = address(indices) - } - - override fun render() { - if (uploadedIndices <= 0) return - - vao.bind() - glDrawElements(mode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) - vao.bind(0) - } - - override fun upload() { - if (indicesCount <= 0) return - - val vboData = vertices.limit((verticesPosition - verticesPointer).toInt()) - val eboData = indices.limit(indicesCount * 4) - - vbo.upload(vboData, 0)?.let(LOG::error) - ebo.upload(eboData, 0)?.let(LOG::error) - - uploadedIndices = indicesCount - } - - override fun clear() { - verticesPosition = verticesPointer - vertexIndex = 0 - indicesCount = 0 - uploadedIndices = 0 - } - - init { - // All the buffers have been generated, all we have to - // do now it bind them correctly and populate them - vao.bind() - vbo.bind() - ebo.bind() - - // Populate the buffers - attributes.attributes - .foldIndexed(0L) { index, pointer, attrib -> - glEnableVertexAttribArray(index) - glVertexAttribPointer(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) - - pointer + attrib.size // I'm not sure why there's no accumulator indexed iterator, this cost me many hours - } - - // Unbind everything to avoid accidental modification - vao.bind(0) - vbo.bind(0) - ebo.bind(0) - } -} 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 d340b524c..468225cb7 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 @@ -18,13 +18,10 @@ package com.lambda.graphics.buffer.frame import com.lambda.Lambda.mc -import com.lambda.graphics.RenderMain.projectionMatrix -import com.lambda.graphics.buffer.VertexPipeline +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.gl.Matrices import com.lambda.graphics.texture.TextureUtils -import org.joml.Matrix4f import org.lwjgl.opengl.GL12C.GL_CLAMP_TO_EDGE import org.lwjgl.opengl.GL30C.* import java.nio.IntBuffer diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/ScreenFrameBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/frame/ScreenFrameBuffer.kt deleted file mode 100644 index 466473a81..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/frame/ScreenFrameBuffer.kt +++ /dev/null @@ -1,76 +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.graphics.buffer.frame - -import com.lambda.Lambda.mc -import com.lambda.graphics.RenderMain -import com.lambda.graphics.gl.GlStateUtils.withBlendFunc -import com.lambda.graphics.shader.Shader -import com.lambda.util.math.Vec2d -import org.lwjgl.opengl.GL11C.* - -class ScreenFrameBuffer(depth: Boolean = false) : FrameBuffer(depth = depth) { - fun read( - shader: Shader, - pos1: Vec2d = Vec2d.ZERO, - pos2: Vec2d = RenderMain.screenSize, - shaderBlock: (Shader) -> Unit = {} - ): ScreenFrameBuffer { - bindColorTexture() - - shader.use() - shaderBlock(shader) - - pipeline.use { - grow(4) - - val uv1 = pos1 / RenderMain.screenSize - val uv2 = pos2 / RenderMain.screenSize - - putQuad( - vec2(pos1.x, pos1.y).vec2(uv1.x, 1.0 - uv1.y).end(), - vec2(pos1.x, pos2.y).vec2(uv1.x, 1.0 - uv2.y).end(), - vec2(pos2.x, pos2.y).vec2(uv2.x, 1.0 - uv2.y).end(), - vec2(pos2.x, pos1.y).vec2(uv2.x, 1.0 - uv1.y).end() - ) - } - - withBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) { - pipeline.upload() - pipeline.render() - pipeline.clear() - } - - return this - } - - override fun write(block: () -> Unit): ScreenFrameBuffer { - width = mc.window.framebufferWidth - height = mc.window.framebufferHeight - - return super.write { - withBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, block) - } as ScreenFrameBuffer - } - - override fun bindColorTexture(slot: Int) = - super.bindColorTexture(slot) as ScreenFrameBuffer - - override fun bindDepthTexture(slot: Int) = - super.bindDepthTexture(slot) as ScreenFrameBuffer -} \ 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 c8e590125..179f9477e 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 @@ -20,6 +20,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 @@ -31,28 +32,38 @@ import java.nio.ByteBuffer * Functions that perform an upload operation, a pixel unpack, will use the buffer object bound to the target GL_PIXEL_UNPACK_BUFFER. * If a buffer is bound, then the pointer value that those functions take is not a pointer, but an offset from the beginning of that buffer. * + * Asynchronous should only be used for medium-size blocks of data being updated one time or less per frame + * Persistent should only be used for streaming operations with large blocks of data updated once or more per frame + * * @property texture The [Texture] instance to use * @property asynchronous Whether to use 2 buffers or not - * @property bufferMapping Whether to map a block in memory to upload or not + * @property persistent Whether to map a block in memory to upload or not * - * @see Reference + * @see Pixel buffer object */ class PixelBuffer( private val texture: Texture, private val asynchronous: Boolean = false, - private val bufferMapping: Boolean = false, + private val persistent: Boolean = false, ) : Buffer(buffers = asynchronous.toInt() + 1) { - override val usage: Int = GL_STATIC_DRAW - override val target: Int = GL_PIXEL_UNPACK_BUFFER - override val access: Int = GL_MAP_WRITE_BIT + override val usage = GL_STATIC_DRAW + override val target = GL_PIXEL_UNPACK_BUFFER + override val access = + if (persistent) GL_MAP_WRITE_BIT or GL_DYNAMIC_STORAGE_BIT or GL_MAP_PERSISTENT_BIT or GL_MAP_COHERENT_BIT + else GL_MAP_WRITE_BIT or GL_DYNAMIC_STORAGE_BIT private val channels = channelMapping[texture.format] ?: throw IllegalArgumentException("Invalid image format, expected OpenGL format, got ${texture.format} instead") private val size = texture.width * texture.height * channels * 1L +// private var sharedRegion: ByteBuffer? = null + + override fun upload(data: ByteBuffer, offset: Long) { + if (!asynchronous) { + glBindTexture(GL_TEXTURE_2D, texture.id) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, texture.format, GL_UNSIGNED_BYTE, data) + glBindTexture(GL_TEXTURE_2D, 0) + return + } - override fun upload( - data: ByteBuffer, - offset: Long, - ): Throwable? { bind() glBindTexture(GL_TEXTURE_2D, texture.id) @@ -72,13 +83,11 @@ class PixelBuffer( swap() bind() - val error = - if (bufferMapping) map(size, offset, data::putTo) - else update(data, offset) +// if (persistent) data.putTo(sharedRegion) +// else update(data, offset) + update(data, offset) bind(0) - - return error } init { @@ -93,6 +102,10 @@ class PixelBuffer( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) storage(size) + +// bind() +// sharedRegion = if (persistent) map(size, 0) else null +// bind(0) } companion object { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt deleted file mode 100644 index 116c16a85..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/ElementBuffer.kt +++ /dev/null @@ -1,54 +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.graphics.buffer.vertex - -import com.lambda.graphics.buffer.Buffer -import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.gl.kibibyte -import org.lwjgl.opengl.GL30C.* -import java.nio.ByteBuffer - -class ElementBuffer( - mode: VertexMode, - override val usage: Int = GL_DYNAMIC_DRAW, -) : Buffer(buffers = 1) { - override val target: Int = GL_ELEMENT_ARRAY_BUFFER - override val access: Int = GL_MAP_WRITE_BIT - - override fun upload( - data: ByteBuffer, - offset: Long, - ): Throwable? = allocate(data) - - override fun bind(id: Int) { - if (id != 0) prevIbo = lastIbo - val target = if (id != 0) id else prevIbo - - super.bind(target) - } - - init { - allocate(mode.indicesCount * 2L.kibibyte) - } - - companion object { - @JvmField - var lastIbo = 0 - var prevIbo = 0 - } -} 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 5362c6ad4..f494bdab6 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 @@ -18,27 +18,39 @@ 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 net.minecraft.client.render.BufferRenderer import org.lwjgl.opengl.GL30C.* +import org.lwjgl.opengl.GL32C.glDrawElementsBaseVertex import java.nio.ByteBuffer -class VertexArray : Buffer(isVertexArray = true) { +class VertexArray( + private val vertexMode: VertexMode, + private val attributes: VertexAttrib.Group +) : Buffer(isVertexArray = true) { override val usage: Int = -1 override val target: Int = -1 override val access: Int = -1 - override fun map( - size: Long, - offset: Long, - block: (ByteBuffer) -> Unit - ): Throwable = throw UnsupportedOperationException("Cannot map a vertex array object to memory") - - override fun upload( - data: ByteBuffer, - offset: Long, - ): Throwable = throw UnsupportedOperationException("Data cannot be uploaded to a vertex array object") + fun render( + indicesSize: Long, + indicesPointer: Long, + verticesOffset: Int + ) { + bind() + glDrawElementsBaseVertex( + vertexMode.mode, + indicesSize.toInt() / UInt.SIZE_BYTES, + GL_UNSIGNED_INT, + indicesPointer, + verticesOffset / attributes.stride + ) + bind(0) + } - override fun allocate(size: Long) = throw UnsupportedOperationException("Cannot grow a vertex array object") + override fun map(size: Long, offset: Long, block: (ByteBuffer) -> Unit) = throw UnsupportedOperationException() + override fun upload(data: ByteBuffer, offset: Long) = throw UnsupportedOperationException() override fun bind(id: Int) { glBindVertexArray(id); BufferRenderer.currentVertexBuffer = null diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt deleted file mode 100644 index 94358fb09..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/VertexBuffer.kt +++ /dev/null @@ -1,43 +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.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.gl.kibibyte -import org.lwjgl.opengl.GL30C.* -import java.nio.ByteBuffer - -class VertexBuffer( - mode: VertexMode, - attributes: VertexAttrib.Group, - override val usage: Int = GL_DYNAMIC_DRAW -) : Buffer(buffers = 1) { - override val target: Int = GL_ARRAY_BUFFER - override val access: Int = GL_MAP_WRITE_BIT - - override fun upload( - data: ByteBuffer, - offset: Long, - ): Throwable? = allocate(data) - - init { - allocate(attributes.stride * mode.indicesCount * 1L.kibibyte) - } -} 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 3ece85693..bac651b5f 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 @@ -17,63 +17,92 @@ package com.lambda.graphics.buffer.vertex.attributes -import com.lambda.graphics.gl.GLObject 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 -enum class VertexAttrib( - val componentCount: Int, +sealed class VertexAttrib( + private val componentCount: Int, componentSize: Int, - val normalized: Boolean, - override val gl: Int -) : GLObject { - Float(1, 4, false, GL_FLOAT), - Vec2(2, 4, false, GL_FLOAT), - Vec3(3, 4, false, GL_FLOAT), - Color(4, 1, true, GL_UNSIGNED_BYTE); + 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) { + companion object : Float() + } + + open class Vec2( + normalized: Boolean = false, single: Boolean = false + ) : VertexAttrib(2, 4, normalized, single, GL_FLOAT) { + companion object : Vec2() + } + + open class Vec3( + normalized: Boolean = false, single: Boolean = false + ) : VertexAttrib(3, 4, normalized, single, GL_FLOAT) { + companion object : Vec3() + } + + open class Color( + normalized: Boolean = true, single: Boolean = false + ) : VertexAttrib(4, 1, normalized, single, GL_UNSIGNED_BYTE) { + companion object : Color() + } val size = componentCount * componentSize - enum class Group(vararg val attributes: VertexAttrib) { - POS_UV(Vec2, Vec2), + fun link(index: Int, pointer: Long, stride: Int) { + glEnableVertexAttribArray(index) + glVertexAttribPointer(index, componentCount, type, normalized, stride, pointer) + if (single) glVertexAttribDivisor(index, 1) + } + + @Suppress("ClassName") + open class Group(vararg val attributes: VertexAttrib) { + object POS_UV : Group( + Vec2, Vec2 + ) // GUI - FONT( - Vec3, // pos - Vec2, // uv - Color - ), - - RECT_FILLED( - Vec3, // pos - Vec2, // uv - Color - ), - - RECT_OUTLINE( - Vec3, // pos - Vec2, // uv - Float, // alpha - Color - ), + object FONT : Group( + Vec3, Vec2, Color + ) + + object RECT : Group( + Vec3, Vec2, Color + ) + + object RECT_OUTLINE : Group( + Vec3, Vec2, Float, Color + ) // WORLD - DYNAMIC_RENDERER( - Vec3, // prev pos - Vec3, // pos - Color - ), - - STATIC_RENDERER( - Vec3, // pos - Color - ), - - PARTICLE(Vec3, - Vec2, // pos - Color - ); - - val stride = attributes.sumOf { attribute -> attribute.size } + object DYNAMIC_RENDERER : Group( + Vec3, Vec3, Color + ) + + object STATIC_RENDERER : Group( + Vec3, Color + ) + + object PARTICLE : Group( + Vec3, Vec2, Color + ) + + val stride = attributes.sumOf { attribute -> + attribute.size + } + + fun link() { + attributes.foldIndexed(0L) { index, pointer, attrib -> + attrib.link(index, pointer, stride) + pointer + attrib.size + } + } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt index e0c6c45df..668c97cd2 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vertex/attributes/VertexMode.kt @@ -17,11 +17,10 @@ package com.lambda.graphics.buffer.vertex.attributes -import com.lambda.graphics.gl.GLObject import org.lwjgl.opengl.GL11C.GL_LINES import org.lwjgl.opengl.GL11C.GL_TRIANGLES -enum class VertexMode(val indicesCount: Int, override val gl: Int) : GLObject { - LINES(2, GL_LINES), - TRIANGLES(3, GL_TRIANGLES) +enum class VertexMode(val mode: Int) { + LINES(GL_LINES), + TRIANGLES(GL_TRIANGLES) } diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt deleted file mode 100644 index c1a1636c9..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Buffers.kt +++ /dev/null @@ -1,63 +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.graphics.gl - -import org.lwjgl.opengl.GL44.* - -/** - * Map of valid buffer binding target to their respective binding check parameter - * using [bufferBound] - */ -val bindingCheckMappings = mapOf( - GL_ARRAY_BUFFER to GL_ARRAY_BUFFER_BINDING, - GL_ATOMIC_COUNTER_BUFFER to GL_ATOMIC_COUNTER_BUFFER_BINDING, - GL_COPY_READ_BUFFER_BINDING to GL_COPY_READ_BUFFER_BINDING, - GL_COPY_WRITE_BUFFER_BINDING to GL_COPY_WRITE_BUFFER_BINDING, - GL_DISPATCH_INDIRECT_BUFFER to GL_DISPATCH_INDIRECT_BUFFER_BINDING, - GL_DRAW_INDIRECT_BUFFER to GL_DRAW_INDIRECT_BUFFER_BINDING, - GL_ELEMENT_ARRAY_BUFFER to GL_ELEMENT_ARRAY_BUFFER_BINDING, - GL_PIXEL_PACK_BUFFER to GL_PIXEL_PACK_BUFFER_BINDING, - GL_PIXEL_UNPACK_BUFFER to GL_PIXEL_UNPACK_BUFFER_BINDING, - GL_QUERY_BUFFER to GL_QUERY_BUFFER_BINDING, - GL_SHADER_STORAGE_BUFFER to GL_SHADER_STORAGE_BUFFER_BINDING, - GL_TEXTURE_BUFFER to GL_TEXTURE_BUFFER_BINDING, - GL_TRANSFORM_FEEDBACK_BUFFER to GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, - GL_UNIFORM_BUFFER to GL_UNIFORM_BUFFER_BINDING, -) - -/** - * Returns whether the buffer target is valid - */ -fun bufferValid(target: Int, access: Int): Boolean = - target in bindingCheckMappings && - // If access contains GL_MAP_COHERENT_BIT, it must also contain GL_MAP_PERSISTENT_BIT. - (access and GL_MAP_COHERENT_BIT == 0 || access and GL_MAP_PERSISTENT_BIT != 0) && - // If access contains GL_MAP_PERSISTENT_BIT, it must also contain at least one of GL_MAP_READ_BIT or GL_MAP_WRITE_BIT. - (access and GL_MAP_PERSISTENT_BIT == 0 || (access and (GL_MAP_READ_BIT or GL_MAP_WRITE_BIT) != 0)) - -/** - * Returns whether the provided buffer target is bound - */ -fun bufferBound(target: Int): Boolean = - IntArray(1) - .apply { glGetIntegerv(bindingCheckMappings.getValue(target), this) }[0] != GL_FALSE - -/** - * Returns whether the provided buffer usage is valid - */ -fun bufferUsageValid(usage: Int) = usage >= GL_STREAM_DRAW && usage <= GL_DYNAMIC_DRAW diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt index c99fe6d68..4607d3f02 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Memory.kt @@ -17,86 +17,8 @@ package com.lambda.graphics.gl -import org.lwjgl.BufferUtils -import org.lwjgl.system.MemoryUtil -import org.lwjgl.system.MemoryUtil.memPutByte -import org.lwjgl.system.MemoryUtil.memPutFloat -import org.lwjgl.system.MemoryUtil.memPutInt -import java.awt.Color -import java.nio.Buffer import java.nio.ByteBuffer -object Memory { - /** - * Puts a float for each axis at the current buffer position - */ - fun vector2f(address: Long, x: Double, y: Double): Int { - float(address + 0, x) - float(address + 4, y) - return 8 - } - - /** - * Puts a float for each axis at the current buffer position - */ - fun vector3f(address: Long, x: Double, y: Double, z: Double): Int { - float(address + 0, x) - float(address + 4, y) - float(address + 8, z) - return 12 - } - - /** - * Puts a byte for each color channel at the current buffer position - */ - fun color(address: Long, color: Color): Int { - byte(address + 0, color.red.toByte()) - byte(address + 1, color.green.toByte()) - byte(address + 2, color.blue.toByte()) - byte(address + 3, color.alpha.toByte()) - return 4 - } - - /** - * Puts a byte at the current buffer position - */ - fun byte(address: Long, value: Byte): Int { - memPutByte(address, value) - return 1 - } - - /** - * Puts an integer at the current buffer position - */ - fun int(address: Long, value: Int): Int { - memPutInt(address, value) - return 4 - } - - /** - * Puts a float at the current buffer position - */ - fun float(address: Long, value: Double): Int { - memPutFloat(address, value.toFloat()) - return 4 - } - - /** - * Returns the address of the first element within the buffer - */ - fun address(buffer: Buffer): Long = MemoryUtil.memAddress0(buffer) - - /** - * Copies [bytes] bytes from [src] to [dst] - */ - fun copy(src: Long, dst: Long, bytes: Long) = MemoryUtil.memCopy(src, dst, bytes) - - /** - * Creates a new buffer of [cap] bytes - */ - fun byteBuffer(cap: Int) = BufferUtils.createByteBuffer(cap) -} - val Int.kilobyte get() = this * 1000 val Int.megabyte get() = this * 1000 * 1000 val Int.gigabyte get() = this * 1000 * 1000 * 1000 @@ -113,4 +35,4 @@ val Long.kibibyte get() = this * 1024 val Long.mebibyte get() = this * 1024 * 1024 val Long.gibibyte get() = this * 1024 * 1024 * 1024 -fun ByteBuffer.putTo(dst: ByteBuffer) { dst.put(this) } +fun ByteBuffer.putTo(dst: ByteBuffer?) { dst?.put(this) } diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt new file mode 100644 index 000000000..ee8d3fbfc --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt @@ -0,0 +1,112 @@ +/* + * 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.pipeline + +import com.lambda.graphics.buffer.Buffer.Companion.createPipelineBuffer +import com.lambda.graphics.buffer.DynamicByteBuffer.Companion.dynamicByteBuffer +import com.lambda.graphics.gl.kibibyte +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 +) { + /** + * Resizable byte buffer that stores all data used last frame + */ + val byteBuffer = dynamicByteBuffer(stride * 1.kibibyte) + + /** + * Data that has passed through the buffer within previous frame + */ + private val snapshot = dynamicByteBuffer(1) + private var cacheSize = 0 + + /** + * Represents a gpu-side buffer + */ + private val glBuffer = createPipelineBuffer(target) + private var glSize = 0 + + var uploadOffset = 0 + + fun upload() { + val dataStart = byteBuffer.pointer + uploadOffset + val dataCount = byteBuffer.bytesPut - uploadOffset + if (dataCount <= 0) return + + if (glSize != byteBuffer.capacity) { + glSize = byteBuffer.capacity + + glBuffer.allocate(byteBuffer.data) + snapshot.realloc(byteBuffer.capacity) + cacheSize = 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 + } + + glBuffer.update(uploadOffset.toLong(), dataCount, dataStart) + } + + fun end() { + uploadOffset = byteBuffer.bytesPut.toInt() + } + + fun sync() { + memCopy(byteBuffer.pointer, snapshot.pointer, byteBuffer.bytesPut) + cacheSize = byteBuffer.bytesPut.toInt() + + byteBuffer.resetPosition() + uploadOffset = 0 + } + + fun clear() { + snapshot.resetPosition() + byteBuffer.resetPosition() + uploadOffset = 0 + cacheSize = 0 + } + + fun use(block: () -> Unit) { + glBuffer.bind() + block() + glBuffer.bind(0) + } + + 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 + } + } + 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 new file mode 100644 index 000000000..aec8b8c12 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt @@ -0,0 +1,267 @@ +/* + * 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.pipeline + +import com.lambda.graphics.buffer.DynamicByteBuffer +import com.lambda.graphics.gl.Matrices +import org.joml.Vector4d + +/** + * A builder class for constructing vertex buffer objects (VBOs) with associated vertex attributes and indices. + * Provides a DSL-like syntax for defining vertices and their attributes in a type-safe manner. + */ +@Suppress("DuplicatedCode") +class VertexBuilder( + private val direct: VertexPipeline? = null +) { + private val vertices by lazy { mutableListOf() } + private val indices by lazy { mutableListOf() } + + private var verticesCounter = 0 + + /** + * Adds multiple indices to the index buffer + * @param indices The indices to add to the element array buffer + */ + fun build(vararg indices: Int) { + direct?.let { + indices.forEach { i -> + it.indices.putInt(i) + } + + return + } + + indices.forEach { this.indices += it } + } + + /** + * Adds rectangle indices (2 triangles) using 4 vertices + */ + fun buildQuad( + index1: Int, index2: Int, index3: Int, index4: Int + ) { + direct?.let { + it.indices.putInt(index1) + it.indices.putInt(index2) + it.indices.putInt(index3) + it.indices.putInt(index3) + it.indices.putInt(index4) + it.indices.putInt(index1) + return + } + + this.indices += index1 + this.indices += index2 + this.indices += index3 + this.indices += index3 + this.indices += index4 + this.indices += index1 + } + + /** + * Adds line indices between 2 vertices + */ + fun buildLine( + index1: Int, index2: Int, + ) { + direct?.let { + it.indices.putInt(index1) + it.indices.putInt(index2) + return + } + + this.indices += index1 + this.indices += index2 + } + + /** + * Creates a collection of indices from varargs + * @return List of provided indices for element array buffer + */ + fun collect(vararg indices: Int) = + indices + + fun use(block: VertexBuilder.() -> Unit) { + apply(block) + } + + /** + * Creates a new vertex with specified attributes + * @param block Configuration lambda for defining vertex attributes + * @return Index of the created vertex in the vertex array + */ + fun vertex(block: Vertex.() -> Unit): Int { + Vertex(this).apply(block) + return verticesCounter++ + } + + /** + * Uploads constructed vertex data to a rendering pipeline + * @param pipeline The target pipeline for vertex and index data + */ + fun uploadTo(pipeline: VertexPipeline) { + check(direct == null) { + "Builder is already associated with a rendering pipeline. Cannot upload data again." + } + + /* Upload vertices */ + vertices.forEach { attribute -> + attribute.upload(pipeline.vertices) + } + + /* Upload indices */ + indices.forEach { + pipeline.indices.putInt(it) + } + } + + /** + * Represents a single vertex with multiple attributes. + * Attributes are stored in the order they're declared, which must match shader layout. + */ + class Vertex(private val builder: VertexBuilder) { + + /** + * Adds a single-precision floating point attribute + * @param value The scalar value to add + */ + fun float(value: Double): Vertex { + builder.direct?.let { + it.vertices.putFloat(value) + return this + } + + builder.vertices.add(Attribute.Float(value)) + return this + } + + /** + * Adds a 2-component vector attribute + * @param x X-axis component + * @param y Y-axis component + */ + fun vec2(x: Double, y: Double): Vertex { + builder.direct?.let { + it.vertices.putVec2(x, y) + return this + } + + builder.vertices.add(Attribute.Vec2(x, y)) + return this + } + + /** + * Adds a matrix-transformed 2-component vector + * @param x X-axis component + * @param y Y-axis component + */ + fun vec2m(x: Double, y: Double) = + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, 0.0, 1.0).apply(mat::transform) + vec2(vec.x, vec.y) + } ?: vec2(x, y) + + /** + * Adds a 3-component vector attribute + * @param x X-axis component + * @param y Y-axis component + * @param z Z-axis component + */ + fun vec3(x: Double, y: Double, z: Double): Vertex { + builder.direct?.let { + it.vertices.putVec3(x, y, z) + return this + } + + builder.vertices.add(Attribute.Vec3(x, y, z)) + return this + } + + /** + * Adds a matrix-transformed 3-component vector attribute + * @param x X-axis component + * @param y Y-axis component + * @param z Z-axis component (defaults to 0.0) + */ + fun vec3m(x: Double, y: Double, z: Double = 0.0) = + Matrices.vertexTransformer?.let { mat -> + val vec = Vector4d(x, y, z, 1.0).apply(mat::transform) + vec3(vec.x, vec.y, vec.z) + } ?: vec3(x, y, z) + + /** + * Adds a color attribute in RGBA format + * @param color Color value using AWT Color class + */ + fun color(color: java.awt.Color): Vertex { + builder.direct?.let { + it.vertices.putColor(color) + return this + } + + builder.vertices.add(Attribute.Color(color)) + return this + } + } + + /** + * Sealed hierarchy representing different vertex attribute types. + * Each attribute knows how to upload itself to a [DynamicByteBuffer]. + * + * @property upload Lambda that handles writing the attribute data to a buffer + */ + sealed class Attribute( + val upload: DynamicByteBuffer.() -> Unit + ) { + /** + * Single-precision floating point attribute + * @property value Scalar floating point value + */ + data class Float( + var value: Double + ) : Attribute({ putFloat(value) }) + + /** + * 2-component vector attribute + * @property x X-axis component + * @property y Y-axis component + */ + data class Vec2( + var x: Double, var y: Double + ) : Attribute({ putVec2(x, y) }) + + /** + * 3-component vector attribute + * @property x X-axis component + * @property y Y-axis component + * @property z Z-axis component + */ + data class Vec3( + var x: Double, var y: Double, var z: Double + ) : Attribute({ putVec3(x, y, z) }) + + /** + * Color attribute in RGBA format + * @property color Color value using AWT Color class + */ + data class Color( + 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 new file mode 100644 index 000000000..31c1c3bf5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/pipeline/VertexPipeline.kt @@ -0,0 +1,152 @@ +/* + * 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.pipeline + +import com.lambda.graphics.buffer.vertex.VertexArray +import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib +import com.lambda.graphics.buffer.vertex.attributes.VertexMode +import org.lwjgl.opengl.GL32C.* + +/** + * A GPU vertex processing pipeline that manages Vertex Array Objects (VAO) and associated buffers. + * Handles vertex data storage, attribute binding, and rendering operations. + * + * @property vertexMode The primitive type used for rendering (e.g., triangles, lines) + * @property attributes Group of vertex attributes defining the data layout + * + * @see VertexMode for vertex configuration + * @see VertexAttrib.Group for attribute configuration + * @see PersistentBuffer for buffer management + */ +class VertexPipeline( + private val vertexMode: VertexMode, + 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) + + /** + * Direct access to the vertex buffer's underlying byte storage + */ + val vertices get() = vbo.byteBuffer + + /** + * Direct access to the index buffer's underlying byte storage + */ + val indices get() = ibo.byteBuffer + + /** + * 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 + ) + + /** + * Builds and renders data constructed by [VertexBuilder] + * + * It is recommended to use this method for direct data transfer + * to avoid the overhead caused by [VertexBuilder] + * + * Uploads buffered data to GPU memory + * + * Note: only one vertex builder could be built and uploaded within 1 batch + */ + fun immediate(block: VertexBuilder.() -> Unit) { + VertexBuilder(this).apply(block) + uploadInternal(); render(); clear() + } + + /** + * Builds data constructed by [VertexBuilder] + * + * It is recommended to use this method for direct data transfer + * to avoid the overhead caused by [VertexBuilder] + * + * Uploads buffered data to GPU memory + * + * Note: only one vertex builder could be built and uploaded within 1 batch + */ + fun upload(block: VertexBuilder.() -> Unit) { + VertexBuilder(this).apply(block) + uploadInternal() + } + + /** + * Builds data constructed by [VertexBuilder] + * + * Uploads buffered data to GPU memory + * + * Note: only one vertex builder could be built and uploaded within 1 batch + */ + fun upload(builder: VertexBuilder) { + builder.uploadTo(this) + uploadInternal() + } + + /** + * Creates a [VertexBuilder] + * + * Note: only one vertex builder could be built and uploaded within 1 batch + */ + fun build(block: VertexBuilder.() -> Unit = {}) = + VertexBuilder().apply(block) + + /** + * Uploads buffered data to GPU memory + */ + private fun uploadInternal() { + vbo.upload() + ibo.upload() + } + + /** + * Finalizes the current draw batch and prepares for new data + */ + fun end() { + vbo.end() + ibo.end() + } + + /** + * Synchronizes buffer states between frames + * Should be called at the end of each frame + */ + fun sync() { + vbo.sync() + ibo.sync() + } + + /** + * Resets both vertex and index buffers + */ + fun clear() { + vbo.clear() + ibo.clear() + } + + init { + vao.bind() + vbo.use(attributes::link) + vao.bind(0) + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/pipeline/ScissorAdapter.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/ScissorAdapter.kt similarity index 96% rename from common/src/main/kotlin/com/lambda/graphics/pipeline/ScissorAdapter.kt rename to common/src/main/kotlin/com/lambda/graphics/renderer/ScissorAdapter.kt index dc87664b9..72ae91df8 100644 --- a/common/src/main/kotlin/com/lambda/graphics/pipeline/ScissorAdapter.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/ScissorAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.graphics.pipeline +package com.lambda.graphics.renderer import com.lambda.Lambda.mc import com.lambda.graphics.RenderMain diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 98ce0937a..fb19d147b 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -118,7 +118,7 @@ class ChunkedESP private constructor( } suspend fun rebuild() { - val newRenderer = awaitMainThread { StaticESPRenderer(false) } + val newRenderer = awaitMainThread { StaticESPRenderer() } iterateChunk { x, y, z -> owner.update(newRenderer, chunk.world, x, y, z) diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ESPRenderer.kt index 3087e16c0..059b42692 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ESPRenderer.kt @@ -18,10 +18,11 @@ package com.lambda.graphics.renderer.esp import com.lambda.Lambda -import com.lambda.graphics.buffer.VertexPipeline +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.gl.GlStateUtils +import com.lambda.graphics.pipeline.VertexBuilder import com.lambda.graphics.shader.Shader import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.module.modules.client.RenderSettings @@ -29,20 +30,27 @@ import com.lambda.util.extension.partialTicks open class ESPRenderer(tickedMode: Boolean) { val shader: Shader + val faces: VertexPipeline + var faceBuilder: VertexBuilder + val outlines: VertexPipeline + var outlineBuilder: VertexBuilder init { val mode = if (tickedMode) dynamicMode else staticMode - shader = mode.first + faces = VertexPipeline(VertexMode.TRIANGLES, mode.second) + faceBuilder = faces.build() + outlines = VertexPipeline(VertexMode.LINES, mode.second) + outlineBuilder = outlines.build() } - open fun upload() { - faces.upload() - outlines.upload() + fun upload() { + faces.upload(faceBuilder) + outlines.upload(outlineBuilder) } fun render() { @@ -54,19 +62,19 @@ open class ESPRenderer(tickedMode: Boolean) { GlStateUtils.withLineWidth(RenderSettings.outlineWidth, outlines::render) } - open fun clear() { + fun clear() { faces.clear() outlines.clear() + faceBuilder = faces.build() + outlineBuilder = outlines.build() } companion object { private val staticMode = shader( - "renderer/pos_color", "renderer/box_static" ) to VertexAttrib.Group.STATIC_RENDERER private val dynamicMode = shader( - "renderer/pos_color", "renderer/box_dynamic" ) to VertexAttrib.Group.DYNAMIC_RENDERER } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt index 749648149..43675a1c1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt @@ -40,31 +40,28 @@ fun DynamicESPRenderer.buildFilled( box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL -) = faces.use { +) = faceBuilder.use { val boxes = box.getBoxPair() ?: return@use - val pos11 = boxes.first.min val pos12 = boxes.first.max val pos21 = boxes.second.min val pos22 = boxes.second.max - grow(8) - - val blb by lazy { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color).end() } - val blf by lazy { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color).end() } - val brb by lazy { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color).end() } - val brf by lazy { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color).end() } - val tlb by lazy { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color).end() } - val tlf by lazy { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color).end() } - val trb by lazy { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color).end() } - val trf by lazy { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color).end() } - - if (sides.hasDirection(DirectionMask.EAST)) putQuad(brb, trb, trf, brf) - if (sides.hasDirection(DirectionMask.WEST)) putQuad(blb, blf, tlf, tlb) - if (sides.hasDirection(DirectionMask.UP)) putQuad(tlb, tlf, trf, trb) - if (sides.hasDirection(DirectionMask.DOWN)) putQuad(blb, brb, brf, blf) - if (sides.hasDirection(DirectionMask.SOUTH)) putQuad(blf, brf, trf, tlf) - if (sides.hasDirection(DirectionMask.NORTH)) putQuad(blb, tlb, trb, brb) + val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } } + val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } } + val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } } + val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } } + val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } } + val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } } + val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } } + val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } } + + if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf) + if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb) + if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb) + if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf) + if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf) + if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb) } fun DynamicESPRenderer.buildOutline( @@ -72,7 +69,7 @@ fun DynamicESPRenderer.buildOutline( color: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR -) = outlines.use { +) = outlineBuilder.use { val boxes = box.getBoxPair() ?: return@use val pos11 = boxes.first.min @@ -80,16 +77,14 @@ fun DynamicESPRenderer.buildOutline( val pos21 = boxes.second.min val pos22 = boxes.second.max - grow(8) - - val blb by lazy { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color).end() } - val blf by lazy { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color).end() } - val brb by lazy { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color).end() } - val brf by lazy { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color).end() } - val tlb by lazy { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color).end() } - val tlf by lazy { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color).end() } - val trb by lazy { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color).end() } - val trf by lazy { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color).end() } + val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } } + val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } } + val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } } + val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } } + val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } } + val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } } + val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } } + val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } } val hasEast = sides.hasDirection(DirectionMask.EAST) val hasWest = sides.hasDirection(DirectionMask.WEST) @@ -98,18 +93,18 @@ fun DynamicESPRenderer.buildOutline( val hasSouth = sides.hasDirection(DirectionMask.SOUTH) val hasNorth = sides.hasDirection(DirectionMask.NORTH) - if (outlineMode.check(hasUp, hasNorth)) putLine(tlb, trb) - if (outlineMode.check(hasUp, hasSouth)) putLine(tlf, trf) - if (outlineMode.check(hasUp, hasWest)) putLine(tlb, tlf) - if (outlineMode.check(hasUp, hasEast)) putLine(trf, trb) + if (outlineMode.check(hasUp, hasNorth)) buildLine(tlb, trb) + if (outlineMode.check(hasUp, hasSouth)) buildLine(tlf, trf) + if (outlineMode.check(hasUp, hasWest)) buildLine(tlb, tlf) + if (outlineMode.check(hasUp, hasEast)) buildLine(trf, trb) - if (outlineMode.check(hasDown, hasNorth)) putLine(blb, brb) - if (outlineMode.check(hasDown, hasSouth)) putLine(blf, brf) - if (outlineMode.check(hasDown, hasWest)) putLine(blb, blf) - if (outlineMode.check(hasDown, hasEast)) putLine(brb, brf) + if (outlineMode.check(hasDown, hasNorth)) buildLine(blb, brb) + if (outlineMode.check(hasDown, hasSouth)) buildLine(blf, brf) + if (outlineMode.check(hasDown, hasWest)) buildLine(blb, blf) + if (outlineMode.check(hasDown, hasEast)) buildLine(brb, brf) - if (outlineMode.check(hasWest, hasNorth)) putLine(tlb, blb) - if (outlineMode.check(hasNorth, hasEast)) putLine(trb, brb) - if (outlineMode.check(hasEast, hasSouth)) putLine(trf, brf) - if (outlineMode.check(hasSouth, hasWest)) putLine(tlf, blf) + if (outlineMode.check(hasWest, hasNorth)) buildLine(tlb, blb) + if (outlineMode.check(hasNorth, hasEast)) buildLine(trb, brb) + if (outlineMode.check(hasEast, hasSouth)) buildLine(trf, brf) + if (outlineMode.check(hasSouth, hasWest)) buildLine(tlf, blf) } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt index 8e70716f3..e82818916 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt @@ -110,7 +110,9 @@ fun StaticESPRenderer.buildFilled( box: Box, color: Color, sides: Int = DirectionMask.ALL -) = buildFilled(box, color, color, sides) +) { + buildFilled(box, color, color, sides) +} fun StaticESPRenderer.buildOutlineShape( pos: BlockPos, @@ -148,36 +150,35 @@ fun StaticESPRenderer.buildOutline( color: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR -) = buildOutline(box, color, color, sides, outlineMode) +) { + buildOutline(box, color, color, sides, outlineMode) +} fun StaticESPRenderer.buildFilled( box: Box, colorBottom: Color, colorTop: Color = colorBottom, sides: Int = DirectionMask.ALL -) = faces.use { +) = faceBuilder.use { val pos1 = box.min val pos2 = box.max - grow(8) - - val blb by vertex(faceVertices, pos1.x, pos1.y, pos1.z, colorBottom) - val blf by vertex(faceVertices, pos1.x, pos1.y, pos2.z, colorBottom) - val brb by vertex(faceVertices, pos2.x, pos1.y, pos1.z, colorBottom) - val brf by vertex(faceVertices, pos2.x, pos1.y, pos2.z, colorBottom) - val tlb by vertex(faceVertices, pos1.x, pos2.y, pos1.z, colorTop) - val tlf by vertex(faceVertices, pos1.x, pos2.y, pos2.z, colorTop) - val trb by vertex(faceVertices, pos2.x, pos2.y, pos1.z, colorTop) - val trf by vertex(faceVertices, pos2.x, pos2.y, pos2.z, colorTop) - - if (sides.hasDirection(DirectionMask.EAST)) putQuad(brb, trb, trf, brf) - if (sides.hasDirection(DirectionMask.WEST)) putQuad(blb, blf, tlf, tlb) - if (sides.hasDirection(DirectionMask.UP)) putQuad(tlb, tlf, trf, trb) - if (sides.hasDirection(DirectionMask.DOWN)) putQuad(blb, brb, brf, blf) - if (sides.hasDirection(DirectionMask.SOUTH)) putQuad(blf, brf, trf, tlf) - if (sides.hasDirection(DirectionMask.NORTH)) putQuad(blb, tlb, trb, brb) - - updateFaces = true + val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(colorBottom) } } + val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(colorBottom) } } + val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(colorBottom) } } + val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(colorBottom) } } + + val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(colorTop) } } + val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(colorTop) } } + val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(colorTop) } } + val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(colorTop) } } + + if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf) + if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb) + if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb) + if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf) + if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf) + if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb) } fun StaticESPRenderer.buildOutline( @@ -186,20 +187,18 @@ fun StaticESPRenderer.buildOutline( colorTop: Color = colorBottom, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR -) = outlines.use { +) = outlineBuilder.use { val pos1 = box.min val pos2 = box.max - grow(8) - - val blb by vertex(outlineVertices, pos1.x, pos1.y, pos1.z, colorBottom) - val blf by vertex(outlineVertices, pos1.x, pos1.y, pos2.z, colorBottom) - val brb by vertex(outlineVertices, pos2.x, pos1.y, pos1.z, colorBottom) - val brf by vertex(outlineVertices, pos2.x, pos1.y, pos2.z, colorBottom) - val tlb by vertex(outlineVertices, pos1.x, pos2.y, pos1.z, colorTop) - val tlf by vertex(outlineVertices, pos1.x, pos2.y, pos2.z, colorTop) - val trb by vertex(outlineVertices, pos2.x, pos2.y, pos1.z, colorTop) - val trf by vertex(outlineVertices, pos2.x, pos2.y, pos2.z, colorTop) + val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(colorBottom) } } + val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(colorBottom) } } + val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(colorBottom) } } + val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(colorBottom) } } + val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(colorTop) } } + val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(colorTop) } } + val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(colorTop) } } + val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(colorTop) } } val hasEast = sides.hasDirection(DirectionMask.EAST) val hasWest = sides.hasDirection(DirectionMask.WEST) @@ -208,20 +207,18 @@ fun StaticESPRenderer.buildOutline( val hasSouth = sides.hasDirection(DirectionMask.SOUTH) val hasNorth = sides.hasDirection(DirectionMask.NORTH) - if (outlineMode.check(hasUp, hasNorth)) putLine(tlb, trb) - if (outlineMode.check(hasUp, hasSouth)) putLine(tlf, trf) - if (outlineMode.check(hasUp, hasWest)) putLine(tlb, tlf) - if (outlineMode.check(hasUp, hasEast)) putLine(trf, trb) - - if (outlineMode.check(hasDown, hasNorth)) putLine(blb, brb) - if (outlineMode.check(hasDown, hasSouth)) putLine(blf, brf) - if (outlineMode.check(hasDown, hasWest)) putLine(blb, blf) - if (outlineMode.check(hasDown, hasEast)) putLine(brb, brf) + if (outlineMode.check(hasUp, hasNorth)) buildLine(tlb, trb) + if (outlineMode.check(hasUp, hasSouth)) buildLine(tlf, trf) + if (outlineMode.check(hasUp, hasWest)) buildLine(tlb, tlf) + if (outlineMode.check(hasUp, hasEast)) buildLine(trf, trb) - if (outlineMode.check(hasWest, hasNorth)) putLine(tlb, blb) - if (outlineMode.check(hasNorth, hasEast)) putLine(trb, brb) - if (outlineMode.check(hasEast, hasSouth)) putLine(trf, brf) - if (outlineMode.check(hasSouth, hasWest)) putLine(tlf, blf) + if (outlineMode.check(hasDown, hasNorth)) buildLine(blb, brb) + if (outlineMode.check(hasDown, hasSouth)) buildLine(blf, brf) + if (outlineMode.check(hasDown, hasWest)) buildLine(blb, blf) + if (outlineMode.check(hasDown, hasEast)) buildLine(brb, brf) - updateOutlines = true + if (outlineMode.check(hasWest, hasNorth)) buildLine(tlb, blb) + if (outlineMode.check(hasNorth, hasEast)) buildLine(trb, brb) + if (outlineMode.check(hasEast, hasSouth)) buildLine(trf, brf) + if (outlineMode.check(hasSouth, hasWest)) buildLine(tlf, blf) } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/DynamicESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/DynamicESP.kt index ceea2084f..e7d33ad69 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/DynamicESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/DynamicESP.kt @@ -17,18 +17,6 @@ package com.lambda.graphics.renderer.esp.global -import com.lambda.event.EventFlow.post -import com.lambda.event.events.RenderEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.impl.DynamicESPRenderer -object DynamicESP : DynamicESPRenderer() { - init { - listen { - clear() - RenderEvent.StaticESP().post() - upload() - } - } -} +object DynamicESP : DynamicESPRenderer() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt index ce00e73c3..6d905dd5a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/global/StaticESP.kt @@ -17,18 +17,6 @@ package com.lambda.graphics.renderer.esp.global -import com.lambda.event.EventFlow.post -import com.lambda.event.events.RenderEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer -object StaticESP : StaticESPRenderer(false) { - init { - listen { - clear() - RenderEvent.StaticESP().post() - upload() - } - } -} +object StaticESP : StaticESPRenderer() diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt index 34edd1ed5..9ba314bdd 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/DynamicESPRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt index 10fa1349b..d3d48deea 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/impl/StaticESPRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -17,46 +17,6 @@ package com.lambda.graphics.renderer.esp.impl -import com.lambda.graphics.buffer.IRenderContext import com.lambda.graphics.renderer.esp.ESPRenderer -import java.awt.Color -import java.util.concurrent.ConcurrentHashMap -open class StaticESPRenderer(private val useVertexCaching: Boolean = true) : ESPRenderer(false) { - val faceVertices = ConcurrentHashMap() - val outlineVertices = ConcurrentHashMap() - - var updateFaces = false - var updateOutlines = false - - override fun upload() { - if (updateFaces) { - updateFaces = false - faces.upload() - } - - if (updateOutlines) { - updateOutlines = false - outlines.upload() - } - } - - override fun clear() { - faceVertices.clear() - outlineVertices.clear() - super.clear() - } - - fun IRenderContext.vertex( - storage: MutableMap, - x: Double, y: Double, z: Double, - color: Color - ) = lazy { - val vtx = { vec3(x, y, z).color(color).end() } - if (!useVertexCaching) return@lazy vtx() - - storage.getOrPut(Vertex(x, y, z, color), vtx) - } - - data class Vertex(val x: Double, val y: Double, val z: Double, val color: Color) -} +open class StaticESPRenderer : ESPRenderer(false) \ No newline at end of file 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 index 3bd9a33c0..755031514 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt @@ -17,43 +17,58 @@ 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.buffer.VertexPipeline +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.pipeline.ScissorAdapter 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.Rect import com.lambda.util.math.Vec2d import org.lwjgl.glfw.GLFW -abstract class AbstractGUIRenderer( +open class AbstractGUIRenderer( attribGroup: VertexAttrib.Group, val shader: Shader ) { private val pipeline = VertexPipeline(VertexMode.TRIANGLES, attribGroup) + private var memoryMapping = true - protected fun render( + init { + listen(alwaysListen = true) { + memoryMapping = RenderSettings.useMemoryMapping + } + + listen(alwaysListen = true) { + if (memoryMapping) pipeline.sync() + } + } + + fun render( shade: Boolean = false, - block: VertexPipeline.() -> Unit + block: VertexPipeline.(Shader) -> Unit ) { - pipeline.clear() shader.use() - block(pipeline) + block(pipeline, shader) shader["u_Shade"] = shade.toInt().toDouble() if (shade) { shader["u_ShadeTime"] = GLFW.glfwGetTime() * GuiSettings.colorSpeed * 5.0 - shader["u_ShadeColor1"] = GuiSettings.shadeColor1 - shader["u_ShadeColor2"] = GuiSettings.shadeColor2 + shader["u_ShadeColor1"] = primaryColor + shader["u_ShadeColor2"] = secondaryColor shader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) } - pipeline.upload() - pipeline.render() + 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/font/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt similarity index 91% rename from common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt rename to common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt index 9c0574bd0..53ee181d5 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -15,11 +15,10 @@ * along with this program. If not, see . */ -package com.lambda.graphics.renderer.gui.font +package com.lambda.graphics.renderer.gui -import com.lambda.graphics.buffer.VertexPipeline import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.renderer.gui.AbstractGUIRenderer +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.renderer.gui.font.core.LambdaAtlas.height @@ -38,7 +37,7 @@ import java.awt.Color * 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("font/font")) { +object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("renderer/font")) { private val chars get() = RenderSettings.textFont private val emojis get() = RenderSettings.emojiFont @@ -71,8 +70,10 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("font/ bind(chars, emojis) - processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ -> - buildGlyph(char, position, pos1, pos2, col) + upload { + processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ -> + buildGlyph(char, position, pos1, pos2, col) + } } } @@ -104,7 +105,9 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("font/ val pos1 = Vec2d(0.0, posY) * actualScale val pos2 = pos1 + scaledSize - buildGlyph(glyph, position, pos1, pos2, color) + upload { + buildGlyph(glyph, position, pos1, pos2, color) + } } /** @@ -116,7 +119,7 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("font/ * @param pos2 The end position of the glyph * @param color The color of the glyph. */ - private fun VertexPipeline.buildGlyph( + private fun VertexBuilder.buildGlyph( glyph: GlyphInfo, origin: Vec2d = Vec2d.ZERO, pos1: Vec2d, @@ -128,13 +131,19 @@ object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("font/ val x2 = pos2.x + origin.x val y2 = pos2.y + origin.y - grow(4) - - putQuad( - vec3m(x1, y1, 0.0).vec2(glyph.uv1.x, glyph.uv1.y).color(color).end(), - vec3m(x1, y2, 0.0).vec2(glyph.uv1.x, glyph.uv2.y).color(color).end(), - vec3m(x2, y2, 0.0).vec2(glyph.uv2.x, glyph.uv2.y).color(color).end(), - vec3m(x2, y1, 0.0).vec2(glyph.uv2.x, glyph.uv1.y).color(color).end() + 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) + } ) } 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 new file mode 100644 index 000000000..1956d957d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/RectRenderer.kt @@ -0,0 +1,209 @@ +/* + * 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.graphics.buffer.vertex.attributes.VertexAttrib +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")) + + fun filledRect( + rect: Rect, + leftTopRadius: Double = 0.0, + rightTopRadius: Double = 0.0, + rightBottomRadius: Double = 0.0, + leftBottomRadius: Double = 0.0, + leftTop: Color = Color.WHITE, + rightTop: Color = Color.WHITE, + rightBottom: Color = Color.WHITE, + leftBottom: Color = Color.WHITE, + shade: Boolean = false, + ) = filled.putRect( + rect, + 0.0, + shade, + leftTopRadius, + rightTopRadius, + rightBottomRadius, + leftBottomRadius, + leftTop, + rightTop, + rightBottom, + leftBottom, + ) + + fun outlineRect( + rect: Rect, + width: Double = 1.0, + leftTopRadius: Double = 0.0, + rightTopRadius: Double = 0.0, + rightBottomRadius: Double = 0.0, + leftBottomRadius: Double = 0.0, + leftTop: Color = Color.WHITE, + rightTop: Color = Color.WHITE, + rightBottom: Color = Color.WHITE, + leftBottom: Color = Color.WHITE, + shade: Boolean = false, + ) { + if (width < 0.01) return + + outline.putRect( + rect, + width * 0.5, + shade, + leftTopRadius, + rightTopRadius, + rightBottomRadius, + leftBottomRadius, + leftTop, + rightTop, + rightBottom, + leftBottom, + ) + } + + fun glowRect( + rect: Rect, + outerSpread: Double = 1.0, + innerSpread: Double = 1.0, + leftTopInnerRadius: Double = 0.0, + rightTopInnerRadius: Double = 0.0, + rightBottomInnerRadius: Double = 0.0, + leftBottomInnerRadius: Double = 0.0, + leftTopOuterRadius: Double = 0.0, + rightTopOuterRadius: Double = 0.0, + rightBottomOuterRadius: Double = 0.0, + leftBottomOuterRadius: Double = 0.0, + leftTop: Color = Color.WHITE, + rightTop: Color = Color.WHITE, + rightBottom: Color = Color.WHITE, + leftBottom: Color = Color.WHITE, + shade: Boolean = false, + ) { + if (outerSpread < 0.01 && innerSpread < 0.01) return + + val pos1 = rect.leftTop + val pos2 = rect.rightBottom + + val size = pos2 - pos1 + val halfSize = size * 0.5 + val maxRadius = min(halfSize.x, halfSize.y) + + fun Double.clampRadius() = + 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.putRect( + rect, + outerSpread.coerceAtLeast(1.0), + shade, + leftTopOuterRadius, + rightTopOuterRadius, + rightBottomOuterRadius, + leftBottomOuterRadius, + leftTop, + rightTop, + rightBottom, + leftBottom, + ) + } + + private fun AbstractGUIRenderer.putRect( + rect: Rect, + expandIn: Double, + shade: Boolean, + leftTopRadius: Double, + rightTopRadius: Double, + rightBottomRadius: Double, + leftBottomRadius: Double, + leftTop: Color, + rightTop: Color, + rightBottom: Color, + leftBottom: Color, + ) = render(shade) { shader -> + val pos1 = rect.leftTop + val pos2 = rect.rightBottom + + val expand = expandIn.coerceAtLeast(0.0) + 1 + val smoothing = 0.5 + + val p1 = pos1 - expand - smoothing + val p2 = pos2 + expand + smoothing + + 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_Size"] = size + + // Round radius + shader["u_RoundLeftTop"] = leftTopRadius.clampRadius() + shader["u_RoundLeftBottom"] = leftBottomRadius.clampRadius() + shader["u_RoundRightBottom"] = rightBottomRadius.clampRadius() + shader["u_RoundRightTop"] = rightTopRadius.clampRadius() + + // 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) + } + ) + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt index b8a736f64..0059aa2f4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/TextureRenderer.kt @@ -18,12 +18,14 @@ package com.lambda.graphics.renderer.gui import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.VertexPipeline +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.Companion.shader import com.lambda.graphics.texture.Texture 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.Rect import com.lambda.util.math.Vec2d import org.lwjgl.glfw.GLFW.glfwGetTime @@ -31,8 +33,8 @@ import org.lwjgl.glfw.GLFW.glfwGetTime object TextureRenderer { private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.POS_UV) - private val mainShader = shader("renderer/pos_tex") - private val coloredShader = shader("renderer/pos_tex_shady") + private val mainShader = shader("pos_tex") + private val coloredShader = shader("pos_tex_shady") fun drawTexture(texture: Texture, rect: Rect) { texture.bind() @@ -45,31 +47,39 @@ object TextureRenderer { texture.bind() coloredShader.use() - coloredShader["u_Time"] = glfwGetTime() * GuiSettings.colorSpeed * 5.0 - coloredShader["u_Color1"] = GuiSettings.shadeColor1 - coloredShader["u_Color2"] = GuiSettings.shadeColor2 - coloredShader["u_Size"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + coloredShader["u_Shade"] = 1.0 + coloredShader["u_ShadeTime"] = glfwGetTime() * GuiSettings.colorSpeed * 5.0 + coloredShader["u_ShadeColor1"] = primaryColor + coloredShader["u_ShadeColor2"] = secondaryColor + + coloredShader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) drawInternal(rect) } - private fun drawInternal(rect: Rect) { + fun drawInternal(rect: Rect) { val pos1 = rect.leftTop val pos2 = rect.rightBottom - pipeline.use { - grow(4) - - putQuad( - vec2(pos1.x, pos1.y).vec2(0.0, 0.0).end(), - vec2(pos1.x, pos2.y).vec2(0.0, 1.0).end(), - vec2(pos2.x, pos2.y).vec2(1.0, 1.0).end(), - vec2(pos2.x, pos1.y).vec2(1.0, 0.0).end() + pipeline.immediate { + buildQuad( + vertex { + vec2(pos1.x, pos1.y) + vec2(0.0, 0.0) + }, + vertex { + vec2(pos1.x, pos2.y) + vec2(0.0, 1.0) + }, + vertex { + vec2(pos2.x, pos2.y) + vec2(1.0, 1.0) + }, + vertex { + vec2(pos2.x, pos1.y) + vec2(1.0, 0.0) + } ) } - - pipeline.upload() - pipeline.render() - pipeline.clear() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/sdf/DistanceFieldTexture.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/sdf/DistanceFieldTexture.kt index 1f8eaf5e0..238cb2943 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/sdf/DistanceFieldTexture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/sdf/DistanceFieldTexture.kt @@ -34,25 +34,30 @@ import java.awt.image.BufferedImage * @param image Image data to upload */ class DistanceFieldTexture(image: BufferedImage) : Texture(image, levels = 0) { - private val shader = shader("signed_distance_field", "renderer/pos_tex") + private val shader = shader("post/sdf") private val frame = CachedFrame(width, height).write { - FrameBuffer.pipeline.use { + FrameBuffer.pipeline.immediate { val (pos1, pos2) = Vec2d.ZERO to Vec2d(width, height) - grow(4) - putQuad( - vec2(pos1.x, pos1.y).vec2(0.0, 1.0).end(), - vec2(pos1.x, pos2.y).vec2(0.0, 0.0).end(), - vec2(pos2.x, pos2.y).vec2(1.0, 0.0).end(), - vec2(pos2.x, pos1.y).vec2(1.0, 1.0).end() + buildQuad( + vertex { + vec2(pos1.x, pos1.y).vec2(0.0, 1.0) + }, + vertex { + vec2(pos1.x, pos2.y).vec2(0.0, 0.0) + }, + vertex { + vec2(pos2.x, pos2.y).vec2(1.0, 0.0) + }, + vertex { + vec2(pos2.x, pos1.y).vec2(1.0, 1.0) + } ) shader.use() shader["u_TexelSize"] = Vec2d.ONE / pos2 super.bind(0) - - immediateDraw() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt deleted file mode 100644 index 073108079..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt +++ /dev/null @@ -1,121 +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.graphics.renderer.gui.rect - -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.renderer.gui.AbstractGUIRenderer -import com.lambda.graphics.shader.Shader.Companion.shader -import com.lambda.util.math.Rect -import java.awt.Color -import kotlin.math.min - -object FilledRectRenderer : AbstractGUIRenderer( - VertexAttrib.Group.RECT_FILLED, shader("renderer/rect_filled") -) { - private const val MIN_SIZE = 0.5 - private const val MIN_ALPHA = 1 - private const val EXPAND = 0.3 - - fun filledRect( - rect: Rect, - roundRadius: Double = 0.0, - color: Color = Color.WHITE, - shade: Boolean = false, - ) = filledRect(rect, roundRadius, color, color, color, color, shade) - - fun filledRect( - rect: Rect, - roundRadius: Double = 0.0, - leftTop: Color = Color.WHITE, - rightTop: Color = Color.WHITE, - rightBottom: Color = Color.WHITE, - leftBottom: Color = Color.WHITE, - shade: Boolean = false, - ) = filledRect( - rect, - roundRadius, roundRadius, roundRadius, roundRadius, - leftTop, rightTop, rightBottom, leftBottom, - shade - ) - - fun filledRect( - rect: Rect, - leftTopRadius: Double = 0.0, - rightTopRadius: Double = 0.0, - rightBottomRadius: Double = 0.0, - leftBottomRadius: Double = 0.0, - color: Color = Color.WHITE, - shade: Boolean = false, - ) = filledRect( - rect, - leftTopRadius, rightTopRadius, rightBottomRadius, leftBottomRadius, - color, color, color, color, - shade - ) - - fun filledRect( - rect: Rect, - leftTopRadius: Double = 0.0, - rightTopRadius: Double = 0.0, - rightBottomRadius: Double = 0.0, - leftBottomRadius: Double = 0.0, - leftTop: Color = Color.WHITE, - rightTop: Color = Color.WHITE, - rightBottom: Color = Color.WHITE, - leftBottom: Color = Color.WHITE, - shade: Boolean = false, - ) = render(shade) { - val pos1 = rect.leftTop - val pos2 = rect.rightBottom - - val size = pos2 - pos1 - - if (leftTop.alpha < MIN_ALPHA && - rightTop.alpha < MIN_ALPHA && - rightBottom.alpha < MIN_ALPHA && - leftBottom.alpha < MIN_ALPHA - ) return@render - - if (size.x < MIN_SIZE || size.y < MIN_SIZE) return@render - - val halfSize = size * 0.5 - val maxRadius = min(halfSize.x, halfSize.y) - - val ltr = leftTopRadius.coerceAtMost(maxRadius).coerceAtLeast(0.0) - val lbr = leftBottomRadius.coerceAtMost(maxRadius).coerceAtLeast(0.0) - val rbr = rightBottomRadius.coerceAtMost(maxRadius).coerceAtLeast(0.0) - val rtr = rightTopRadius.coerceAtMost(maxRadius).coerceAtLeast(0.0) - - val p1 = pos1 - EXPAND - val p2 = pos2 + EXPAND - - shader["u_Size"] = size - shader["u_RoundLeftTop"] = ltr - shader["u_RoundLeftBottom"] = lbr - shader["u_RoundRightBottom"] = rbr - shader["u_RoundRightTop"] = rtr - - grow(4) - putQuad( - vec3m(p1.x, p1.y, 0.0).vec2(0.0, 0.0).color(leftTop).end(), - vec3m(p1.x, p2.y, 0.0).vec2(0.0, 1.0).color(leftBottom).end(), - vec3m(p2.x, p2.y, 0.0).vec2(1.0, 1.0).color(rightBottom).end(), - vec3m(p2.x, p1.y, 0.0).vec2(1.0, 0.0).color(rightTop).end() - ) - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt deleted file mode 100644 index a43381723..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/OutlineRectRenderer.kt +++ /dev/null @@ -1,114 +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.graphics.renderer.gui.rect - -import com.lambda.graphics.buffer.IRenderContext -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.pipeline.ScissorAdapter -import com.lambda.graphics.renderer.gui.AbstractGUIRenderer -import com.lambda.graphics.shader.Shader -import com.lambda.graphics.shader.Shader.Companion.shader -import com.lambda.util.math.lerp -import com.lambda.util.math.MathUtils.toInt -import com.lambda.util.math.MathUtils.toRadian -import com.lambda.util.math.Rect -import com.lambda.util.math.Vec2d -import com.lambda.util.math.transform -import java.awt.Color -import kotlin.math.cos -import kotlin.math.min -import kotlin.math.sin - -object OutlineRectRenderer : AbstractGUIRenderer( - VertexAttrib.Group.RECT_OUTLINE, shader("renderer/rect_outline") -) { - private const val QUALITY = 8 - private const val VERTICES_COUNT = QUALITY * 4 - - fun outlineRect( - rect: Rect, - roundRadius: Double = 0.0, - glowRadius: Double = 1.0, - color: Color = Color.WHITE, - shade: Boolean = false, - ) = outlineRect(rect, roundRadius, glowRadius, color, color, color, color, shade) - - fun outlineRect( - rect: Rect, - roundRadius: Double = 0.0, - glowRadius: Double = 1.0, - leftTop: Color = Color.WHITE, - rightTop: Color = Color.WHITE, - rightBottom: Color = Color.WHITE, - leftBottom: Color = Color.WHITE, - shade: Boolean = false, - ) = render(shade) { - if (glowRadius < 0.1) return@render - - grow(VERTICES_COUNT * 3) - - fun IRenderContext.genVertices(size: Double, isGlow: Boolean): MutableList { - val r = rect.expand(size) - val a = (!isGlow).toInt().toDouble() - - val halfSize = r.size * 0.5 - val maxRadius = min(halfSize.x, halfSize.y) - 0.5 - val round = (roundRadius + size).coerceAtMost(maxRadius).coerceAtLeast(0.0) - - fun MutableList.buildCorners(base: Vec2d, c: Color, angleRange: IntRange) = repeat(QUALITY) { - val min = angleRange.first.toDouble() - val max = angleRange.last.toDouble() - val p = it.toDouble() / QUALITY - val angle = lerp(p, min, max).toRadian() - - val pos = base + Vec2d(cos(angle), -sin(angle)) * round - - val uvx = transform(pos.x, rect.left, rect.right, 0.0, 1.0) - val uvy = transform(pos.y, rect.top, rect.bottom, 0.0, 1.0) - - add(vec3m(pos.x, pos.y, 0.0).vec2(uvx, uvy).float(a).color(c).end()) - } - - val rt = r.rightTop + Vec2d(-round, round) - val lt = r.leftTop + Vec2d(round, round) - val lb = r.leftBottom + Vec2d(round, -round) - val rb = r.rightBottom + Vec2d(-round, -round) - - return mutableListOf().apply { - buildCorners(rt, rightTop, 0..90) - buildCorners(lt, leftTop, 90..180) - buildCorners(lb, leftBottom, 180..270) - buildCorners(rb, rightBottom, 270..360) - } - } - - val main = genVertices(0.0, false) - - fun drawStripWith(vertices: MutableList) { - var prev = main.last() to vertices.last() - repeat(VERTICES_COUNT) { - val new = main[it] to vertices[it] - putQuad(new.first, new.second, prev.second, prev.first) - prev = new - } - } - - drawStripWith(genVertices(-(glowRadius.coerceAtMost(1.0)), true)) - drawStripWith(genVertices(glowRadius, true)) - } -} diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/ParsedShader.kt b/common/src/main/kotlin/com/lambda/graphics/shader/ParsedShader.kt new file mode 100644 index 000000000..5bf5e53d2 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/shader/ParsedShader.kt @@ -0,0 +1,116 @@ +/* + * 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.shader + +import com.lambda.util.stream +import org.apache.commons.io.IOUtils + +class ParsedShader(path: String) { + private val text = IOUtils.toString("shaders/$path.glsl".stream, Charsets.UTF_8) + + private val blocks = text.getBlocks() + private val dependencies = text.getDependencies() + + val methods = text.getMethods() + val definitions = text.getDefinitions() + + val attribs = blocks.getBlock("attributes") + val uniforms = blocks.getBlock("uniforms") + val exported = blocks.getBlock("export") + + init { + dependencies.forEach { dependencyName -> + val dependency = ParsedShader("shared/$dependencyName") + + //attribs.addAll(shader.blocks.getBlock("attributes")) + dependency.attribs.forEach { attrib -> + check(attribs.any { it.name == attrib.name && it.type == attrib.type }) { + "[${path}]: dependency \"$dependencyName\" requires \"${attrib.type} ${attrib.name}\" attribute to be present" + } + } + + uniforms.addAll(dependency.uniforms) + exported.addAll(dependency.exported) + + definitions.addAll(dependency.definitions) + methods.addAll(dependency.methods.filter { + it.name != "fragment" && it.name != "vertex" + }) + } + } + + data class Field(val type: String, val name: String, val flag: String?) + data class Method(val name: String, val returnType: String, val parameters: String, var code: String) { + fun construct(constructedName: String = name): String { + return "$returnType $constructedName($parameters) {\n $code\n}\n" + } + } + + companion object { + private val blockRegex = Regex("""\s*(\w+)\s*\{(.*?)}\s*""", RegexOption.DOT_MATCHES_ALL) + private val methodRegex = Regex("""^\s*(\w+)\s+(\w+)\s*\((.*?)\)\s*\{(.*?)}#""", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.MULTILINE)) + + private val fieldRegex = Regex("""\s*(\w+(?:\s+\w+)*)\s+(\w+)\s*;\s*(#.*)?""") + private val includeRegex = Regex("""^#include\s+"([\w-]+)"""", RegexOption.MULTILINE) + private val defineRegex = Regex("""^#define\s+(\w+)\s+(.+)""", RegexOption.MULTILINE) + + private fun String.getBlocks() = mutableMapOf>().apply { + blockRegex.findAll(this@getBlocks).forEach { match -> + val blockName = match.groupValues[1] + val blockFieldsStr = match.groupValues[2] + + val fields = fieldRegex.findAll(blockFieldsStr).map { fieldMatch -> + val type = fieldMatch.groupValues[1] + val name = fieldMatch.groupValues[2] + + val flag = fieldMatch.groupValues[3] + .removePrefix("#").trim() + .takeIf { it.isNotEmpty() } + + Field(type, name, flag) + }.toMutableSet() + + getOrPut(blockName, ::mutableSetOf).addAll(fields) + } + } + + private fun String.getMethods() = mutableSetOf().apply { + methodRegex.findAll(this@getMethods).forEach { match -> + val returnType = match.groupValues[1] + val methodName = match.groupValues[2] + val parameters = match.groupValues[3] + val code = match.groupValues[4].trim() + + add(Method(methodName, returnType, parameters, code)) + } + } + + private fun String.getDependencies() = includeRegex.findAll(this) + .map { it.groupValues[1] } + .toSet() + + private fun String.getDefinitions() = mutableSetOf().apply { + defineRegex.findAll(this@getDefinitions).forEach { match -> + add(Field(match.groupValues[2].trim(), match.groupValues[1], null)) + } + } + + private fun MutableMap>.getBlock(name: String) = + getOrDefault(name, mutableSetOf()) + } +} 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 c5b718a4d..8b42dfd93 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt @@ -29,13 +29,19 @@ import org.joml.Matrix4f import org.lwjgl.opengl.GL20C.* import java.awt.Color -class Shader private constructor(fragmentPath: String, vertexPath: String) { +class Shader private constructor(name: String) { private val uniformCache: Object2IntMap = Object2IntOpenHashMap() - private val id = createShaderProgram( - loadShader(ShaderType.VERTEX_SHADER, "shaders/vertex/$vertexPath.vert"), - loadShader(ShaderType.FRAGMENT_SHADER, "shaders/fragment/$fragmentPath.frag") - ) + private val id: Int + + init { + val texts = buildShaderSource(name) + + id = createShaderProgram( + loadShader(ShaderType.VERTEX_SHADER, texts.first), + loadShader(ShaderType.FRAGMENT_SHADER, texts.second) + ) + } fun use() { glUseProgram(id) @@ -79,14 +85,11 @@ class Shader private constructor(fragmentPath: String, vertexPath: String) { uniformMatrix(loc(name), mat) companion object { - private val shaderCache = hashMapOf, Shader>() + private val shaderCache = hashMapOf() fun shader(path: String) = - shader(path, path) - - fun shader(fragmentPath: String, vertexPath: String) = - shaderCache.getOrPut(fragmentPath to vertexPath) { - Shader(fragmentPath, vertexPath) + shaderCache.getOrPut(path) { + Shader(path) } } } diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/ShaderInterp.kt b/common/src/main/kotlin/com/lambda/graphics/shader/ShaderInterp.kt new file mode 100644 index 000000000..8c5d8cd61 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/shader/ShaderInterp.kt @@ -0,0 +1,125 @@ +/* + * 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.shader + + +fun buildShaderSource(path: String): Pair { + val shader = ParsedShader(path) + + val vertexShader = StringBuilder().appendLine(HEADER) + val fragmentShader = StringBuilder().appendLine(HEADER) + val shaders = setOf(vertexShader, fragmentShader) + + /* Build attribs for vertex shader */ + shader.attribs.apply { + if (isNotEmpty()) vertexShader.appendLine() + + forEachIndexed { i, attribute -> + vertexShader.appendLine( + "layout (location = $i) in ${attribute.type} ${attribute.name};" + ) + } + } + + /* Build uniforms */ + shader.uniforms.apply { + val vertex = mutableSetOf() + val fragment = mutableSetOf() + + vertex.add("uniform mat4 u_ProjModel;") + forEach { uniform -> + val line = "uniform ${uniform.type} ${uniform.name};" + + when (uniform.flag) { + "vertex" -> vertex += line + "fragment" -> fragment += line + "global" -> { vertex += line; fragment += line } + else -> throw IllegalStateException("Destination of \"$line\" is not specified") + } + } + + vertex.apply { + vertexShader.appendLine() + forEach { vertexShader.appendLine(it) } + } + + fragment.apply { + if (isNotEmpty()) fragmentShader.appendLine() + forEach { fragmentShader.appendLine(it) } + } + } + + /* Does "v_TexCoord = uv" in the vertex shader for you */ + val autoAssignment = StringBuilder() + + /* Build in/out vars */ + shader.exported.apply { + if (isNotEmpty()) shaders.forEach(StringBuilder::appendLine) + + // Add gl_Position = u_ProjModel * pos; by default + val hasPosAttribute = shader.attribs.any { it.name == "pos" && it.type == "vec4" } + val glPosImplemented = any { it.name == "gl_Position" } + if (hasPosAttribute && !glPosImplemented) { + autoAssignment.appendLine(" gl_Position = u_ProjModel * pos;") + } + + forEach { export -> + if (export.type != "core") { + vertexShader.appendLine("out ${export.type} ${export.name};") + fragmentShader.appendLine("in ${export.type} ${export.name};") + } + + export.flag?.let { expr -> /* Add assignment if present */ + autoAssignment.appendLine(" ${export.name} = ${expr};") + } + } + + fragmentShader.appendLine() + fragmentShader.appendLine("out vec4 color;") + } + + /* Build definitions */ + shader.definitions.apply { + if (isNotEmpty()) fragmentShader.appendLine() + + forEach { definition -> + fragmentShader.appendLine("#define ${definition.name} ${definition.type}") + } + } + + /* Build methods */ + shaders.forEach(StringBuilder::appendLine) + val methods = shader.methods + + val vertexMain = methods.takeMain("vertex").apply { + code += autoAssignment.trim() + }.construct("main") + vertexShader.appendLine(vertexMain) + + val fragmentMain = methods.takeMain("fragment").construct("main") + methods.forEach { fragmentShader.appendLine(it.construct()) } + fragmentShader.appendLine(fragmentMain) + + return vertexShader.toString().trimEnd() to fragmentShader.toString().trimEnd() +} + +private fun MutableSet.takeMain(name: String) = firstOrNull { + it.name == name && it.returnType == "void" && it.parameters.isEmpty() +}.apply(this::remove) ?: ParsedShader.Method(name, "void", "", "") + +private const val HEADER = "#version 330 core" diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/ShaderUtils.kt b/common/src/main/kotlin/com/lambda/graphics/shader/ShaderUtils.kt index 6cfda3c32..f1f349b87 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/ShaderUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/ShaderUtils.kt @@ -30,12 +30,11 @@ object ShaderUtils { private val matrixBuffer = BufferUtils.createFloatBuffer(4 * 4) private const val shaderInfoLogLength = 512 - fun loadShader(type: ShaderType, resource: LambdaResource): Int { + fun loadShader(type: ShaderType, text: String): Int { // Create new shader object val shader = glCreateShader(type.gl) // Attach source code and compile it - val text = IOUtils.toString(resource.stream, Charsets.UTF_8) GlStateManager.glShaderSource(shader, ImmutableList.of(text)) val error = compileShader(shader) @@ -43,9 +42,10 @@ object ShaderUtils { error?.let { err -> val builder = StringBuilder() .append("Failed to compile ${type.name} shader").appendLine() - .append("Path: $resource").appendLine() .append("Compiler output:").appendLine() .append(err) + .appendLine().appendLine("CODE:") + .append(text) throw RuntimeException(builder.toString()) } diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/AnimatedTexture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/AnimatedTexture.kt index 6229c2c17..4a2687756 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/AnimatedTexture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/AnimatedTexture.kt @@ -18,7 +18,6 @@ package com.lambda.graphics.texture import com.lambda.graphics.buffer.pixel.PixelBuffer -import com.lambda.util.Communication.logError import com.lambda.util.LambdaResource import com.lambda.util.stream import org.lwjgl.BufferUtils @@ -29,7 +28,7 @@ import java.nio.ByteBuffer class AnimatedTexture(path: LambdaResource) : Texture(image = null) { private val pbo: PixelBuffer private val gif: ByteBuffer // Do NOT free this pointer - private val frameDurations: IntArray + private val frameDurations: IntArray // Array of frame duration milliseconds as ints val channels: Int val frames: Int @@ -39,13 +38,12 @@ class AnimatedTexture(path: LambdaResource) : Texture(image = null) { private var currentFrame = 0 private var lastUpload = 0L - override fun bind(slot: Int) { - update() - super.bind(slot) - } + override fun bind(slot: Int) { update(); super.bind(slot) } fun update() { - if (System.currentTimeMillis() - lastUpload >= frameDurations[currentFrame]) { + val now = System.currentTimeMillis() + + if (now - lastUpload >= frameDurations[currentFrame]) { // This is cool because instead of having a buffer for each frame we can // just move the frame's block on each update // 0 memory allocation and few cpu cycles @@ -54,12 +52,10 @@ class AnimatedTexture(path: LambdaResource) : Texture(image = null) { .limit(blockSize * (currentFrame + 1)) pbo.upload(slice, offset = 0) - ?.let { err -> logError("Error uploading to PBO", err) } - gif.clear() currentFrame = (currentFrame + 1) % frames - lastUpload = System.currentTimeMillis() + lastUpload = now } } diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index fe56a853e..b7e9261f4 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -20,6 +20,8 @@ package com.lambda.graphics.texture import com.lambda.graphics.texture.TextureUtils.bindTexture import com.lambda.graphics.texture.TextureUtils.readImage import com.lambda.graphics.texture.TextureUtils.setupTexture +import com.lambda.util.LambdaResource +import com.lambda.util.readImage import net.minecraft.client.texture.NativeImage import org.lwjgl.opengl.GL45C.* import java.awt.image.BufferedImage @@ -36,6 +38,21 @@ open class Texture { private val levels: Int private val nativeFormat: NativeImage.Format // For mojang native images + /** + * @param path Lambda resource path + * @param format The format of the image passed in + * @param levels Number of mipmap levels to generate for the texture + */ + constructor(path: LambdaResource, format: Int = GL_RGBA, levels: Int = 4) { + val image = path.readImage() + this.format = bufferedMapping[image.type] ?: format + this.levels = levels + this.nativeFormat = nativeMapping.getOrDefault(format, NativeImage.Format.RGBA) + + bindTexture(id) + upload(image) + } + /** * @param image Optional initial image to upload to the texture * @param format The format of the image passed in, if the [image] is null, then you must pass the appropriate format diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureOwner.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureOwner.kt index 98f51465b..ecc01a8ba 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureOwner.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureOwner.kt @@ -44,27 +44,27 @@ object TextureOwner { textureMap.getValue(this@texture)[index] as T /** - * Binds a list of textures to texture slots, ensuring no more than 32 textures + * Binds a list of textures to texture slots, ensuring no more than 96 textures * are bound at once (to fit within the typical GPU limitations) * * @param textures The list of objects that own textures to be bound. - * @throws IllegalArgumentException If more than 32 textures are provided. + * @throws IllegalArgumentException If more than 96 textures are provided. */ fun bind(vararg textures: Any) { - check(textures.size < 33) { "Texture slot overflow, expected to use less than 33 slots, got ${textures.size} slots" } + check(textures.size <= 96) { "Expected equal or less than 96 simultaneous textures, got ${textures.size} textures" } textures.forEachIndexed { index, texture -> texture.texture.bind(index) } } /** - * Binds a list of textures to texture slots, ensuring no more than 32 textures + * Binds a list of textures to texture slots, ensuring no more than 96 textures * are bound at once (to fit within the typical GPU limitations) * * @param textures The list of textures to be bound - * @throws IllegalArgumentException If more than 32 textures are provided + * @throws IllegalArgumentException If more than 96 textures are provided */ fun bind(vararg textures: Texture) { - check(textures.size < 33) { "Texture slot overflow, expected to use less than 33 slots, got ${textures.size} slots" } + check(textures.size <= 96) { "Expected equal or less than 96 simultaneous textures, got ${textures.size} textures" } textures.forEachIndexed { index, texture -> texture.bind(index) } } diff --git a/common/src/main/kotlin/com/lambda/gui/GuiManager.kt b/common/src/main/kotlin/com/lambda/gui/GuiManager.kt index 822a8e3f2..c2efba21e 100644 --- a/common/src/main/kotlin/com/lambda/gui/GuiManager.kt +++ b/common/src/main/kotlin/com/lambda/gui/GuiManager.kt @@ -17,19 +17,24 @@ package com.lambda.gui -import com.lambda.config.settings.NumericSetting import com.lambda.config.settings.comparable.BooleanSetting import com.lambda.config.settings.comparable.EnumSetting import com.lambda.config.settings.FunctionSetting +import com.lambda.config.settings.complex.ColorSetting import com.lambda.config.settings.complex.KeyBindSetting +import com.lambda.config.settings.numeric.DoubleSetting +import com.lambda.config.settings.numeric.FloatSetting +import com.lambda.config.settings.numeric.IntegerSetting +import com.lambda.config.settings.numeric.LongSetting import com.lambda.core.Loadable import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout -import com.lambda.gui.impl.clickgui.module.settings.BooleanButton.Companion.booleanSetting -import com.lambda.gui.impl.clickgui.module.settings.EnumSlider.Companion.enumSetting -import com.lambda.gui.impl.clickgui.module.settings.KeybindPicker.Companion.keybindSetting -import com.lambda.gui.impl.clickgui.module.settings.NumberSlider.Companion.numericSetting -import com.lambda.gui.impl.clickgui.settings.UnitButton.Companion.unitSetting +import com.lambda.gui.impl.clickgui.module.setting.settings.BooleanButton.Companion.booleanSetting +import com.lambda.gui.impl.clickgui.module.setting.settings.ColorPicker.Companion.colorPicker +import com.lambda.gui.impl.clickgui.module.setting.settings.EnumSlider.Companion.enumSetting +import com.lambda.gui.impl.clickgui.module.setting.settings.KeybindPicker.Companion.keybindSetting +import com.lambda.gui.impl.clickgui.module.setting.settings.NumberSlider.Companion.numberSlider +import com.lambda.gui.impl.clickgui.module.setting.settings.UnitButton.Companion.unitButton import kotlin.reflect.KClass object GuiManager : Loadable { @@ -41,23 +46,73 @@ object GuiManager : Loadable { override fun load(): String { typeAdapter { owner, ref -> - owner.booleanSetting(ref) + owner.booleanSetting(ref.name, ref::value).apply { + visibility { ref.visibility() } + } } typeAdapter> { owner, ref -> - owner.enumSetting(ref) + owner.enumSetting(ref.name, ref::value).apply { + visibility { ref.visibility() } + } } typeAdapter> { owner, ref -> - owner.unitSetting(ref) + owner.unitButton(ref.name, ref::value).apply { + visibility { ref.visibility() } + } } - typeAdapter> { owner, ref -> - owner.numericSetting(ref) + typeAdapter { owner, ref -> + owner.numberSlider( + ref.name, ref.unit, + ref.range.start, ref.range.endInclusive, ref.step, + ref::value + ).apply { + visibility { ref.visibility() } + } + } + + typeAdapter { owner, ref -> + owner.numberSlider( + ref.name, ref.unit, + ref.range.start, ref.range.endInclusive, ref.step, + ref::value + ).apply { + visibility { ref.visibility() } + } + } + + typeAdapter { owner, ref -> + owner.numberSlider( + ref.name, ref.unit, + ref.range.start, ref.range.endInclusive, ref.step, + ref::value + ).apply { + visibility { ref.visibility() } + } + } + + typeAdapter { owner, ref -> + owner.numberSlider( + ref.name, ref.unit, + ref.range.start, ref.range.endInclusive, ref.step, + ref::value + ).apply { + visibility { ref.visibility() } + } } typeAdapter { owner, ref -> - owner.keybindSetting(ref) + owner.keybindSetting(ref.name, ref::value).apply { + visibility { ref.visibility() } + } + } + + typeAdapter { owner, ref -> + owner.colorPicker(ref.name, ref::value).apply { + visibility { ref.visibility() } + } } return "Loaded ${typeMap.size} gui type adapters." diff --git a/common/src/main/kotlin/com/lambda/gui/LambdaScreen.kt b/common/src/main/kotlin/com/lambda/gui/LambdaScreen.kt index cf9a58d83..b51555f48 100644 --- a/common/src/main/kotlin/com/lambda/gui/LambdaScreen.kt +++ b/common/src/main/kotlin/com/lambda/gui/LambdaScreen.kt @@ -38,7 +38,7 @@ import net.minecraft.text.Text */ class LambdaScreen( override val name: String, - val layout: ScreenLayout + val layout: RootLayout ) : Screen(Text.of(name)), Nameable, Muteable { override val isMuted: Boolean get() = !isOpen diff --git a/common/src/main/kotlin/com/lambda/gui/ScreenLayout.kt b/common/src/main/kotlin/com/lambda/gui/RootLayout.kt similarity index 85% rename from common/src/main/kotlin/com/lambda/gui/ScreenLayout.kt rename to common/src/main/kotlin/com/lambda/gui/RootLayout.kt index 07c642ebc..d0a4f8df6 100644 --- a/common/src/main/kotlin/com/lambda/gui/ScreenLayout.kt +++ b/common/src/main/kotlin/com/lambda/gui/RootLayout.kt @@ -21,7 +21,7 @@ import com.lambda.graphics.RenderMain import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout -class ScreenLayout : Layout(owner = null) { +class RootLayout : Layout(owner = null) { init { onUpdate { size = RenderMain.screenSize @@ -33,7 +33,7 @@ class ScreenLayout : Layout(owner = null) { * Creates gui layout */ @UIBuilder - fun gui(name: String, block: ScreenLayout.() -> Unit) = - LambdaScreen(name, ScreenLayout().apply(block)) + fun gui(name: String, block: RootLayout.() -> Unit) = + LambdaScreen(name, RootLayout().apply(block)) } } diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/FilledRect.kt b/common/src/main/kotlin/com/lambda/gui/component/core/FilledRect.kt index 877430948..2d93f94c3 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/core/FilledRect.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/core/FilledRect.kt @@ -17,29 +17,13 @@ package com.lambda.gui.component.core -import com.lambda.graphics.renderer.gui.rect.FilledRectRenderer.filledRect +import com.lambda.graphics.renderer.gui.RectRenderer.filledRect import com.lambda.gui.component.layout.Layout -import com.lambda.util.math.Rect -import java.awt.Color class FilledRect( owner: Layout -) : Layout(owner) { - @UIRenderPr0p3rty var leftTopRadius = 0.0 - @UIRenderPr0p3rty var rightTopRadius = 0.0 - @UIRenderPr0p3rty var rightBottomRadius = 0.0 - @UIRenderPr0p3rty var leftBottomRadius = 0.0 - - @UIRenderPr0p3rty var leftTopColor: Color = Color.WHITE - @UIRenderPr0p3rty var rightTopColor: Color = Color.WHITE - @UIRenderPr0p3rty var rightBottomColor: Color = Color.WHITE - @UIRenderPr0p3rty var leftBottomColor: Color = Color.WHITE - - @UIRenderPr0p3rty var shade = false - +) : RectLayout(owner) { init { - properties.interactionPassthrough = true - onRender { filledRect( rect, @@ -56,46 +40,6 @@ class FilledRect( } } - fun setRadius(radius: Double) { - leftTopRadius = radius - rightTopRadius = radius - rightBottomRadius = radius - leftBottomRadius = radius - } - - fun setRadius( - leftTopRadius: Double, - rightTopRadius: Double, - rightBottomRadius: Double, - leftBottomRadius: Double, - ) { - this.leftTopRadius = leftTopRadius - this.rightTopRadius = rightTopRadius - this.rightBottomRadius = rightBottomRadius - this.leftBottomRadius = leftBottomRadius - } - - fun setColor(color: Color) { - leftTopColor = color - rightTopColor = color - rightBottomColor = color - leftBottomColor = color - } - - fun setColorH(colorL: Color, colorR: Color) { - leftTopColor = colorL - rightTopColor = colorR - rightBottomColor = colorR - leftBottomColor = colorL - } - - fun setColorV(colorT: Color, colorB: Color) { - leftTopColor = colorT - rightTopColor = colorT - rightBottomColor = colorB - leftBottomColor = colorB - } - companion object { /** * Creates a [FilledRect] component - layout-based rect representation diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/GlowRect.kt b/common/src/main/kotlin/com/lambda/gui/component/core/GlowRect.kt new file mode 100644 index 000000000..f0f6f610a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/GlowRect.kt @@ -0,0 +1,109 @@ +/* + * 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.RectRenderer.glowRect +import com.lambda.gui.component.layout.Layout + +class GlowRect( + owner: Layout +) : RectLayout(owner) { + @UIRenderPr0p3rty var outerSpread = 1.0 + @UIRenderPr0p3rty var innerSpread = 0.0 + + @UIRenderPr0p3rty var leftTopInnerRadius = 0.0 + @UIRenderPr0p3rty var rightTopInnerRadius = 0.0 + @UIRenderPr0p3rty var rightBottomInnerRadius = 0.0 + @UIRenderPr0p3rty var leftBottomInnerRadius = 0.0 + + init { + onRender { + glowRect( + rect, + outerSpread, + innerSpread, + + leftTopInnerRadius, + rightTopInnerRadius, + rightBottomInnerRadius, + leftBottomInnerRadius, + + leftTopRadius, + rightTopRadius, + rightBottomRadius, + leftBottomRadius, + + leftTopColor, + rightTopColor, + rightBottomColor, + leftBottomColor, + + shade + ) + } + } + + fun setInnerRadius(radius: Double) { + leftTopInnerRadius = radius + rightTopInnerRadius = radius + rightBottomInnerRadius = radius + leftBottomInnerRadius = radius + } + + fun setInnerRadiusH(radiusL: Double, radiusR: Double) { + leftTopInnerRadius = radiusL + rightTopInnerRadius = radiusR + rightBottomInnerRadius = radiusR + leftBottomInnerRadius = radiusL + } + + fun setInnerRadiusV(radiusT: Double, radiusB: Double) { + leftTopInnerRadius = radiusT + rightTopInnerRadius = radiusT + rightBottomInnerRadius = radiusB + leftBottomInnerRadius = radiusB + } + + companion object { + /** + * Creates a [GlowRect] component - layout-based rect representation + */ + @UIBuilder + fun Layout.glow( + block: GlowRect.() -> Unit = {} + ) = GlowRect(this).apply(children::add).apply(block) + + /** + * Adds a [GlowRect] behind given [layout] + */ + @UIBuilder + fun Layout.glowBehind( + layout: Layout, + block: GlowRect.() -> Unit = {} + ) = GlowRect(this).insertLayout(this, layout, false).apply(block) + + /** + * Adds a [GlowRect] over given [layout] + */ + @UIBuilder + fun Layout.glowOver( + layout: Layout, + block: GlowRect.() -> Unit = {} + ) = GlowRect(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/OutlineRect.kt b/common/src/main/kotlin/com/lambda/gui/component/core/OutlineRect.kt index 014f0fd1c..2109096ca 100644 --- a/common/src/main/kotlin/com/lambda/gui/component/core/OutlineRect.kt +++ b/common/src/main/kotlin/com/lambda/gui/component/core/OutlineRect.kt @@ -17,31 +17,23 @@ package com.lambda.gui.component.core -import com.lambda.graphics.renderer.gui.rect.OutlineRectRenderer.outlineRect +import com.lambda.graphics.renderer.gui.RectRenderer.outlineRect import com.lambda.gui.component.layout.Layout -import java.awt.Color class OutlineRect( owner: Layout -) : Layout(owner) { - @UIRenderPr0p3rty var roundRadius = 0.0 - @UIRenderPr0p3rty var glowRadius = 1.0 - - @UIRenderPr0p3rty var leftTopColor: Color = Color.WHITE - @UIRenderPr0p3rty var rightTopColor: Color = Color.WHITE - @UIRenderPr0p3rty var rightBottomColor: Color = Color.WHITE - @UIRenderPr0p3rty var leftBottomColor: Color = Color.WHITE - - @UIRenderPr0p3rty var shade = false +) : RectLayout(owner) { + @UIRenderPr0p3rty var outlineWidth = 1.0 init { - properties.interactionPassthrough = true - onRender { outlineRect( rect, - roundRadius, - glowRadius, + outlineWidth, + leftTopRadius, + rightTopRadius, + rightBottomRadius, + leftBottomRadius, leftTopColor, rightTopColor, rightBottomColor, @@ -51,30 +43,9 @@ class OutlineRect( } } - fun setColor(color: Color) { - leftTopColor = color - rightTopColor = color - rightBottomColor = color - leftBottomColor = color - } - - fun setColorH(colorL: Color, colorR: Color) { - leftTopColor = colorL - rightTopColor = colorR - rightBottomColor = colorR - leftBottomColor = colorL - } - - fun setColorV(colorT: Color, colorB: Color) { - leftTopColor = colorT - rightTopColor = colorT - rightBottomColor = colorB - leftBottomColor = colorB - } - companion object { /** - * Creates an [OutlineRect] component - layout-based rect representation + * Creates a [OutlineRect] component - layout-based rect representation */ @UIBuilder fun Layout.outline( @@ -91,7 +62,7 @@ class OutlineRect( ) = OutlineRect(this).insertLayout(this, layout, false).apply(block) /** - * Creates an [OutlineRect] component - layout-based rect representation + * Adds a [OutlineRect] over given [layout] */ @UIBuilder fun Layout.outlineOver( diff --git a/common/src/main/kotlin/com/lambda/gui/component/core/RectLayout.kt b/common/src/main/kotlin/com/lambda/gui/component/core/RectLayout.kt new file mode 100644 index 000000000..29e66f37c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/core/RectLayout.kt @@ -0,0 +1,83 @@ +/* + * 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 java.awt.Color + +abstract class RectLayout( + owner: Layout +) : Layout(owner) { + @UIRenderPr0p3rty var leftTopRadius = 0.0 + @UIRenderPr0p3rty var rightTopRadius = 0.0 + @UIRenderPr0p3rty var rightBottomRadius = 0.0 + @UIRenderPr0p3rty var leftBottomRadius = 0.0 + + @UIRenderPr0p3rty var leftTopColor: Color = Color.WHITE + @UIRenderPr0p3rty var rightTopColor: Color = Color.WHITE + @UIRenderPr0p3rty var rightBottomColor: Color = Color.WHITE + @UIRenderPr0p3rty var leftBottomColor: Color = Color.WHITE + + @UIRenderPr0p3rty var shade = false + + init { + properties.interactionPassthrough = true + } + + fun setRadius(radius: Double) { + leftTopRadius = radius + rightTopRadius = radius + rightBottomRadius = radius + leftBottomRadius = radius + } + + fun setRadiusH(radiusL: Double, radiusR: Double) { + leftTopRadius = radiusL + rightTopRadius = radiusR + rightBottomRadius = radiusR + leftBottomRadius = radiusL + } + + fun setRadiusV(radiusT: Double, radiusB: Double) { + leftTopRadius = radiusT + rightTopRadius = radiusT + rightBottomRadius = radiusB + leftBottomRadius = radiusB + } + + fun setColor(color: Color) { + leftTopColor = color + rightTopColor = color + rightBottomColor = color + leftBottomColor = color + } + + fun setColorH(colorL: Color, colorR: Color) { + leftTopColor = colorL + rightTopColor = colorR + rightBottomColor = colorR + leftBottomColor = colorL + } + + fun setColorV(colorT: Color, colorB: Color) { + leftTopColor = colorT + rightTopColor = colorT + rightBottomColor = colorB + leftBottomColor = colorB + } +} 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 0f871695a..86cbfc6c5 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 @@ -17,8 +17,8 @@ package com.lambda.gui.component.core -import com.lambda.graphics.renderer.gui.font.FontRenderer -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.drawString import com.lambda.gui.component.HAlign import com.lambda.gui.component.VAlign import com.lambda.gui.component.layout.Layout 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 e7b946d1a..c6c4da26d 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 @@ -20,12 +20,13 @@ package com.lambda.gui.component.layout import com.lambda.graphics.RenderMain import com.lambda.graphics.animation.AnimationTicker import com.lambda.event.events.GuiEvent -import com.lambda.graphics.pipeline.ScissorAdapter +import com.lambda.graphics.renderer.ScissorAdapter import com.lambda.gui.component.HAlign import com.lambda.gui.component.VAlign import com.lambda.gui.component.core.* import com.lambda.util.KeyCode import com.lambda.util.Mouse +import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Rect import com.lambda.util.math.Vec2d @@ -118,12 +119,23 @@ open class Layout( // Structure val children = mutableListOf() var selectedChild: Layout? = null + val root = run { + var own: Layout = owner ?: this + + while (true) { + own = own.owner ?: break + } + + own// as? RootLayout ?: throw IllegalStateException("Root layout is not a ScreenLayout class") + } + protected open val renderSelf: Boolean get() = width > 1 && height > 1 + protected open val updateChildren: Boolean get() = true protected open val scissorRect get() = rect // Inputs protected var mousePosition = Vec2d.ZERO; set(value) { - if (field == value) return + //if (field == value) return field = value selectedChild = if (isHovered) children.lastOrNull { @@ -157,8 +169,9 @@ open class Layout( * @param action The action to be performed. */ @LayoutBuilder - fun T.use(action: T.() -> Unit) { + fun T.use(action: T.() -> Unit): T { action(this) + return this@use } /** @@ -289,18 +302,6 @@ open class Layout( } } - init { - onUpdate { // Update the layout - screenSize = RenderMain.screenSize - - // Update relative position and bounds - ownerX = owner?.positionX ?: ownerX - ownerY = owner?.positionY ?: ownerY - ownerWidth = owner?.width ?: screenSize.x - ownerHeight = owner?.height ?: screenSize.y - } - } - fun onEvent(e: GuiEvent) { // Update self when (e) { @@ -314,6 +315,8 @@ open class Layout( hideActions.forEach { it(this) } } is GuiEvent.Tick -> { + // hack to update hover state once a tick if not moving the mouse + mousePosition = mousePosition tickActions.forEach { it(this) } } is GuiEvent.KeyPress -> { @@ -323,6 +326,14 @@ open class Layout( charTypedActions.forEach { it(this, e.char) } } is GuiEvent.Update -> { + screenSize = RenderMain.screenSize + + // Update relative position and bounds + ownerX = owner?.positionX ?: ownerX + ownerY = owner?.positionY ?: ownerY + ownerWidth = owner?.width ?: screenSize.x + ownerHeight = owner?.height ?: screenSize.y + updateActions.forEach { it(this) } } is GuiEvent.Render -> { @@ -360,6 +371,8 @@ 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 @@ -374,7 +387,7 @@ open class Layout( if (e is GuiEvent.Render) { val block = { renderActions.forEach { it(this) } - if (renderSelf) children.forEach { it.onEvent(e) } + children.forEach { it.onEvent(e) } } if (!properties.scissor) block() @@ -382,6 +395,21 @@ open class Layout( } } + fun buildTree(builder: StringBuilder = StringBuilder(), level: Int = 0): String { + val space = " ".repeat(level) + builder.appendLine( + space + this::class.java.simpleName + " {".repeat(children.isNotEmpty().toInt()) + ) + + children.forEach { + it.buildTree(builder, level + 1) + } + + if (children.isNotEmpty()) builder.appendLine("$space}") + + return builder.toString() + } + companion object { /** * Creates an empty [Layout]. diff --git a/common/src/main/kotlin/com/lambda/gui/component/popup/Popup.kt b/common/src/main/kotlin/com/lambda/gui/component/popup/Popup.kt new file mode 100644 index 000000000..0b6d202e5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/component/popup/Popup.kt @@ -0,0 +1,159 @@ +/* + * 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.popup + +import com.lambda.event.events.GuiEvent +import com.lambda.graphics.animation.Animation.Companion.exp +import com.lambda.gui.component.core.FilledRect.Companion.rect +import com.lambda.gui.component.layout.Layout +import com.lambda.gui.component.layout.Layout.Companion.animationTicker +import com.lambda.gui.component.layout.Layout.Companion.layout +import com.lambda.gui.component.window.Window +import com.lambda.gui.component.window.Window.Companion.window +import com.lambda.module.modules.client.ClickGui.backgroundTint +import com.lambda.util.Mouse +import com.lambda.util.math.multAlpha + +open class Popup( + requestor: Layout, + title: String +) { + private val baseHider = LayoutHideService { base } + protected val windowHider = LayoutHideService { window } + + private val animation = requestor.animationTicker() + private val tintAnimation by animation.exp(0.0, 1.0, 0.6) { windowHider.isShown } + private val root = requestor.root + + private val base: Layout = root.layout { + rect { + onUpdate { + width = this@layout.width + height = this@layout.height + setColor(backgroundTint.multAlpha(tintAnimation)) + } + } + + onUpdate { + properties.interactionPassthrough = !windowHider.isShown + } + }.apply { + destroy() + } + + /** + * The shown window on the popup screen + */ + val window = base.window( + title = title, + draggable = false, + scrollable = false, + minimizing = Window.Minimizing.Disabled, + resizable = false, + autoResize = Window.AutoResize.Disabled + ).apply { + destroy() + } + + fun show() { + windowHider.isShown = true + baseHider.isShown = true + window.onEvent(GuiEvent.Update) + } + + open fun hide() { + windowHider.isShown = false + } + + init { + requestor.apply { + root.onMouse(action = Mouse.Action.Click) { + if (!window.isHovered) hide() + } + + root.onTick { + if (tintAnimation == 0.0 && !windowHider.isShown) + baseHider.isShown = false + } + + root.onUpdate { + base.width = root.width + base.height = root.height + window.positionX = (base.width - window.width) * 0.5 + window.positionY = (base.height - window.height) * 0.5 - window.titleBar.height + + baseHider.update() + windowHider.update() + } + } + } + + abstract class Returnable ( + requestor: Layout, + title: String + ) : Popup(requestor, title) { + private var receiver: ((V) -> Unit)? = null + protected abstract fun buildOutput(): V + + override fun hide() { + if (windowHider.isShown) { + receiver?.invoke(buildOutput()) + receiver = null + } + super.hide() + } + + companion object { + /** + * Shows the given [popupLayout]. + * + * @param block The action to run on the popup exit. + */ + fun popup(popupLayout: Returnable, block: (V) -> Unit) = popupLayout.apply { + receiver = block + show() + } + } + } + + protected class LayoutHideService(val layoutBlock: () -> Layout) { + private var lastShown = false + var isShown = false + + fun update() { + val layout = layoutBlock() + val owner = layout.owner ?: throw IllegalStateException( + "Cannot hide root layout." + ) + + val prev = lastShown + val value = isShown + lastShown = value + + if (prev == value) return + + if (value) { + owner.children += layout + layout.onEvent(GuiEvent.Show) + } else { + owner.children -= layout + layout.onEvent(GuiEvent.Hide) + } + } + } +} \ No newline at end of file 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 0843e6bed..791de159c 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 @@ -19,8 +19,9 @@ package com.lambda.gui.component.window import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.module.modules.client.ClickGui -import com.lambda.gui.ScreenLayout +import com.lambda.gui.RootLayout import com.lambda.gui.component.core.FilledRect.Companion.rect +import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.core.LayoutBuilder import com.lambda.gui.component.core.OutlineRect.Companion.outline import com.lambda.gui.component.layout.Layout @@ -69,16 +70,28 @@ open class Window( val content = windowContent(scrollable) + val glowRect = glow { + onUpdate { + position = this@Window.position + size = this@Window.size + + setColor(ClickGui.glowColor) + setRadius(ClickGui.roundRadius) + + outerSpread = ClickGui.glowWidth * ClickGui.glow.toInt().toDouble() + shade = ClickGui.glowShade + } + } + val outlineRect = outline { onUpdate { position = this@Window.position size = this@Window.size setColor(ClickGui.outlineColor) + setRadius(ClickGui.roundRadius) - roundRadius = ClickGui.roundRadius - glowRadius = ClickGui.outlineWidth * ClickGui.outline.toInt().toDouble() - + outlineWidth = ClickGui.outlineWidth * ClickGui.outline.toInt().toDouble() shade = ClickGui.outlineShade } } @@ -126,7 +139,12 @@ open class Window( get() = !isMinimized set(value) { isMinimized = !value } - var windowWidth = initialSize.x + var windowWidth = initialSize.x; set(value) { + if (field == value) return + field = value + + if (resizeX == null) widthAnimation = value + } var windowHeight = initialSize.y var widthAnimation by animation.exp(0.8, ::windowWidth) @@ -147,9 +165,12 @@ open class Window( init { position = initialPosition - properties.clampPosition = owner is ScreenLayout + properties.clampPosition = owner is RootLayout onUpdate { + // Update it here + content.updateHeight() + width = widthAnimation height = titleBar.height + when (minimizing) { Minimizing.Disabled -> targetHeight 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 9a9d2cc5c..ad997f854 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 @@ -46,6 +46,9 @@ class WindowContent( override val renderSelf: Boolean get() = window.heightAnimation > 0.05 + override val updateChildren: Boolean + get() = window.heightAnimation > 0.05 + /** * Orders the children set vertically */ @@ -73,13 +76,6 @@ class WindowContent( positionX = owner.titleBar.positionX positionY = owner.titleBar.let { it.positionY + it.height } + renderScrollOffset * scrollable.toInt() width = owner.width - - height = ClickGui.padding * 2 - - val lastIndex = children.lastIndex - children.forEachIndexed { i, it -> - height += layoutHeight(it, false, i == lastIndex) - } } onShow { @@ -113,6 +109,15 @@ class WindowContent( } } + fun updateHeight() { + height = ClickGui.padding * 2 + + val lastIndex = children.lastIndex + children.forEachIndexed { i, it -> + height += layoutHeight(it, false, i == lastIndex) + } + } + private fun layoutHeight(layout: Layout, animate: Boolean, isLast: Boolean = false): Double { var height = layout.height + ClickGui.listStep * (!isLast).toInt() val animated = layout as? AnimatedChild ?: return height diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/AnimatedChild.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/AnimatedChild.kt index 923065995..69c6392d2 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/AnimatedChild.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/AnimatedChild.kt @@ -19,13 +19,18 @@ package com.lambda.gui.impl.clickgui.core import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.gui.component.HAlign +import com.lambda.gui.component.core.FilledRect +import com.lambda.gui.component.core.FilledRect.Companion.rect +import com.lambda.gui.component.core.FilledRect.Companion.rectBehind import com.lambda.gui.component.layout.Layout import com.lambda.gui.component.window.Window -import com.lambda.gui.impl.clickgui.module.SettingLayout +import com.lambda.gui.impl.clickgui.module.ModuleLayout +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout import com.lambda.module.modules.client.ClickGui import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d import com.lambda.util.math.lerp +import com.lambda.util.math.multAlpha import com.lambda.util.math.setAlpha import com.lambda.util.math.transform import java.awt.Color @@ -71,7 +76,7 @@ abstract class AnimatedChild( speed = transform(index.toDouble(), 0.0, lastIndex.toDouble(), start, end) } - if ((this as? SettingLayout<*, *>)?.isVisible == true) speed *= 0.8 + if ((this as? SettingLayout<*>)?.isVisible == true) speed *= 0.8 speed + isShownInternal.toInt() * 0.1 }) { isShownInternal }.apply { @@ -107,6 +112,26 @@ abstract class AnimatedChild( if (isHovered) lastHover = System.currentTimeMillis() } + onWindowExpand { + if (ClickGui.multipleSettingWindows) return@onWindowExpand + + val close = if (this !is ModuleLayout) { + owner.children.filterIsInstance() + } else { + val base = owner // window content + .owner // window + ?.owner // environment with windows + + base?.children?.filterIsInstance()?.flatMap { window -> + window.content.children.filterIsInstance() + } ?: mutableListOf() + } + + close.forEach { + if (it != this) it.isMinimized = true + } + } + titleBar.textField.use { textHAlignment = HAlign.LEFT @@ -128,7 +153,71 @@ abstract class AnimatedChild( listOf( titleBarBackground, contentBackground, - outlineRect + outlineRect, + glowRect ).forEach(Layout::destroy) } + + companion object { + fun AnimatedChild.animatedBackground( + enableProgress: () -> Double = { hoverAnimation } + ) = rectBehind(titleBar) { + // base rect with lowest z to avoid children overlying + onUpdate { + val enableAnimation = enableProgress() + + rect = this@animatedBackground.rect.shrink(shrink) + shade = ClickGui.backgroundShade + + val openRev = 1.0 - openAnimation // 1.0 <-> 0.0 + val openRevSigned = openRev * 2 - 1 // 1.0 <-> -1.0 + val enableRev = 1.0 - enableAnimation // 1.0 <-> 0.0 + + var progress = enableAnimation + + // hover: +0.1 to alpha if minimized, -0.1 to alpha if maximized and enabled + progress += hoverAnimation * ClickGui.moduleHoverAccent * + lerp(enableAnimation, 1.0, openRevSigned) + + // +0.4 to alpha if opened and disabled + progress += openAnimation * ClickGui.moduleOpenAccent * enableRev + + // interpolate and set the color + setColor( + lerp(progress, + ClickGui.moduleDisabledColor, + ClickGui.moduleEnabledColor + ).multAlpha(showAnimation) + ) + + setRadius(hoverAnimation) + + if (isLast && ClickGui.autoResize) { + leftBottomRadius = ClickGui.roundRadius - (ClickGui.padding + shrink) + rightBottomRadius = leftBottomRadius + } + } + + rect { // hover fx + onUpdate { + val base = this@rect.owner as FilledRect + + position = base.position + size = base.size + shade = base.shade + + leftTopRadius = base.leftTopRadius + rightTopRadius = base.rightTopRadius + rightBottomRadius = base.rightBottomRadius + leftBottomRadius = base.leftBottomRadius + + val hoverColor = Color.WHITE.setAlpha( + ClickGui.moduleHoverAccent * hoverAnimation * (1.0 - openAnimation) * showAnimation + ) + + setColorH(hoverColor.setAlpha(0.0), hoverColor) + } + } + } + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/SliderLayout.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/SliderLayout.kt index 55464f924..407108631 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/SliderLayout.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/SliderLayout.kt @@ -24,9 +24,10 @@ import com.lambda.gui.component.core.OutlineRect.Companion.outline import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.core.insertLayout import com.lambda.gui.component.layout.Layout -import com.lambda.gui.impl.clickgui.module.settings.SettingSlider +import com.lambda.gui.impl.clickgui.module.setting.SettingSlider import com.lambda.module.modules.client.ClickGui import com.lambda.util.Mouse +import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d import com.lambda.util.math.multAlpha import com.lambda.util.math.setAlpha @@ -34,12 +35,12 @@ import com.lambda.util.math.transform import java.awt.Color class SliderLayout( - owner: Layout + owner: Layout, + private val isVertical: Boolean ) : AnimatedChild(owner, "") { // Not a great solution - private val setting = owner as? SettingSlider<*, *> + private val setting = owner as? SettingSlider<*> private val showAnim get() = setting?.showAnimation ?: showAnimation - private val hoverAnim get() = setting?.hoverAnimation ?: hoverAnimation private val pressedBut get() = setting?.pressedButton ?: pressedButton // Actions @@ -74,9 +75,9 @@ class SliderLayout( } private val dragProgress: Double get() = transform( - mousePosition.x - bg.positionX, - 0.0, bg.width, - 0.0, 1.0 + if (isVertical) mousePosition.y - bg.positionY else mousePosition.x - bg.positionX, + 0.0, if (isVertical) bg.height else bg.width, + isVertical.toInt().toDouble(), 1.0 - isVertical.toInt().toDouble() ).coerceIn(0.0, 1.0) init { @@ -93,7 +94,13 @@ class SliderLayout( rect { onUpdate { // progress rect = bg.rect - width *= renderProgress + + if (isVertical) { + height *= renderProgress + positionY = bg.positionY + bg.height - height + } else { + width *= renderProgress + } shade = ClickGui.backgroundShade setColor(Color.WHITE.setAlpha(0.25 * showAnim)) @@ -106,8 +113,9 @@ class SliderLayout( rect = bg.rect val c = Color.BLACK.setAlpha(0.3 * showAnim) val a = transform(renderProgress, 0.5, 1.0, 0.0, 1.0).coerceIn(0.0, 1.0) + outlineWidth = 0.5 setColorH(c, c.multAlpha(a)) - roundRadius = 100.0 + setRadius(100.0) } } } @@ -118,8 +126,9 @@ class SliderLayout( */ @UIBuilder fun Layout.slider( + isVertical: Boolean = false, block: SliderLayout.() -> Unit = {} - ) = SliderLayout(this).apply(children::add).apply(block) + ) = SliderLayout(this, isVertical).apply(children::add).apply(block) /** * Adds a [SliderLayout] behind given [layout] @@ -127,8 +136,9 @@ class SliderLayout( @UIBuilder fun Layout.sliderBehind( layout: Layout, + isVertical: Boolean = false, block: SliderLayout.() -> Unit = {} - ) = SliderLayout(this).insertLayout(this, layout, false).apply(block) + ) = SliderLayout(this, isVertical).insertLayout(this, layout, false).apply(block) /** * Adds a [SliderLayout] over given [layout] @@ -136,7 +146,8 @@ class SliderLayout( @UIBuilder fun Layout.sliderOver( layout: Layout, + isVertical: Boolean = false, block: SliderLayout.() -> Unit = {} - ) = SliderLayout(this).insertLayout(this, layout, true).apply(block) + ) = SliderLayout(this, isVertical).insertLayout(this, layout, true).apply(block) } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/ModuleLayout.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/ModuleLayout.kt index b07718345..a50074d66 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/ModuleLayout.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/ModuleLayout.kt @@ -29,6 +29,7 @@ import com.lambda.gui.component.layout.Layout import com.lambda.gui.component.window.Window import com.lambda.gui.impl.clickgui.ModuleWindow import com.lambda.gui.impl.clickgui.core.AnimatedChild +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout import com.lambda.util.Mouse import com.lambda.util.math.* import java.awt.Color @@ -49,63 +50,7 @@ class ModuleLayout( private var enableAnimation by animation.exp(0.0, 1.0, 0.6, module::isEnabled) - val backgroundRect = rectBehind(titleBar) { // base rect with lowest y to avoid children overlying - onUpdate { - rect = this@ModuleLayout.rect.shrink(shrink) - shade = ClickGui.backgroundShade - - val openRev = 1.0 - openAnimation // 1.0 <-> 0.0 - val openRevSigned = openRev * 2 - 1 // 1.0 <-> -1.0 - val enableRev = 1.0 - enableAnimation // 1.0 <-> 0.0 - - var progress = enableAnimation - - // hover: +0.1 to alpha if minimized, -0.1 to alpha if maximized and enabled - progress += hoverAnimation * ClickGui.moduleHoverAccent * - lerp(enableAnimation, 1.0, openRevSigned) - - // +0.4 to alpha if opened and disabled - progress += openAnimation * ClickGui.moduleOpenAccent * enableRev - - // interpolate and set the color - setColor( - lerp(progress, - ClickGui.moduleDisabledColor, - ClickGui.moduleEnabledColor - ).multAlpha(showAnimation) - ) - - setRadius(hoverAnimation) - - if (isLast && ClickGui.autoResize) { - leftBottomRadius = ClickGui.roundRadius - (ClickGui.padding + shrink) - rightBottomRadius = leftBottomRadius - } - } - - rect { // hover fx - onUpdate { - val base = this@rect.owner as FilledRect - - position = base.position - size = base.size - shade = base.shade - - setRadius( - base.leftTopRadius, - base.rightTopRadius, - base.rightBottomRadius, - base.leftBottomRadius - ) - - val hoverColor = Color.WHITE.setAlpha( - ClickGui.moduleHoverAccent * hoverAnimation * (1.0 - openAnimation) * showAnimation - ) - - setColorH(hoverColor.setAlpha(0.0), hoverColor) - } - } - } + val backgroundRect = animatedBackground(::enableAnimation) init { backgroundTint() @@ -136,25 +81,14 @@ class ModuleLayout( cursorController.setCursor(cursor) } - onWindowExpand { - if (ClickGui.multipleSettingWindows) return@onWindowExpand - - val base = owner // window content - .owner // window - ?.owner // environment with windows - ?: return@onWindowExpand - - base.children.filterIsInstance().forEach { window -> - window.content.children.filterIsInstance().forEach { module -> - if (module != this) module.isMinimized = true - } + val settings = module.settings.mapNotNull { setting -> + content.layoutOf(setting) + }.map { it as SettingLayout<*> }.onEach { + it.onUpdate { + width = this@ModuleLayout.content.width } } - val settings = module.settings.map { setting -> - content.layoutOf(setting) - }.filterIsInstance>() - val minimizeSettings = { settings.forEach { it.isMinimized = true @@ -181,7 +115,7 @@ class ModuleLayout( */ @UIBuilder fun Window.backgroundTint(tintTitleBar: Boolean = false) { - check(this is SettingLayout<*, *> || this is ModuleLayout || this is ModuleWindow) + check(this is SettingLayout<*> || this is ModuleLayout || this is ModuleWindow) val base = this@backgroundTint diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/SettingLayout.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingLayout.kt similarity index 65% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/SettingLayout.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingLayout.kt index 20183318b..0e7059646 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/SettingLayout.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingLayout.kt @@ -15,26 +15,28 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module +package com.lambda.gui.impl.clickgui.module.setting -import com.lambda.config.AbstractSetting +import com.lambda.gui.component.core.LayoutBuilder import com.lambda.module.modules.client.ClickGui import com.lambda.gui.component.layout.Layout import com.lambda.gui.impl.clickgui.module.ModuleLayout.Companion.backgroundTint import com.lambda.gui.impl.clickgui.core.AnimatedChild import com.lambda.util.math.* +import kotlin.reflect.KMutableProperty0 /** * A base class for setting layouts. */ -abstract class SettingLayout > ( +abstract class SettingLayout ( owner: Layout, - val setting: T, - private val expandable: Boolean = false + name: String, + private val property: KMutableProperty0, + expandable: Boolean = false ) : AnimatedChild( owner, - setting.name, - Vec2d.ZERO, Vec2d.ZERO, + name, + Vec2d.ZERO, Vec2d(110.0, 0.0), false, false, if (expandable) Minimizing.Absolute else Minimizing.Disabled, false, @@ -42,20 +44,33 @@ abstract class SettingLayout > ( ) { protected val cursorController = cursorController() - var settingDelegate by setting - val isVisible get() = setting.visibility() + protected var settingDelegate: V + get() = property.get() + set(value) { + if (property.get() == value) return + property.set(value) + onValueSet.forEach { it(value) } + } - override val isShown: Boolean get() = super.isShown && isVisible + private var onValueSet = mutableListOf<(V) -> Unit>() + private var getVisibilityBlock = { true } + val isVisible get() = getVisibilityBlock() + + @LayoutBuilder + fun visibility(action: () -> Boolean) { + getVisibilityBlock = { action() } + } + @LayoutBuilder + fun onValueSet(action: (V) -> Unit) { + onValueSet += action + } + override val isShown: Boolean get() = super.isShown && isVisible init { isMinimized = true - onUpdate { - width = owner.width - } - titleBar.onUpdate { height = ClickGui.settingsHeight } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/SettingSlider.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingSlider.kt similarity index 88% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/SettingSlider.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingSlider.kt index 7b43339d7..932fec471 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/SettingSlider.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/SettingSlider.kt @@ -15,26 +15,26 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module.settings +package com.lambda.gui.impl.clickgui.module.setting -import com.lambda.config.AbstractSetting import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.gui.component.HAlign import com.lambda.gui.component.VAlign import com.lambda.gui.component.core.TextField.Companion.textField import com.lambda.gui.component.layout.Layout import com.lambda.gui.impl.clickgui.core.SliderLayout.Companion.sliderBehind -import com.lambda.gui.impl.clickgui.module.SettingLayout import com.lambda.module.modules.client.ClickGui import com.lambda.util.math.lerp +import kotlin.reflect.KMutableProperty0 -abstract class SettingSlider >( - owner: Layout, setting: T -) : SettingLayout(owner, setting, false) { +abstract class SettingSlider ( + owner: Layout, + name: String, + field: KMutableProperty0 +) : SettingLayout(owner, name, field) { abstract val settingValue: String private var changeAnimation by animation.exp(0.0, 1.0, 0.5) { true } - private val sliderHeight = 3.0 protected val slider = sliderBehind(titleBar) { diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/BooleanButton.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/BooleanButton.kt similarity index 82% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/BooleanButton.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/BooleanButton.kt index 35fa632a0..8fd8c770e 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/BooleanButton.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/BooleanButton.kt @@ -15,27 +15,28 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module.settings +package com.lambda.gui.impl.clickgui.module.setting.settings -import com.lambda.config.settings.comparable.BooleanSetting import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.module.modules.client.ClickGui import com.lambda.gui.component.core.FilledRect.Companion.rect -import com.lambda.gui.component.core.OutlineRect.Companion.outline +import com.lambda.gui.component.core.GlowRect.Companion.glow import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout -import com.lambda.gui.impl.clickgui.module.SettingLayout +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout import com.lambda.util.Mouse import com.lambda.util.math.Rect import com.lambda.util.math.Vec2d import com.lambda.util.math.lerp import com.lambda.util.math.setAlpha import java.awt.Color +import kotlin.reflect.KMutableProperty0 class BooleanButton( owner: Layout, - setting: BooleanSetting -) : SettingLayout(owner, setting) { + name: String, + field: KMutableProperty0 +) : SettingLayout(owner, name, field) { private val activeAnimation by animation.exp(0.0, 1.0, 0.6, ::settingDelegate) init { @@ -81,26 +82,26 @@ class BooleanButton( } } - outline { + glow { onUpdate { rect = checkBox.rect - roundRadius = 100.0 - glowRadius = 5.0 + setRadius(100.0) + outerSpread = 5.0 setColor(Color.BLACK.setAlpha(0.1 * showAnimation)) } } onMouseAction(Mouse.Button.Left) { - setting.value = !setting.value + settingDelegate = !settingDelegate } } companion object { /** - * Creates a [BooleanButton] - visual representation of the [BooleanSetting] + * Creates a [BooleanButton] */ @UIBuilder - fun Layout.booleanSetting(setting: BooleanSetting) = - BooleanButton(this, setting).apply(children::add) + fun Layout.booleanSetting(name: String, field: KMutableProperty0) = + BooleanButton(this, name, field).apply(children::add) } } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/ColorPicker.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/ColorPicker.kt new file mode 100644 index 000000000..192fc0736 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/ColorPicker.kt @@ -0,0 +1,206 @@ +/* + * 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.impl.clickgui.module.setting.settings + +import com.lambda.graphics.renderer.gui.TextureRenderer +import com.lambda.graphics.shader.Shader.Companion.shader +import com.lambda.gui.component.core.FilledRect.Companion.rect +import com.lambda.gui.component.core.OutlineRect.Companion.outline +import com.lambda.gui.component.core.UIBuilder +import com.lambda.gui.component.layout.Layout +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout +import com.lambda.gui.impl.clickgui.module.setting.settings.NumberSlider.Companion.numberSlider +import com.lambda.module.modules.client.ClickGui +import com.lambda.util.Mouse +import com.lambda.util.StringUtils.capitalize +import com.lambda.util.math.MathUtils.toDegree +import com.lambda.util.math.MathUtils.toRadian +import com.lambda.util.math.Vec2d +import com.lambda.util.math.a +import com.lambda.util.math.lerp +import com.lambda.util.math.multAlpha +import com.lambda.util.math.setAlpha +import com.lambda.util.math.transform +import net.minecraft.util.math.Vec3d +import java.awt.Color +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.hypot +import kotlin.math.sin +import kotlin.math.sqrt +import kotlin.reflect.KMutableProperty0 + +class ColorPicker( + owner: Layout, + name: String, + field: KMutableProperty0, +) : SettingLayout(owner, name, field, true) { + + private var hsb get() = Color.RGBtoHSB(settingDelegate.red, settingDelegate.green, settingDelegate.blue, null).let { + Vec3d(it[0].toDouble(), it[1].toDouble(), it[2].toDouble()) + }; set(value) { + settingDelegate = Color(Color.HSBtoRGB(value.x.toFloat(), value.y.toFloat(), value.z.toFloat())).setAlpha(alpha) + } + + private var hue get() = hsb.x; set(value) { + val cache = hsb + if (cache.x == value) return + hsb = Vec3d(value, cache.y, cache.z) + } + + private var saturation get() = hsb.y; set(value) { + val cache = hsb + if (cache.y == value) return + hsb = Vec3d(cache.x, value, cache.z) + } + + private var brightness get() = hsb.z; set(value) { + val cache = hsb + if (cache.z == value) return + hsb = Vec3d(cache.x, cache.y, value) + } + + private var alpha get() = settingDelegate.a; set(value) { + settingDelegate = settingDelegate.setAlpha(value) + } + + init { + val preview = rect { + val shrink = 3.0 + + onUpdate { + rect = titleBar.rect + .moveFirst(Vec2d.RIGHT * (titleBar.width - titleBar.height)) + .shrink(shrink + (1.0 - showAnimation)) + + Vec2d.RIGHT * lerp(showAnimation, 5.0, -ClickGui.fontOffset + shrink) + + setColor(settingDelegate.multAlpha(showAnimation)) + setRadius(100.0) + } + } + + outline { + onUpdate { + rect = preview.rect + setRadius(100.0) + outlineWidth = 0.5 + setColor(Color.BLACK.setAlpha(0.1 * showAnimation)) + } + } + + content.layout { + val getU = { transform(mousePosition.x, positionX, positionX + width, 0.0, 1.0) } + val getV = { transform(mousePosition.y, positionY, positionY + height, 0.0, 1.0) } + + onUpdate { + width = content.width * 0.5 + positionX = content.positionX + (content.width - width) * 0.5 + height = width + } + + onRender { + hueCircleShader.use() + TextureRenderer.drawInternal(rect) + } + + onMouseMove { + if (pressedButton != Mouse.Button.Left) return@onMouseMove + + val (u, v) = getU() to getV() + hue = uvToHue(u, v) + saturation = hypot(u - 0.5, v - 0.5).coerceIn(0.0, 0.5) * 2 + } + + val circle = this + val knobSize = 2.0 + + outline { + width = knobSize; height = knobSize + + setColor(Color.BLACK) + outlineWidth = 1.0 + setRadius(100.0) + + onUpdate { + val uv = if (circle.pressedButton == Mouse.Button.Left) clampToCircle(Vec2d(getU(), getV())) + else hueToUv(hue, saturation) + + positionX = transform(uv.x, 0.0, 1.0, circle.positionX, circle.positionX + circle.width) - knobSize * 0.5 + positionY = transform(uv.y, 0.0, 1.0, circle.positionY, circle.positionY + circle.height) - knobSize * 0.5 + } + } + } + + listOf(::brightness, ::alpha).map { + content.numberSlider(it.name.capitalize(), "", 0.0, 1.0, 0.01, it).apply { + forceRoundDisplayValue = true + } + }.onEach { + it.onUpdate { + width = this@ColorPicker.content.width + } + } + + content.listify() + } + + companion object { + private val hueCircleShader = shader("renderer/hue_circle") + + private fun uvToHue(u: Double, v: Double): Double { + val x = (u - 0.5) * 2.0 + val y = ((1.0 - v) - 0.5) * -2.0 + + var hue = atan2(y, x).toDegree() + 90.0 + if (hue < 0) hue += 360.0 + return hue.div(360.0).coerceIn(0.0, 1.0) + } + + private fun hueToUv(hue: Double, radius: Double): Vec2d { + val angle = (hue * 360 - 90.0).toRadian() + + val x = cos(angle) * radius + val y = sin(angle) * radius + + val u = (x * 0.5f) + 0.5f + val v = (y * -0.5f) + 0.5f + + return Vec2d(u, 1.0 - v) + } + + private fun clampToCircle(uv: Vec2d): Vec2d { + val center = Vec2d(0.5f, 0.5f) + val radius = 0.5f + val dx = uv.x - center.x + val dy = uv.y - center.y + val dist = sqrt(dx * dx + dy * dy) + + return if (dist > radius) { + val scale = radius / dist + Vec2d(center.x + dx * scale, center.y + dy * scale) + } else uv + } + + /** + * Creates a [ColorPicker] + */ + @UIBuilder + fun Layout.colorPicker(name: String, field: KMutableProperty0) = + ColorPicker(this, name, field).apply(children::add) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/EnumSlider.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/EnumSlider.kt similarity index 69% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/EnumSlider.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/EnumSlider.kt index d1d2364ae..a0632337e 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/EnumSlider.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/EnumSlider.kt @@ -15,19 +15,23 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module.settings +package com.lambda.gui.impl.clickgui.module.setting.settings import com.lambda.config.settings.comparable.EnumSetting +import com.lambda.config.settings.comparable.EnumSetting.Companion.enumValues import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout import com.lambda.util.NamedEnum +import com.lambda.gui.impl.clickgui.module.setting.SettingSlider import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.transform +import kotlin.reflect.KMutableProperty0 class EnumSlider >( owner: Layout, - setting: EnumSetting -) : SettingSlider>(owner, setting) { + name: String, + field: KMutableProperty0 +) : SettingSlider(owner, name, field) { override val settingValue: String get() = (settingDelegate as? NamedEnum)?.displayName ?: settingDelegate.name @@ -35,13 +39,13 @@ class EnumSlider >( slider.progress { transform( value = settingDelegate.ordinal.toDouble(), - ogStart = 0.0, ogEnd = setting.enumValues.lastIndex.toDouble(), + ogStart = 0.0, ogEnd = settingDelegate.enumValues.lastIndex.toDouble(), nStart = 0.0, nEnd = 1.0 ) } slider.onSlide { - settingDelegate = setting.enumValues.let { entries -> + settingDelegate = settingDelegate.enumValues.let { entries -> entries[(it * entries.size) .floorToInt() .coerceIn(0, entries.size - 1)] @@ -54,7 +58,10 @@ class EnumSlider >( * Creates an [EnumSlider] - visual representation of the [EnumSetting] */ @UIBuilder - fun > Layout.enumSetting(setting: EnumSetting) = - EnumSlider(this, setting).apply(children::add) + @Suppress("UNCHECKED_CAST") + fun > Layout.enumSetting( + name: String, + field: KMutableProperty0> + ) = EnumSlider(this, name, field as KMutableProperty0).apply(children::add) } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/KeybindPicker.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/KeybindPicker.kt similarity index 79% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/KeybindPicker.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/KeybindPicker.kt index ba786a44a..f6f2d1b0d 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/KeybindPicker.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/KeybindPicker.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module.settings +package com.lambda.gui.impl.clickgui.module.setting.settings import com.lambda.config.settings.complex.KeyBindSetting import com.lambda.gui.component.HAlign @@ -23,26 +23,37 @@ import com.lambda.gui.component.core.TextField.Companion.textField import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout import com.lambda.gui.impl.clickgui.module.ModuleLayout -import com.lambda.gui.impl.clickgui.module.SettingLayout +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout import com.lambda.util.KeyCode import com.lambda.util.Mouse import com.lambda.util.extension.displayValue +import kotlin.reflect.KMutableProperty0 class KeybindPicker( owner: Layout, - setting: KeyBindSetting -) : SettingLayout(owner, setting) { + name: String, + field: KMutableProperty0 +) : SettingLayout(owner, name, field) { private var isListening = false init { textField { onUpdate { mergeFrom(titleBar.textField) - text = if (isListening) "..." else setting.value.displayValue + text = if (isListening) "..." else settingDelegate.displayValue textHAlignment = HAlign.RIGHT } } + onShow { + isListening = false + } + + onTick { + val module = (owner.owner as? ModuleLayout) ?: return@onTick + if (module.isMinimized) isListening = false + } + onMouseAction(Mouse.Button.Left) { isListening = !isListening } @@ -53,15 +64,6 @@ class KeybindPicker( settingDelegate = key isListening = false } - - onShow { - isListening = false - } - - onTick { - val module = (owner.owner as? ModuleLayout) ?: return@onTick - if (module.isMinimized) isListening = false - } } companion object { @@ -69,7 +71,7 @@ class KeybindPicker( * Creates a [KeybindPicker] - visual representation of the [KeyBindSetting] */ @UIBuilder - fun Layout.keybindSetting(setting: KeyBindSetting) = - KeybindPicker(this, setting).apply(children::add) + fun Layout.keybindSetting(name: String, field: KMutableProperty0) = + KeybindPicker(this, name, field).apply(children::add) } } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/NumberSlider.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/NumberSlider.kt similarity index 60% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/NumberSlider.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/NumberSlider.kt index 6999a7950..e585c10f2 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/NumberSlider.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/NumberSlider.kt @@ -15,24 +15,33 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.module.settings +package com.lambda.gui.impl.clickgui.module.setting.settings import com.lambda.config.settings.NumericSetting import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout +import com.lambda.gui.impl.clickgui.module.setting.SettingSlider import com.lambda.util.math.MathUtils.roundToStep import com.lambda.util.math.MathUtils.typeConvert import com.lambda.util.math.lerp import com.lambda.util.math.transform +import kotlin.reflect.KMutableProperty0 class NumberSlider ( - owner: Layout, setting: NumericSetting -) : SettingSlider>(owner, setting) where V : Number, V : Comparable { - private val min = setting.range.start.toDouble() - private val max = setting.range.endInclusive.toDouble() + owner: Layout, + name: String, private val unit: String, + minV: V, maxV: V, stepV: V, + field: KMutableProperty0 +) : SettingSlider(owner, name, field) where V : Number, V : Comparable { + var min = minV.toDouble() + var max = maxV.toDouble() + var step = stepV.toDouble() + var forceRoundDisplayValue = false override val settingValue: String - get() = "${setting.value}${setting.unit}" + get() = "${settingDelegate.let { + if (forceRoundDisplayValue) it.roundToStep(step) else it + }}${unit}" init { slider.progress { @@ -45,7 +54,7 @@ class NumberSlider ( slider.onSlide { settingDelegate = settingDelegate.typeConvert( - lerp(it, min, max).roundToStep(setting.step).toDouble() + lerp(it, min, max).roundToStep(step).toDouble() ) } } @@ -55,7 +64,11 @@ class NumberSlider ( * Creates an [NumberSlider] - visual representation of the [NumericSetting] */ @UIBuilder - fun Layout.numericSetting(setting: NumericSetting) where T : Number, T : Comparable = - NumberSlider(this, setting).apply(children::add) + fun Layout.numberSlider( + name: String, unit: String, + minV: T, maxV: T, stepV: T, + field: KMutableProperty0 + ) where T : Number, T : Comparable = + NumberSlider(this, name, unit, minV, maxV, stepV, field).apply(children::add) } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/settings/UnitButton.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/UnitButton.kt similarity index 53% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/settings/UnitButton.kt rename to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/UnitButton.kt index 290a5c7ea..b90c8eaeb 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/settings/UnitButton.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/setting/settings/UnitButton.kt @@ -15,30 +15,42 @@ * along with this program. If not, see . */ -package com.lambda.gui.impl.clickgui.settings +package com.lambda.gui.impl.clickgui.module.setting.settings -import com.lambda.config.settings.FunctionSetting import com.lambda.gui.component.core.UIBuilder import com.lambda.gui.component.layout.Layout -import com.lambda.gui.impl.clickgui.module.SettingLayout +import com.lambda.gui.impl.clickgui.module.setting.SettingLayout import com.lambda.util.Mouse +import kotlin.reflect.KMutableProperty0 class UnitButton ( owner: Layout, - setting: FunctionSetting, -) : SettingLayout<() -> T, FunctionSetting>(owner, setting) { + name: String, + field: KMutableProperty0<() -> T>, +) : SettingLayout<() -> T>(owner, name, field) { init { - onMouse(Mouse.Button.Left, Mouse.Action.Click) { - setting.value() + onMouseAction(Mouse.Button.Left) { + settingDelegate() } } companion object { /** - * Creates a [UnitButton] - visual representation of the [FunctionSetting] + * Creates a [UnitButton] */ @UIBuilder - fun Layout.unitSetting(setting: FunctionSetting) = - UnitButton(this, setting).apply(children::add) + fun Layout.unitButton(name: String, field: KMutableProperty0<() -> T>) = + UnitButton(this, name, field).apply(children::add) + + /** + * Creates a [UnitButton] + */ + @UIBuilder + fun Layout.unitButton(name: String, block: () -> T) = + UnitButton(this, name, CapturedUnit(block)::block).apply(children::add) + + private class CapturedUnit ( + var block: () -> T + ) } } diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt index 15cdb1ec0..b22ee6c95 100644 --- a/common/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt @@ -17,16 +17,25 @@ package com.lambda.module +import com.lambda.event.events.GuiEvent 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.gui.component.DockingRect +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 +import com.lambda.gui.component.core.TextField.Companion.textField +import com.lambda.gui.component.core.UIBuilder +import com.lambda.gui.component.layout.Layout +import com.lambda.gui.component.layout.Layout.Companion.layout +import com.lambda.module.modules.client.ClickGui import com.lambda.module.tag.ModuleTag -import com.lambda.gui.component.HAlign -import com.lambda.gui.component.VAlign import com.lambda.util.KeyCode +import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.Vec2d +import kotlin.math.max +import kotlin.math.min abstract class HudModule( name: String, @@ -35,47 +44,137 @@ abstract class HudModule( alwaysListening: Boolean = false, enabledByDefault: Boolean = false, defaultKeybind: KeyCode = KeyCode.UNBOUND, + background: Boolean = true ) : Module(name, description, defaultTags, alwaysListening, enabledByDefault, defaultKeybind) { - protected abstract val width: Double - protected abstract val height: Double - - private val rectHandler = object : DockingRect() { - private var relativePosX by setting("Position X", 0.0, -10000.0..10000.0, 0.1) { false } - private var relativePosY by setting("Position Y", 0.0, -10000.0..10000.0, 0.1) { false } - override var relativePos - get() = Vec2d(relativePosX, relativePosY) - set(value) { - relativePosX = value.x; relativePosY = value.y + + protected val animation = AnimationTicker() + + private val base by lazy { + Layout(null).apply { + if (!background) return@apply + + rect { + onUpdate { + position = this@apply.position + size = this@apply.size + + setColor(ClickGui.backgroundColor) + setRadius(ClickGui.roundRadius) + + shade = ClickGui.backgroundShade + } } - override val width get() = this@HudModule.width - override val height get() = this@HudModule.height + glow { + onUpdate { + position = this@apply.position + size = this@apply.size - override val autoDocking by setting("Auto Docking", true).onValueChange { _, _ -> autoDocking() } + setColor(ClickGui.glowColor) + setRadius(ClickGui.roundRadius) - override var dockingH by setting("Docking H", HAlign.LEFT) { !autoDocking } - .onValueChange { from, to -> - val delta = to.multiplier - from.multiplier - relativePosX += delta * (size.x - screenSize.x) + outerSpread = ClickGui.glowWidth * ClickGui.glow.toInt().toDouble() + shade = ClickGui.glowShade + } } - override var dockingV by setting("Docking V", VAlign.TOP) { !autoDocking } - .onValueChange { from, to -> - val delta = to.multiplier - from.multiplier - relativePosY += delta * (size.y - screenSize.y) + outline { + onUpdate { + position = this@apply.position + size = this@apply.size + + setColor(ClickGui.outlineColor) + setRadius(ClickGui.roundRadius) + + outlineWidth = ClickGui.outlineWidth * ClickGui.outline.toInt().toDouble() + shade = ClickGui.outlineShade + } } + } } - var position by rectHandler::position - val rect by rectHandler::rect - val animation = AnimationTicker() + protected val content by lazy { + base.layout { + onUpdate { + val maxRadius = min(size.x, size.y) + val w = ClickGui.roundRadius.coerceAtMost(maxRadius) + val h = if (this@HudModule is Text) 0.0 else ClickGui.roundRadius.coerceAtMost(maxRadius) + val padding = Vec2d(max(ClickGui.hudPadding, w), max(ClickGui.hudPadding, h)) + base.size = size + padding * 2 + position = base.position + padding + } + } + } - protected fun onRender(block: () -> Unit) = - listen { block() } + private val scheduled = mutableListOf<() -> Unit>() init { listen { animation.tick() } + + listen { + while (scheduled.isNotEmpty()) { + scheduled.removeAt(0).invoke() + } + + base.onEvent(GuiEvent.Update) + base.onEvent(GuiEvent.Render) + } + + onEnable { + base.onEvent(GuiEvent.Show) + } + + onDisable { + base.onEvent(GuiEvent.Hide) + } + } + + @UIBuilder + protected fun build(block: Layout.() -> Unit) { + scheduled += { content.apply(block) } + } + + @UIBuilder + protected fun Layout.customDrawable(render: Layout.() -> Unit) { + layout { + onUpdate { + width = this@customDrawable.width + height = this@customDrawable.height + } + + onRender { + render(this@layout) + } + } + } + + fun getRootLayout() = base + + abstract class Text( + name: String, + description: String = "", + defaultTags: Set = setOf(), + alwaysListening: Boolean = false, + enabledByDefault: Boolean = false, + defaultKeybind: KeyCode = KeyCode.UNBOUND + ) : HudModule(name, description, defaultTags, alwaysListening, enabledByDefault, defaultKeybind) { + abstract fun getText(): String + + init { + build { + val text = textField { + onUpdate { + text = getText() + } + } + + onUpdate { + content.width = text.textWidth + content.height = text.textHeight + } + } + } } } diff --git a/common/src/main/kotlin/com/lambda/module/Module.kt b/common/src/main/kotlin/com/lambda/module/Module.kt index 48a42e148..fb8beff11 100644 --- a/common/src/main/kotlin/com/lambda/module/Module.kt +++ b/common/src/main/kotlin/com/lambda/module/Module.kt @@ -115,7 +115,7 @@ abstract class Module( enabledByDefault: Boolean = false, defaultKeybind: KeyCode = KeyCode.UNBOUND, ) : Nameable, Muteable, Configurable(ModuleConfig) { - private val isEnabledSetting = setting("Enabled", enabledByDefault, visibility = { false }) + private val isEnabledSetting = setting("Enabled", enabledByDefault) { false } private val keybindSetting = setting("Keybind", defaultKeybind) val isVisible = setting("Visible", true) { ModuleList.isEnabled } val reset by setting("Reset", { settings.forEach { it.reset() }; this@Module.info("Settings set to default") }) diff --git a/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt b/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt index 837240367..370ffaaad 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt @@ -18,8 +18,7 @@ package com.lambda.module.hud import com.lambda.context.SafeContext -import com.lambda.graphics.renderer.gui.font.FontRenderer -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString +import com.lambda.graphics.renderer.gui.FontRenderer import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe @@ -30,7 +29,7 @@ import com.lambda.util.extension.isNether import com.lambda.util.math.netherCoord import com.lambda.util.math.overworldCoord -object Coordinates : HudModule( +object Coordinates : HudModule.Text( name = "Coordinates", description = "Show your coordinates", defaultTags = setOf(ModuleTag.CLIENT), @@ -38,19 +37,7 @@ object Coordinates : HudModule( private val showDimension by setting("Show Dimension", true) private val decimals by setting("Decimals", 2, 0..4, 1) - private val text: String - get() = runSafe { "XYZ ${if (showDimension) dimensionName else ""} ${positionForDimension()}" } ?: "" - - override val height: Double get() = FontRenderer.getHeight() - override val width: Double get() = FontRenderer.getWidth(text) - - init { - onRender { - runSafe { - drawString(text, position) - } - } - } + override fun getText() = runSafe { "XYZ ${if (showDimension) dimensionName else ""} ${positionForDimension()}" } ?: "" private fun SafeContext.positionForDimension() = when { diff --git a/common/src/main/kotlin/com/lambda/module/hud/GifTest.kt b/common/src/main/kotlin/com/lambda/module/hud/GifTest.kt index b4c8fe833..fe991b01d 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/GifTest.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/GifTest.kt @@ -19,6 +19,7 @@ package com.lambda.module.hud import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture import com.lambda.graphics.texture.TextureOwner.uploadGif +import com.lambda.gui.component.layout.Layout.Companion.layout import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag @@ -26,14 +27,16 @@ object GifTest : HudModule( name = "GifTest", defaultTags = setOf(ModuleTag.CLIENT), ) { - val test = uploadGif("chika.gif") - - override val width = 100.0 - override val height = 100.0 + private val test = uploadGif("chika.gif") init { - onRender { - drawTexture(test, rect) + build { + width = 100.0 + height = 100.0 + + customDrawable { + drawTexture(test, rect) + } } } } diff --git a/common/src/main/kotlin/com/lambda/module/hud/ModuleList.kt b/common/src/main/kotlin/com/lambda/module/hud/ModuleList.kt index ee2037817..236cdbe02 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/ModuleList.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/ModuleList.kt @@ -17,25 +17,20 @@ package com.lambda.module.hud -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString import com.lambda.module.HudModule import com.lambda.module.ModuleRegistry import com.lambda.module.tag.ModuleTag -import com.lambda.util.math.Vec2d -object ModuleList : HudModule( +object ModuleList : HudModule.Text( name = "ModuleList", defaultTags = setOf(ModuleTag.CLIENT), ) { - override val width = 200.0 - override val height = 200.0 - init { - onRender { - val enabled = ModuleRegistry.modules - .filter { it.isEnabled } - .filter { it.isVisible.value } - drawString(enabled.joinToString("\n") { "${it.name} [${it.keybind.name}]" }, Vec2d.ZERO) - } + override fun getText(): String { + val enabled = ModuleRegistry.modules + .filter { it.isEnabled } + .filter { it.isVisible.value } + + return enabled.joinToString("\n") { "${it.name} [${it.keybind.name}]" } } } diff --git a/common/src/main/kotlin/com/lambda/module/hud/TPS.kt b/common/src/main/kotlin/com/lambda/module/hud/TPS.kt index a5cf622e5..f89ba5fcb 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/TPS.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/TPS.kt @@ -17,33 +17,21 @@ package com.lambda.module.hud -import com.lambda.graphics.renderer.gui.font.FontRenderer -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag import com.lambda.util.Formatting.string import com.lambda.util.NamedEnum import com.lambda.util.ServerTPS.averageMSPerTick -object TPS : HudModule( +object TPS : HudModule.Text( name = "TPS", description = "Display the server's tick rate", defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.NETWORK), ) { private val format by setting("Tick format", TickFormat.TPS) - private val text: String get() = "${format.displayName}: ${format.output().string}${format.unit}" - - // TODO: Replace by LambdaAtlas height cache and actually build a proper text with highlighted parameters - - override val height: Double get() = FontRenderer.getHeight() - override val width: Double get() = FontRenderer.getWidth(text) - - init { - onRender { - drawString(text, position) - } - } + override fun getText() = + "${format.displayName}: ${format.output().string}${format.unit}" @Suppress("unused") private enum class TickFormat( diff --git a/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt index f4fd69552..0ff90fa70 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt @@ -17,22 +17,13 @@ package com.lambda.module.hud -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask -import com.lambda.util.math.Vec2d -object TaskFlowHUD : HudModule( +object TaskFlowHUD : HudModule.Text( name = "TaskFlowHud", defaultTags = setOf(ModuleTag.CLIENT), ) { - override val width = 200.0 - override val height = 200.0 - - init { - onRender { - drawString(RootTask.toString(), Vec2d.ZERO) - } - } + override fun getText() = RootTask.toString() } diff --git a/common/src/main/kotlin/com/lambda/module/hud/TickShiftCharge.kt b/common/src/main/kotlin/com/lambda/module/hud/TickShiftCharge.kt index 600a21271..a29a087e2 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/TickShiftCharge.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/TickShiftCharge.kt @@ -18,19 +18,9 @@ package com.lambda.module.hud import com.lambda.graphics.animation.Animation.Companion.exp -import com.lambda.graphics.renderer.gui.rect.FilledRectRenderer -import com.lambda.graphics.renderer.gui.rect.FilledRectRenderer.filledRect -import com.lambda.graphics.renderer.gui.rect.OutlineRectRenderer -import com.lambda.graphics.renderer.gui.rect.OutlineRectRenderer.outlineRect import com.lambda.module.HudModule -import com.lambda.module.modules.client.ClickGui -import com.lambda.module.modules.client.GuiSettings -import com.lambda.module.modules.client.GuiSettings.primaryColor import com.lambda.module.modules.movement.TickShift import com.lambda.module.tag.ModuleTag -import com.lambda.util.math.Rect -import com.lambda.util.math.multAlpha -import java.awt.Color object TickShiftCharge : HudModule( name = "TickShiftCharge", @@ -45,11 +35,12 @@ object TickShiftCharge : HudModule( (TickShift.balance / TickShift.maxBalance.toDouble()).coerceIn(0.0..1.0) } - override val width = 70.0 - override val height = 14.0 - init { - onRender { + build { + + } + + /*onRender { filledRect( rect = rect, roundRadius = ClickGui.roundRadius, @@ -74,6 +65,6 @@ object TickShiftCharge : HudModule( shade = true ) } - } + }*/ } } diff --git a/common/src/main/kotlin/com/lambda/module/hud/Watermark.kt b/common/src/main/kotlin/com/lambda/module/hud/Watermark.kt index 794738eb0..ef477296d 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/Watermark.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/Watermark.kt @@ -20,6 +20,7 @@ package com.lambda.module.hud import com.lambda.graphics.renderer.gui.TextureRenderer.drawTexture import com.lambda.graphics.renderer.gui.TextureRenderer.drawTextureShaded import com.lambda.graphics.texture.TextureOwner.upload +import com.lambda.gui.component.layout.Layout.Companion.layout import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag @@ -29,16 +30,18 @@ object Watermark : HudModule( ) { private val shade by setting("Shade", true) - override val width = 50.0 - override val height = 50.0 - private val normalTexture = upload("textures/lambda.png") private val monoTexture = upload("textures/lambda_mono.png") init { - onRender { - if (shade) drawTextureShaded(monoTexture, rect) - else drawTexture(normalTexture, rect) + build { + width = 50.0 + height = 50.0 + + customDrawable { + if (shade) drawTextureShaded(monoTexture, rect) + else drawTexture(normalTexture, rect) + } } } } 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 f3db6b239..3c7ccacbe 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,15 +18,26 @@ package com.lambda.module.modules.client import com.lambda.Lambda.mc +import com.lambda.gui.LambdaScreen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.gui.ScreenLayout.Companion.gui +import com.lambda.gui.RootLayout.Companion.gui +import com.lambda.gui.component.HAlign +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.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 +import com.lambda.module.HudModule +import com.lambda.module.ModuleRegistry import com.lambda.util.KeyCode +import com.lambda.util.Mouse import com.lambda.util.math.Vec2d import com.lambda.util.math.setAlpha import java.awt.Color +import kotlin.math.hypot object ClickGui : Module( name = "ClickGui", @@ -43,15 +54,24 @@ object ClickGui : Module( val roundRadius by setting("Round Radius", 3.0, 0.0..10.0, 0.1) 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 cornerTintShade by setting("Corner Tint Shade", true) val titleBackgroundColor by setting("Title Background Color", Color(80, 80, 80)) 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", 6.0, 1.0..10.0, 0.1) { outline } - val outlineColor by setting("Outline Color", Color.WHITE.setAlpha(0.6)) { outline } + val outlineWidth by setting("Outline Width", 0.5, 0.5..5.0, 0.1) { outline } + val outlineColor by setting("Outline Color", Color.WHITE) { outline } val outlineShade by setting("Outline Shade", true) { outline } + + val glow by setting("Glow", true) + val glowWidth by setting("Glow Width", 8.0, 1.0..20.0, 0.1) { glow } + val glowColor by setting("Glow Color", Color.WHITE.setAlpha(0.35)) { glow } + val glowShade by setting("Glow Shade", true) { glow } + 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) @@ -62,27 +82,79 @@ object ClickGui : Module( val moduleOpenAccent by setting("Module Open Accent", 0.3, 0.0..0.5, 0.01) val multipleSettingWindows by setting("Multiple Setting Windows", false) - val animationCurve by setting("List Animation Curve", AnimationCurve.Normal) + val animationCurve by setting("List Animation Curve", AnimationCurve.Reverse) val smoothness by setting("Smoothness", 0.4, 0.3..0.7, 0.01) { animationCurve != AnimationCurve.Static } - val SCREEN get() = gui("Click Gui") { - onKeyPress { - if (it.keyCode != keybind.keyCode || keybind == KeyCode.UNBOUND) return@onKeyPress - mc.currentScreen?.close() - } + val hudPadding by setting("Hud Padding", 3.0, 0.0..10.0, 0.1) - rect { - onUpdate { - rect = owner!!.rect - setColor(backgroundTint) + val SCREEN: LambdaScreen by lazy { + gui("Click Gui") { + onKeyPress { + if (it.keyCode != keybind.keyCode || keybind == KeyCode.UNBOUND) return@onKeyPress + mc.currentScreen?.close() + } + + rect { + onUpdate { + rect = owner!!.rect + setColor(backgroundTint) + } + } + + glow { + onUpdate { + rect = owner!!.rect + innerSpread = cornerTintWidth + shade = cornerTintShade + + setColor(backgroundCornerTint) + setInnerRadius(cornerTintWidth) + } + } + + var x = 10.0 + val y = x + + ModuleTag.defaults.forEach { tag -> + x += moduleWindow(tag, Vec2d(x, y)).width + 5 } + + switchButton("HUD", ::HUD) } + } + + val HUD: LambdaScreen by lazy { + gui("Hud GUI") { + switchButton("Back", ::SCREEN) + + var dragInfo: Pair? = null - var x = 10.0 - val y = x + onShow { + dragInfo = null + } + + onMouse(action = Mouse.Action.Click, button = Mouse.Button.Left) { + dragInfo = null + } + + onMouseMove { mouse -> + if (pressedButton != Mouse.Button.Left) return@onMouseMove - ModuleTag.defaults.forEach { tag -> - x += moduleWindow(tag, Vec2d(x, y)).width + 5 + ModuleRegistry.modules + .filterIsInstance() + .filter { mouse in it.getRootLayout().rect } + .minByOrNull { + val (x, y) = it.getRootLayout().rect.center + hypot(x - mouse.x, y - mouse.y) + }?.let { module -> + dragInfo = dragInfo ?: (module to (mouse - module.getRootLayout().position)) + + } + + dragInfo?.let { drag -> + drag.first.getRootLayout().position = mouse - drag.second + } + } } } @@ -98,4 +170,25 @@ object ClickGui : Module( toggle() } } + + private fun Layout.switchButton(text: String, gui: () -> LambdaScreen) { + unitButton(text) { + gui().show() + }.apply { + animatedBackground { + hoverAnimation * 0.5 + 0.5 + } + + horizontalAlignment = HAlign.RIGHT + verticalAlignment = VAlign.BOTTOM + titleBar.textField.textHAlignment = HAlign.CENTER + + positionX = owner!!.positionX - width - 10.0 + positionY = owner.positionY - height - 10.0 + + onUpdate { + width = height * 1.5 + } + } + } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/GuiSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/GuiSettings.kt index 52055ab17..cb314c591 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/GuiSettings.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/GuiSettings.kt @@ -17,11 +17,13 @@ package com.lambda.module.modules.client +import com.lambda.Lambda.mc import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.animation.Animation.Companion.exp import com.lambda.graphics.animation.AnimationTicker +import com.lambda.gui.LambdaScreen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import java.awt.Color @@ -40,18 +42,11 @@ object GuiSettings : Module( // Colors val primaryColor by setting("Primary Color", Color(130, 200, 255), visibility = { page == Page.Colors }) val secondaryColor by setting("Secondary Color", Color(225, 130, 225), visibility = { page == Page.Colors }) - val backgroundColor by setting("Background Color", Color(50, 50, 50, 150), visibility = { page == Page.Colors }) val shade by setting("Shade", true, visibility = { page == Page.Colors }) - val shadeBackground by setting("Shade Background", true, visibility = { page == Page.Colors }) val colorWidth by setting("Shade Width", 200.0, 10.0..1000.0, 10.0, visibility = { page == Page.Colors }) val colorHeight by setting("Shade Height", 200.0, 10.0..1000.0, 10.0, visibility = { page == Page.Colors }) val colorSpeed by setting("Color Speed", 1.0, 0.1..5.0, 0.1, visibility = { page == Page.Colors }) - val mainColor: Color get() = if (shade) Color.WHITE else primaryColor - - val shadeColor1 get() = primaryColor - val shadeColor2 get() = secondaryColor - enum class Page { General, Colors @@ -59,7 +54,7 @@ object GuiSettings : Module( private var targetScale = 2.0 get() { - val update = System.currentTimeMillis() - lastChange > 200 || !ClickGui.SCREEN.isOpen + val update = System.currentTimeMillis() - lastChange > 500 || mc.currentScreen !is LambdaScreen if (update) field = scaleSetting / 100.0 * 2.0 return field } 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 b97bca99b..8ce70690d 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,7 +20,7 @@ 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.font.FontRenderer.drawGlyph +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.module.Module @@ -37,12 +37,8 @@ object LambdaMoji : Module( defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.RENDER), enabledByDefault = true, ) { - val scale by setting("Emoji Scale", 1.0, 0.5..1.5, 0.1) val suggestions by setting("Chat Suggestions", true) - private val emojiWhitespace: String - get() = " ".repeat(((mc.textRenderer.fontHeight / 2 / mc.textRenderer.getWidth(" ")) * scale).toInt()) - private val renderQueue = mutableListOf>() init { @@ -73,7 +69,6 @@ object LambdaMoji : Module( val index = raw.indexOf(emoji) if (index == -1) return@forEach - val height = mc.textRenderer.fontHeight val width = mc.textRenderer.getWidth(raw.substring(0, index)) // Dude I'm sick of working with the shitcode that is minecraft's codebase :sob: @@ -82,19 +77,19 @@ object LambdaMoji : Module( else -> Color(255, 255, 255, (color shr 24 and 0xFF)) } - val glyph = RenderSettings.emojiFont[emoji]!! - renderQueue.add(Triple(glyph, Vec2d(x + width, y + height / 2), trueColor)) + val glyph = RenderSettings.emojiFont[emoji] ?: return@forEach + renderQueue.add(Triple(glyph, Vec2d(x + width, y), trueColor)) // Replace the emoji with whitespaces depending on the player's settings - raw = raw.replaceFirst(emoji, emojiWhitespace) + raw = raw.replaceFirst(emoji, " ") } val constructed = mutableListOf() // Will not work properly if the emoji is part of the style - saved.forEach { (charIndex: Int, style: Style) -> - if (charIndex >= raw.length) return@forEach - constructed.add(OrderedText.styledForwardsVisitedString(raw.substring(charIndex, charIndex + 1), style)) + saved.forEach { (index, style) -> + if (index >= raw.length) return@forEach + constructed.add(OrderedText.styledForwardsVisitedString(raw.substring(index, index + 1), style)) } return OrderedText.concat(constructed) 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 08936c229..b7f228fb2 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 @@ -30,6 +30,9 @@ 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 } @@ -49,6 +52,7 @@ 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 466fff405..9c2cd573c 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 @@ -18,7 +18,6 @@ package com.lambda.module.modules.combat import com.lambda.Lambda -import com.lambda.Lambda.mc import com.lambda.config.groups.RotationSettings import com.lambda.config.groups.Targeting import com.lambda.context.SafeContext @@ -29,8 +28,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.buildWorldProjection import com.lambda.graphics.gl.Matrices.withVertexTransform -import com.lambda.graphics.renderer.gui.font.FontRenderer -import com.lambda.graphics.renderer.gui.font.FontRenderer.drawString +import com.lambda.graphics.renderer.gui.FontRenderer +import com.lambda.graphics.renderer.gui.FontRenderer.drawString import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotation.RotationManager import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt index d77819dbd..6484cff55 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.buffer.VertexPipeline +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.gl.GlStateUtils.withBlendFunc @@ -32,6 +32,7 @@ import com.lambda.graphics.gl.GlStateUtils.withDepth import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.buildWorldProjection import com.lambda.graphics.gl.Matrices.withVertexTransform +import com.lambda.graphics.pipeline.VertexBuilder import com.lambda.graphics.shader.Shader.Companion.shader import com.lambda.interaction.request.rotation.Rotation import com.lambda.module.Module @@ -81,7 +82,7 @@ object Particles : Module( private var particles = mutableListOf() private val pipeline = VertexPipeline(VertexMode.TRIANGLES, VertexAttrib.Group.PARTICLE) - private val shader = shader("renderer/particle", "renderer/particle") + private val shader = shader("renderer/particle") init { listen { @@ -91,13 +92,16 @@ object Particles : Module( listen { // Todo: interpolated tickbased upload? - particles.forEach(Particle::build) + val builder = pipeline.build() + particles.forEach { + it.build(builder) + } withBlendFunc(GL_SRC_ALPHA, GL_ONE) { shader.use() shader["u_CameraPosition"] = mc.gameRenderer.camera.pos - pipeline.upload() + pipeline.upload(builder) withDepth(false, pipeline::render) pipeline.clear() } @@ -178,7 +182,7 @@ object Particles : Module( return age > maxAge + fadeTicks * 2 + 5 } - fun build() { + fun build(builder: VertexBuilder) = builder.apply { val smoothAge = age + mc.partialTicks val colorTicks = smoothAge * 0.1 / colorSpeed @@ -199,15 +203,20 @@ object Particles : Module( val size = if (lay) environmentSize else sizeSetting * lerp(alpha, 0.5, 1.0) withVertexTransform(buildWorldProjection(position, size, projRotation)) { - pipeline.use { - grow(4) // DO NOT FUCKING FORGOTEOIJTOWKET TO GROW (cost me an hour) - putQuad( - vec3m(-1.0, -1.0, 0.0).vec2(0.0, 0.0).color(color).end(), - vec3m(-1.0, 1.0, 0.0).vec2(0.0, 1.0).color(color).end(), - vec3m(1.0, 1.0, 0.0).vec2(1.0, 1.0).color(color).end(), - vec3m(1.0, -1.0, 0.0).vec2(1.0, 0.0).color(color).end() - ) - } + buildQuad( + vertex { + vec3m(-1.0, -1.0, 0.0).vec2(0.0, 0.0).color(color) + }, + vertex { + vec3m(-1.0, 1.0, 0.0).vec2(0.0, 1.0).color(color) + }, + vertex { + vec3m(1.0, 1.0, 0.0).vec2(1.0, 1.0).color(color) + }, + vertex { + vec3m(1.0, -1.0, 0.0).vec2(1.0, 0.0).color(color) + } + ) } } } diff --git a/common/src/main/kotlin/com/lambda/util/Formatting.kt b/common/src/main/kotlin/com/lambda/util/Formatting.kt index d5703e886..71940cf03 100644 --- a/common/src/main/kotlin/com/lambda/util/Formatting.kt +++ b/common/src/main/kotlin/com/lambda/util/Formatting.kt @@ -17,6 +17,7 @@ package com.lambda.util +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import java.time.LocalDateTime import java.time.ZoneId @@ -39,6 +40,8 @@ object Formatting { return "(${format.format(Locale.US, x)}, ${format.format(Locale.US, y)}, ${format.format(Locale.US, z)})" } + fun BlockPos.asString() = "($x, $y, $z)" + fun getTime(formatter: DateTimeFormatter = DateTimeFormatter.RFC_1123_DATE_TIME): String { val localDateTime = LocalDateTime.now() val zoneId = ZoneId.systemDefault() diff --git a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt index 8784cf45b..9d72823b6 100644 --- a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt +++ b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt @@ -88,6 +88,13 @@ infix operator fun Vec3d.div(other: Double): Vec3d = times(1 / other) infix operator fun Vec3d.div(other: Float): Vec3d = times(1 / other) infix operator fun Vec3d.div(other: Int): Vec3d = times(1 / other) +infix operator fun ClosedRange.rangeTo(other: Double) = Vec3d(start, endInclusive, other) +infix operator fun ClosedRange.rangeTo(other: Float) = Vec3d(start.toDouble(), endInclusive.toDouble(), other.toDouble()) +infix operator fun ClosedRange.rangeTo(other: Int) = Vec3d(start.toDouble(), endInclusive.toDouble(), other.toDouble()) +infix operator fun OpenEndRange.rangeTo(other: Double) = Vec3d(start, endExclusive, other) +infix operator fun OpenEndRange.rangeTo(other: Float) = Vec3d(start.toDouble(), endExclusive.toDouble(), other.toDouble()) +infix operator fun OpenEndRange.rangeTo(other: Int) = BlockPos.Mutable(start, endExclusive, other) + /* Vec3i */ val Vec3i.vec3d get() = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/font/font.frag b/common/src/main/resources/assets/lambda/shaders/fragment/font/font.frag deleted file mode 100644 index 37aa5a897..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/font/font.frag +++ /dev/null @@ -1,27 +0,0 @@ -#version 330 core - -uniform sampler2D u_FontTexture; -uniform sampler2D u_EmojiTexture; -uniform float u_SDFMin; -uniform float u_SDFMax; - -in vec2 v_TexCoord; -in vec4 v_Color; - -out vec4 color; - -float sdf(float channel) { - return 1.0 - smoothstep(u_SDFMin, u_SDFMax, 1.0 - channel); -} - -void main() { - bool isEmoji = v_TexCoord.x < 0.0; - - if (isEmoji) { - vec4 c = texture(u_EmojiTexture, -v_TexCoord); - color = vec4(c.rgb, sdf(c.a)) * v_Color; - return; - } - - color = vec4(1.0, 1.0, 1.0, sdf(texture(u_FontTexture, v_TexCoord).r)) * v_Color; -} diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/post/cgui_animation.frag b/common/src/main/resources/assets/lambda/shaders/fragment/post/cgui_animation.frag deleted file mode 100644 index 7ca5b52e3..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/post/cgui_animation.frag +++ /dev/null @@ -1,22 +0,0 @@ -#version 330 core - -uniform sampler2D u_Texture; -uniform float u_Progress; - -in vec2 v_TexCoord; -out vec4 color; - -void main() { - vec2 coord = v_TexCoord; - - coord -= 0.5; - coord *= 2.0; - - coord *= mix(0.9, 1.0, u_Progress); - - coord *= 0.5; - coord += 0.5; - - vec4 tex = texture(u_Texture, coord); - color = vec4(tex.rgb * mix(1.1, 1.0, u_Progress), tex.a * u_Progress); -} diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/particle.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/particle.frag deleted file mode 100644 index 53b683d85..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/particle.frag +++ /dev/null @@ -1,11 +0,0 @@ -#version 330 core - -in vec2 v_TexCoord; -in vec4 v_Color; - -out vec4 color; - -void main() { - float a = 1.0 - length(v_TexCoord - 0.5) * 2.0; - color = v_Color * vec4(1.0, 1.0, 1.0, a); -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_color.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_color.frag deleted file mode 100644 index 71f50775d..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_color.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 330 core - -in vec4 v_Color; -out vec4 color; - -void main() { - color = v_Color; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex.frag deleted file mode 100644 index d89d3550a..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex.frag +++ /dev/null @@ -1,10 +0,0 @@ -#version 330 core - -uniform sampler2D u_Texture; - -in vec2 v_TexCoord; -out vec4 color; - -void main() { - color = texture(u_Texture, v_TexCoord); -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex_shady.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex_shady.frag deleted file mode 100644 index f6ec30c85..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/pos_tex_shady.frag +++ /dev/null @@ -1,23 +0,0 @@ -#version 330 core - -uniform sampler2D u_Texture; -uniform float u_Time; -uniform vec4 u_Color1; -uniform vec4 u_Color2; -uniform vec2 u_Size; - -in vec2 v_Position; -in vec2 v_TexCoord; - -out vec4 color; - -vec4 shade() { - vec2 pos = v_Position * u_Size; - float p = sin(pos.x - pos.y - u_Time) * 0.5 + 0.5; - - return mix(u_Color1, u_Color2, p); -} - -void main() { - color = texture(u_Texture, v_TexCoord) * shade(); -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_filled.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_filled.frag deleted file mode 100644 index 97c5b5d34..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_filled.frag +++ /dev/null @@ -1,81 +0,0 @@ -#version 330 core - -uniform vec2 u_Size; -uniform float u_RoundLeftTop; -uniform float u_RoundLeftBottom; -uniform float u_RoundRightBottom; -uniform float u_RoundRightTop; - -uniform float u_Shade; -uniform float u_ShadeTime; -uniform vec4 u_ShadeColor1; -uniform vec4 u_ShadeColor2; -uniform vec2 u_ShadeSize; - -in vec2 v_Position; -in vec2 v_TexCoord; -in vec4 v_Color; - -out vec4 color; - -#define SMOOTHING 0.25 -#define NOISE_GRANULARITY 0.004 - -vec4 noise() { - // https://shader-tutorial.dev/advanced/color-banding-dithering/ - float random = fract(sin(dot(v_TexCoord, vec2(12.9898, 78.233))) * 43758.5453); - float ofs = mix(-NOISE_GRANULARITY, NOISE_GRANULARITY, random); - return vec4(ofs, ofs, ofs, 0.0); -} - -vec4 shade() { - if (u_Shade != 1.0) return v_Color; - - vec2 pos = v_Position * u_ShadeSize; - float p = sin(pos.x - pos.y - u_ShadeTime) * 0.5 + 0.5; - - return mix(u_ShadeColor1, u_ShadeColor2, p) * v_Color; -} - -float getRoundRadius() { - // ToDo: use step - bool xcmp = v_TexCoord.x > 0.5; - bool ycmp = v_TexCoord.y > 0.5; - - float r = 0.0; - - if (xcmp) { - if (ycmp) { - r = u_RoundRightBottom; - } else { - r = u_RoundRightTop; - } - } else { - if (ycmp) { - r = u_RoundLeftBottom; - } else { - r = u_RoundLeftTop; - } - } - - return r; -} - -vec4 round() { - vec2 halfSize = u_Size * 0.5; - - float radius = max(getRoundRadius(), SMOOTHING); - - vec2 smoothVec = vec2(SMOOTHING); - vec2 coord = mix(-smoothVec, u_Size + smoothVec, v_TexCoord); - - vec2 center = halfSize - coord; - float distance = length(max(abs(center) - halfSize + radius, 0.0)) - radius; - - float alpha = 1.0 - smoothstep(-SMOOTHING, SMOOTHING, distance); - return vec4(1.0, 1.0, 1.0, clamp(alpha, 0.0, 1.0)); -} - -void main() { - color = shade() * round() + noise(); -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_outline.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_outline.frag deleted file mode 100644 index 71e8680fe..000000000 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/rect_outline.frag +++ /dev/null @@ -1,31 +0,0 @@ -#version 330 core - -uniform float u_Shade; -uniform float u_ShadeTime; -uniform vec4 u_ShadeColor1; -uniform vec4 u_ShadeColor2; -uniform vec2 u_ShadeSize; - -in vec2 v_Position; -in float v_Alpha; -in vec4 v_Color; - -out vec4 color; - -vec4 shade() { - if (u_Shade != 1.0) return v_Color; - - vec2 pos = v_Position * u_ShadeSize; - float p = sin(pos.x - pos.y - u_ShadeTime) * 0.5 + 0.5; - - return mix(u_ShadeColor1, u_ShadeColor2, p) * v_Color; -} - -vec4 glow() { - float newAlpha = min(1.0, v_Alpha * v_Alpha * v_Alpha); - return vec4(1.0, 1.0, 1.0, newAlpha); -} - -void main() { - color = shade() * glow(); -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/pos_color.glsl b/common/src/main/resources/assets/lambda/shaders/pos_color.glsl new file mode 100644 index 000000000..c8536f01b --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/pos_color.glsl @@ -0,0 +1,12 @@ +attributes { + vec4 pos; + vec4 color; +}; + +export { + vec4 v_Color; # color +}; + +void fragment() { + color = v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/pos_tex.glsl b/common/src/main/resources/assets/lambda/shaders/pos_tex.glsl new file mode 100644 index 000000000..b058e7bc2 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/pos_tex.glsl @@ -0,0 +1,16 @@ +attributes { + vec4 pos; + vec2 uv; +}; + +uniforms { + sampler2D u_Texture; # fragment +}; + +export { + vec2 v_TexCoord; # uv +}; + +void fragment() { + color = texture(u_Texture, v_TexCoord); +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/pos_tex_color.glsl b/common/src/main/resources/assets/lambda/shaders/pos_tex_color.glsl new file mode 100644 index 000000000..e10b9a4a8 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/pos_tex_color.glsl @@ -0,0 +1,18 @@ +attributes { + vec4 pos; + vec2 uv; + vec4 color; +}; + +uniforms { + sampler2D u_Texture; # fragment +}; + +export { + vec2 v_TexCoord; # uv + vec4 v_Color; # color +}; + +void fragment() { + color = texture(u_Texture, v_TexCoord) * v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/pos_tex_shady.glsl b/common/src/main/resources/assets/lambda/shaders/pos_tex_shady.glsl new file mode 100644 index 000000000..395642309 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/pos_tex_shady.glsl @@ -0,0 +1,18 @@ +attributes { + vec4 pos; + vec2 uv; +}; + +uniforms { + sampler2D u_Texture; # fragment +}; + +export { + vec2 v_TexCoord; # uv +}; + +#include "shade" + +void fragment() { + color = texture(u_Texture, v_TexCoord) * shade; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/signed_distance_field.frag b/common/src/main/resources/assets/lambda/shaders/post/sdf.glsl similarity index 54% rename from common/src/main/resources/assets/lambda/shaders/fragment/signed_distance_field.frag rename to common/src/main/resources/assets/lambda/shaders/post/sdf.glsl index 5d4f485f1..fa3566da6 100644 --- a/common/src/main/resources/assets/lambda/shaders/fragment/signed_distance_field.frag +++ b/common/src/main/resources/assets/lambda/shaders/post/sdf.glsl @@ -1,19 +1,25 @@ -#version 330 core +attributes { + vec4 pos; + vec2 uv; +}; -uniform sampler2D u_Texture; -uniform vec2 u_TexelSize; +uniforms { + sampler2D u_Texture; # fragment + vec2 u_TexelSize; # fragment +}; -in vec2 v_TexCoord; -out vec4 color; +export { + vec2 v_TexCoord; # uv +}; -#define SPHREAD 4 +#define SPREAD 4 -void main() { +void fragment() { vec4 colors = vec4(0.0); vec4 blurWeight = vec4(0.0); - for (int x = -SPHREAD; x <= SPHREAD; ++x) { - for (int y = -SPHREAD; y <= SPHREAD; ++y) { + for (int x = -SPREAD; x <= SPREAD; ++x) { + for (int y = -SPREAD; y <= SPREAD; ++y) { vec2 offset = vec2(x, y) * u_TexelSize; vec4 color = texture(u_Texture, v_TexCoord + offset); @@ -25,4 +31,4 @@ void main() { } color = colors / blurWeight; -} +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/box_dynamic.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/box_dynamic.glsl new file mode 100644 index 000000000..2bbfcdd0f --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/box_dynamic.glsl @@ -0,0 +1,20 @@ +attributes { + vec3 pos1; + vec3 pos2; + vec4 color; +}; + +uniforms { + mat4 u_ProjModel; # vertex + float u_TickDelta; # vertex + vec3 u_CameraPosition; # vertex +}; + +export { + core gl_Position; # u_ProjModel * vec4(mix(pos1, pos2, u_TickDelta) - u_CameraPosition, 1.0) + vec4 v_Color; # color +}; + +void fragment() { + color = v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/box_static.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/box_static.glsl new file mode 100644 index 000000000..3a97249d5 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/box_static.glsl @@ -0,0 +1,18 @@ +attributes { + vec3 pos; + vec4 color; +}; + +uniforms { + mat4 u_ProjModel; # vertex + vec3 u_CameraPosition; # vertex +}; + +export { + core gl_Position; # u_ProjModel * vec4(pos - u_CameraPosition, 1.0) + vec4 v_Color; # color +}; + +void fragment() { + color = v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl new file mode 100644 index 000000000..5d4abd1dc --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/font.glsl @@ -0,0 +1,32 @@ +attributes { + vec4 pos; + vec2 uv; + vec4 color; +}; + +uniforms { + sampler2D u_FontTexture; # fragment + sampler2D u_EmojiTexture; # fragment + float u_SDFMin; # fragment + float u_SDFMax; # fragment +}; + +export { + vec2 v_TexCoord; # uv + vec4 v_Color; # color +}; + +#include "sdf" + +void fragment() { + bool isEmoji = v_TexCoord.x < 0.0; + + if (isEmoji) { + vec4 c = texture(u_EmojiTexture, -v_TexCoord); + color = vec4(c.rgb, sdf(c.a, u_SDFMin, u_SDFMax)) * v_Color; + return; + } + + 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 diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/hue_circle.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/hue_circle.glsl new file mode 100644 index 000000000..a4ead7707 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/hue_circle.glsl @@ -0,0 +1,21 @@ +attributes { + vec4 pos; + vec2 uv; +}; + +export { + vec2 v_TexCoord; # uv +}; + +#include "hsb" +#include "sdf" + +void fragment() { + float dst = 0.5 - length(v_TexCoord - 0.5); + float sdf = sdf(dst, 0.98, 1.0); + + float hue255 = hue(vec2(v_TexCoord.y, 1.0 - v_TexCoord.x)); + vec3 rgb = hsb2rgb(vec3(hue255, 1.0 - dst * 2.0, 1.0)); + + color = vec4(rgb, sdf); +}# diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/particle.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/particle.glsl new file mode 100644 index 000000000..9444708e9 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/particle.glsl @@ -0,0 +1,20 @@ +attributes { + vec3 pos; + vec2 uv; + vec4 color; +}; + +uniforms { + vec3 u_CameraPosition; # vertex +}; + +export { + core gl_Position; # u_ProjModel * vec4(pos - u_CameraPosition, 1.0) + vec2 v_TexCoord; # uv + vec4 v_Color; # color +}; + +void fragment() { + float a = 1.0 - length(v_TexCoord - 0.5) * 2.0; + color = v_Color * vec4(1.0, 1.0, 1.0, a); +}# \ No newline at end of file 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 new file mode 100644 index 000000000..7ee5ec204 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_filled.glsl @@ -0,0 +1,13 @@ +attributes { + vec4 pos; + vec2 uv; + vec4 color; +}; + +#include "rect" + +void fragment() { + 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 new file mode 100644 index 000000000..094f9c373 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_glow.glsl @@ -0,0 +1,40 @@ +attributes { + vec4 pos; + vec2 uv; + vec4 color; +}; + +uniforms { + float u_RectWidth; # fragment + float u_InnerRectWidth; # fragment + + float u_InnerRoundLeftTop; # fragment + float u_InnerRoundLeftBottom; # fragment + float u_InnerRoundRightBottom; # fragment + float u_InnerRoundRightTop; # fragment +}; + +#include "rect" + +void fragment() { + float distance = signedDistance(); + float innerDistance = signedDistance(vec4( + u_InnerRoundRightBottom, + u_InnerRoundRightTop, + u_InnerRoundLeftBottom, + u_InnerRoundLeftTop + )); + + float bloomDistance = distance; + if (innerDistance > 0.0 && distance < 0.0) bloomDistance = 0.0; + if (innerDistance < 0.0) bloomDistance = innerDistance; + + float bloomSpread = ((bloomDistance > 0.0) ? u_RectWidth : -u_InnerRectWidth); + float bloomAlpha = 1.0 / (1.0 + bloomDistance / bloomSpread); + + float glowAlpha = + smoothstep(-u_InnerRectWidth, 0.0, bloomDistance) - + 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 new file mode 100644 index 000000000..d27ac5873 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/rect_outline.glsl @@ -0,0 +1,18 @@ +attributes { + vec4 pos; + vec2 uv; + vec4 color; +}; + +uniforms { + float u_RectWidth; # fragment +}; + +#include "rect" + +void fragment() { + 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/renderer/tracer_dynamic.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/tracer_dynamic.glsl new file mode 100644 index 000000000..a178426fd --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/tracer_dynamic.glsl @@ -0,0 +1,27 @@ +attributes { + vec3 pos1; + vec3 pos2; + vec4 color; +}; + +uniforms { + mat4 u_ProjModel; # vertex + float u_TickDelta; # vertex + vec3 u_CameraPosition; # vertex +}; + +export { + core gl_Position; # vec4(0.0, 0.0, 0.0, 1.0) + vec4 v_Color; # color +}; + +void vertex() { + if (gl_VertexID % 2 == 0) { + vec3 VERTEX_POSITION = mix(pos1, pos2, u_TickDelta) - u_CameraPosition; + gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); + } +}# + +void fragment() { + color = v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/renderer/tracer_static.glsl b/common/src/main/resources/assets/lambda/shaders/renderer/tracer_static.glsl new file mode 100644 index 000000000..8197af918 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/renderer/tracer_static.glsl @@ -0,0 +1,25 @@ +attributes { + vec3 pos; + vec4 color; +}; + +uniforms { + mat4 u_ProjModel; # vertex + vec3 u_CameraPosition; # vertex +}; + +export { + core gl_Position; # vec4(0.0, 0.0, 0.0, 1.0) + vec4 v_Color; # color +}; + +void vertex() { + if (gl_VertexID % 2 == 0) { + vec3 VERTEX_POSITION = pos - u_CameraPosition; + gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); + } +}# + +void fragment() { + color = v_Color; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/shared/hsb.glsl b/common/src/main/resources/assets/lambda/shaders/shared/hsb.glsl new file mode 100644 index 000000000..25b9eb99d --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/hsb.glsl @@ -0,0 +1,30 @@ +vec3 hsb2rgb(vec3 hsb) { + float C = hsb.z * hsb.y; + float X = C * (1.0 - abs(mod(hsb.x / 60.0, 2.0) - 1.0)); + float m = hsb.z - C; + + vec3 rgb; + + if (0.0 <= hsb.x && hsb.x < 60.0) { + rgb = vec3(C, X, 0.0); + } else if (60.0 <= hsb.x && hsb.x < 120.0) { + rgb = vec3(X, C, 0.0); + } else if (120.0 <= hsb.x && hsb.x < 180.0) { + rgb = vec3(0.0, C, X); + } else if (180.0 <= hsb.x && hsb.x < 240.0) { + rgb = vec3(0.0, X, C); + } else if (240.0 <= hsb.x && hsb.x < 300.0) { + rgb = vec3(X, 0.0, C); + } else { + rgb = vec3(C, 0.0, X); + } + + return (rgb + vec3(m)); +}# + +float hue(vec2 uv) { + vec2 centered = uv * 2.0 - 1.0; + float hue = degrees(atan(centered.y, centered.x)) + 180.0; + + return hue; +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl new file mode 100644 index 000000000..4fe1b2220 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/rect.glsl @@ -0,0 +1,45 @@ +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 +}; + +export { + vec2 v_TexCoord; # uv + vec4 v_Color; # color +}; + +#include "shade" + +#define NOISE_GRANULARITY 0.004 +#define SMOOTHING 0.3 + +#define noise getNoise() + +vec4 getNoise() { + // https://shader-tutorial.dev/advanced/color-banding-dithering/ + float random = fract(sin(dot(v_TexCoord, vec2(12.9898, 78.233))) * 43758.5453); + float ofs = mix(-NOISE_GRANULARITY, NOISE_GRANULARITY, random); + return vec4(ofs, ofs, ofs, 0.0); +}# + +float signedDistance(in vec4 r) { + r.xy = (v_TexCoord.x > 0.5) ? r.xy : r.zw; + r.x = (v_TexCoord.y > 0.5) ? r.x : r.y; + + vec2 q = u_Size * (abs(v_TexCoord - 0.5) - 0.5) + r.x; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; +}# + +float signedDistance() { + return signedDistance(vec4(u_RoundRightBottom, u_RoundRightTop, u_RoundLeftBottom, u_RoundLeftTop)); +}# diff --git a/common/src/main/resources/assets/lambda/shaders/shared/sdf.glsl b/common/src/main/resources/assets/lambda/shaders/shared/sdf.glsl new file mode 100644 index 000000000..165043f1c --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/sdf.glsl @@ -0,0 +1,3 @@ +float sdf(float channel, float min, float max) { + return 1.0 - smoothstep(min, max, 1.0 - channel); +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/shared/shade.glsl b/common/src/main/resources/assets/lambda/shaders/shared/shade.glsl new file mode 100644 index 000000000..9371edc22 --- /dev/null +++ b/common/src/main/resources/assets/lambda/shaders/shared/shade.glsl @@ -0,0 +1,22 @@ +uniforms { + float u_Shade; # fragment + float u_ShadeTime; # fragment + vec4 u_ShadeColor1; # fragment + vec4 u_ShadeColor2; # fragment + vec2 u_ShadeSize; # fragment +}; + +export { + vec2 v_Position; # gl_Position.xy * 0.5 + 0.5 +}; + +#define shade getShadeColor() + +vec4 getShadeColor() { + if (u_Shade != 1.0) return vec4(1.0); + + vec2 pos = v_Position * u_ShadeSize; + float p = sin(pos.x - pos.y - u_ShadeTime) * 0.5 + 0.5; + + return mix(u_ShadeColor1, u_ShadeColor2, p); +}# \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/font/font.vert b/common/src/main/resources/assets/lambda/shaders/vertex/font/font.vert deleted file mode 100644 index 5f16359df..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/font/font.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 330 core - -layout (location = 0) in vec4 pos; -layout (location = 1) in vec2 uv; -layout (location = 2) in vec4 color; - -uniform mat4 u_ProjModel; - -out vec2 v_TexCoord; -out vec4 v_Color; - -void main() { - gl_Position = u_ProjModel * pos; - v_TexCoord = uv; - v_Color = color; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert deleted file mode 100644 index d33b24566..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert +++ /dev/null @@ -1,19 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 pos1; -layout (location = 1) in vec3 pos2; -layout (location = 2) in vec4 color; - -uniform mat4 u_ProjModel; - -uniform float u_TickDelta; -uniform vec3 u_CameraPosition; - -out vec4 v_Color; - -#define VERTEX_POSITION mix(pos1, pos2, u_TickDelta) - u_CameraPosition - -void main() { - gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); - v_Color = color; -} diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert deleted file mode 100644 index 197c14ca5..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 pos; -layout (location = 1) in vec4 color; - -uniform mat4 u_ProjModel; -uniform vec3 u_CameraPosition; - -out vec4 v_Color; - -#define VERTEX_POSITION pos - u_CameraPosition - -void main() { - gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); - v_Color = color; -} diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/particle.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/particle.vert deleted file mode 100644 index 6e3fed3d9..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/particle.vert +++ /dev/null @@ -1,19 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 pos; -layout (location = 1) in vec2 uv; -layout (location = 2) in vec4 color; - -uniform mat4 u_ProjModel; -uniform vec3 u_CameraPosition; - -out vec2 v_TexCoord; -out vec4 v_Color; - -#define VERTEX_POSITION pos - u_CameraPosition - -void main() { - gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); - v_TexCoord = uv; - v_Color = color; -} diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex.vert deleted file mode 100644 index 51ab11af5..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex.vert +++ /dev/null @@ -1,13 +0,0 @@ -#version 330 core - -layout (location = 0) in vec4 pos; -layout (location = 1) in vec2 uv; - -uniform mat4 u_ProjModel; - -out vec2 v_TexCoord; - -void main() { - gl_Position = u_ProjModel * pos; - v_TexCoord = uv; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex_shady.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex_shady.vert deleted file mode 100644 index 082b85b24..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/pos_tex_shady.vert +++ /dev/null @@ -1,15 +0,0 @@ -#version 330 core - -layout (location = 0) in vec4 pos; -layout (location = 1) in vec2 uv; - -uniform mat4 u_ProjModel; - -out vec2 v_Position; -out vec2 v_TexCoord; - -void main() { - gl_Position = u_ProjModel * pos; - v_Position = gl_Position.xy * 0.5 + 0.5; - v_TexCoord = uv; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert deleted file mode 100644 index 225d9dfd7..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert +++ /dev/null @@ -1,19 +0,0 @@ -#version 330 core - -layout (location = 0) in vec4 pos; -layout (location = 1) in vec2 uv; -layout (location = 2) in vec4 color; - -uniform mat4 u_ProjModel; - -out vec2 v_Position; -out vec2 v_TexCoord; -out vec4 v_Color; - -void main() { - gl_Position = u_ProjModel * pos; - - v_Position = gl_Position.xy * 0.5 + 0.5; - v_TexCoord = uv; - v_Color = color; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert deleted file mode 100644 index 42e8d4ac0..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert +++ /dev/null @@ -1,22 +0,0 @@ -#version 330 core - -layout (location = 0) in vec4 pos; -layout (location = 1) in vec2 uv; -layout (location = 2) in float alpha; -layout (location = 3) in vec4 color; - -uniform mat4 u_ProjModel; - -out vec2 v_Position; -out vec2 v_TexCoord; -out float v_Alpha; -out vec4 v_Color; - -void main() { - gl_Position = u_ProjModel * pos; - - v_Position = gl_Position.xy * 0.5 + 0.5; - v_TexCoord = uv; - v_Alpha = alpha; - v_Color = color; -} \ No newline at end of file diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert deleted file mode 100644 index 57c3accbd..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert +++ /dev/null @@ -1,22 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 pos1; -layout (location = 1) in vec3 pos2; -layout (location = 2) in vec4 color; - -uniform mat4 u_ProjModel; - -uniform float u_TickDelta; -uniform vec3 u_CameraPosition; - -out vec4 v_Color; - -#define VERTEX_POSITION mix(pos1, pos2, u_TickDelta) - u_CameraPosition - -#define SCREEN_CENTER vec4(0.0, 0.0, 0.0, 1.0) -#define PROJECTED u_ProjModel * vec4(VERTEX_POSITION, 1.0) - -void main() { - gl_Position = gl_VertexID % 2 == 0 ? SCREEN_CENTER : PROJECTED; - v_Color = color; -} diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert deleted file mode 100644 index 68b534c64..000000000 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert +++ /dev/null @@ -1,20 +0,0 @@ -#version 330 core - -layout (location = 0) in vec3 pos; -layout (location = 1) in vec4 color; - -uniform mat4 u_ProjModel; - -uniform vec3 u_CameraPosition; - -out vec4 v_Color; - -#define VERTEX_POSITION pos - u_CameraPosition - -#define SCREEN_CENTER vec4(0.0, 0.0, 0.0, 1.0) -#define PROJECTED u_ProjModel * vec4(VERTEX_POSITION, 1.0) - -void main() { - gl_Position = gl_VertexID % 2 == 0 ? SCREEN_CENTER : PROJECTED; - v_Color = color; -}