aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/events/WorldKeyboardEvent.kt17
-rw-r--r--src/main/kotlin/events/WorldMouseMoveEvent.kt5
-rw-r--r--src/main/kotlin/features/macros/KeyComboTrie.kt5
-rw-r--r--src/main/kotlin/features/macros/MacroData.kt5
-rw-r--r--src/main/kotlin/features/macros/MacroUI.kt170
-rw-r--r--src/main/kotlin/features/macros/RadialMenu.kt149
-rw-r--r--src/main/kotlin/keybindings/IKeyBinding.kt31
-rw-r--r--src/main/kotlin/keybindings/SavedKeyBinding.kt7
-rw-r--r--src/main/kotlin/util/collections/RangeUtil.kt40
-rw-r--r--src/main/kotlin/util/math/Projections.kt46
-rw-r--r--src/main/kotlin/util/render/CustomRenderLayers.kt37
-rw-r--r--src/main/kotlin/util/render/DrawContextExt.kt11
-rw-r--r--src/main/kotlin/util/render/LerpUtils.kt35
-rw-r--r--src/main/kotlin/util/render/RenderCircleProgress.kt118
14 files changed, 554 insertions, 122 deletions
diff --git a/src/main/kotlin/events/WorldKeyboardEvent.kt b/src/main/kotlin/events/WorldKeyboardEvent.kt
index e8566fd..1d6a758 100644
--- a/src/main/kotlin/events/WorldKeyboardEvent.kt
+++ b/src/main/kotlin/events/WorldKeyboardEvent.kt
@@ -1,18 +1,17 @@
-
-
package moe.nea.firmament.events
import net.minecraft.client.option.KeyBinding
import moe.nea.firmament.keybindings.IKeyBinding
data class WorldKeyboardEvent(val keyCode: Int, val scanCode: Int, val modifiers: Int) : FirmamentEvent.Cancellable() {
- companion object : FirmamentEventBus<WorldKeyboardEvent>()
+ companion object : FirmamentEventBus<WorldKeyboardEvent>()
- fun matches(keyBinding: KeyBinding): Boolean {
- return matches(IKeyBinding.minecraft(keyBinding))
- }
+ fun matches(keyBinding: KeyBinding): Boolean {
+ return matches(IKeyBinding.minecraft(keyBinding))
+ }
- fun matches(keyBinding: IKeyBinding): Boolean {
- return keyBinding.matches(keyCode, scanCode, modifiers)
- }
+ fun matches(keyBinding: IKeyBinding, atLeast: Boolean = false): Boolean {
+ return if (atLeast) keyBinding.matchesAtLeast(keyCode, scanCode, modifiers) else
+ keyBinding.matches(keyCode, scanCode, modifiers)
+ }
}
diff --git a/src/main/kotlin/events/WorldMouseMoveEvent.kt b/src/main/kotlin/events/WorldMouseMoveEvent.kt
new file mode 100644
index 0000000..7a17ba4
--- /dev/null
+++ b/src/main/kotlin/events/WorldMouseMoveEvent.kt
@@ -0,0 +1,5 @@
+package moe.nea.firmament.events
+
+data class WorldMouseMoveEvent(val deltaX: Double, val deltaY: Double) : FirmamentEvent.Cancellable() {
+ companion object : FirmamentEventBus<WorldMouseMoveEvent>()
+}
diff --git a/src/main/kotlin/features/macros/KeyComboTrie.kt b/src/main/kotlin/features/macros/KeyComboTrie.kt
index 57ff289..452bc56 100644
--- a/src/main/kotlin/features/macros/KeyComboTrie.kt
+++ b/src/main/kotlin/features/macros/KeyComboTrie.kt
@@ -44,6 +44,11 @@ sealed interface KeyComboTrie {
}
}
+@Serializable
+data class MacroWheel(
+ val key: SavedKeyBinding,
+ val options: List<HotkeyAction>
+)
@Serializable
data class ComboKeyAction(
diff --git a/src/main/kotlin/features/macros/MacroData.kt b/src/main/kotlin/features/macros/MacroData.kt
index 78a5948..91de423 100644
--- a/src/main/kotlin/features/macros/MacroData.kt
+++ b/src/main/kotlin/features/macros/MacroData.kt
@@ -5,7 +5,8 @@ import moe.nea.firmament.util.data.DataHolder
@Serializable
data class MacroData(
- var comboActions: List<ComboKeyAction> = listOf(),
-){
+ var comboActions: List<ComboKeyAction> = listOf(),
+ var wheels: List<MacroWheel> = listOf(),
+) {
object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData)
}
diff --git a/src/main/kotlin/features/macros/MacroUI.kt b/src/main/kotlin/features/macros/MacroUI.kt
index 17fdd0a..88f20a1 100644
--- a/src/main/kotlin/features/macros/MacroUI.kt
+++ b/src/main/kotlin/features/macros/MacroUI.kt
@@ -32,7 +32,142 @@ class MacroUI {
@field:Bind("combos")
val combos = Combos()
- class Combos {
+ @field:Bind("wheels")
+ val wheels = Wheels()
+ var dontSave = false
+
+ @Bind
+ fun beforeClose(): CloseEventListener.CloseAction {
+ if (!dontSave)
+ save()
+ return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE
+ }
+
+ fun save() {
+ MacroData.DConfig.data.comboActions = combos.actions.map { it.asSaveable() }
+ MacroData.DConfig.data.wheels = wheels.wheels.map { it.asSaveable() }
+ MacroData.DConfig.markDirty()
+ RadialMacros.setWheels(MacroData.DConfig.data.wheels)
+ ComboProcessor.setActions(MacroData.DConfig.data.comboActions)
+ }
+
+ fun discard() {
+ dontSave = true
+ MC.screen?.close()
+ }
+
+ class Command(
+ @field:Bind("text")
+ var text: String,
+ val parent: Wheel,
+ ) {
+ @Bind
+ fun delete() {
+ parent.editableCommands.removeIf { it === this }
+ parent.editableCommands.update()
+ parent.commands.update()
+ }
+
+ fun asCommandAction() = CommandAction(text)
+ }
+
+ inner class Wheel(
+ val parent: Wheels,
+ var binding: SavedKeyBinding,
+ commands: List<CommandAction>,
+ ) {
+
+ fun asSaveable(): MacroWheel {
+ return MacroWheel(binding, commands.map { it.asCommandAction() })
+ }
+
+ @Bind("keyCombo")
+ fun text() = binding.format().string
+
+ @field:Bind("commands")
+ val commands = commands.mapTo(ObservableList(mutableListOf())) { Command(it.command, this) }
+
+ @field:Bind("editableCommands")
+ val editableCommands = this.commands.toObservableList()
+
+ @Bind
+ fun addOption() {
+ editableCommands.add(Command("", this))
+ }
+
+ @Bind
+ fun back() {
+ MC.screen?.close()
+ }
+
+ @Bind
+ fun edit() {
+ MC.screen = MoulConfigUtils.loadScreen("config/macros/editor_wheel", this, MC.screen)
+ }
+
+ @Bind
+ fun delete() {
+ parent.wheels.removeIf { it === this }
+ parent.wheels.update()
+ }
+
+ val sm = KeyBindingStateManager(
+ { binding },
+ { binding = it },
+ ::blur,
+ ::requestFocus
+ )
+
+ @field:Bind
+ val button = sm.createButton()
+
+ init {
+ sm.updateLabel()
+ }
+
+ fun blur() {
+ button.blur()
+ }
+
+
+ fun requestFocus() {
+ button.requestFocus()
+ }
+ }
+
+ inner class Wheels {
+ @field:Bind("wheels")
+ val wheels: ObservableList<Wheel> = MacroData.DConfig.data.wheels.mapTo(ObservableList(mutableListOf())) {
+ Wheel(this, it.key, it.options.map { CommandAction((it as CommandAction).command) })
+ }
+
+ @Bind
+ fun discard() {
+ this@MacroUI.discard()
+ }
+
+ @Bind
+ fun saveAndClose() {
+ this@MacroUI.saveAndClose()
+ }
+
+ @Bind
+ fun save() {
+ this@MacroUI.save()
+ }
+
+ @Bind
+ fun addWheel() {
+ wheels.add(Wheel(this, SavedKeyBinding.unbound(), listOf()))
+ }
+ }
+
+ fun saveAndClose() {
+ save()
+ MC.screen?.close()
+ }
+
+ inner class Combos {
@field:Bind("actions")
val actions: ObservableList<ActionEditor> = ObservableList(
MacroData.DConfig.data.comboActions.mapTo(mutableListOf()) {
@@ -40,15 +175,6 @@ class MacroUI {
}
)
- var dontSave = false
-
- @Bind
- fun beforeClose(): CloseEventListener.CloseAction {
- if (!dontSave)
- save()
- return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE
- }
-
@Bind
fun addCommand() {
actions.add(
@@ -64,21 +190,17 @@ class MacroUI {
@Bind
fun discard() {
- dontSave = true
- MC.screen?.close()
+ this@MacroUI.discard()
}
@Bind
fun saveAndClose() {
- save()
- MC.screen?.close()
+ this@MacroUI.discard()
}
@Bind
fun save() {
- MacroData.DConfig.data.comboActions = actions.map { it.asSaveable() }
- MacroData.DConfig.markDirty()
- ComboProcessor.setActions(MacroData.DConfig.data.comboActions) // TODO: automatically reload those from the config on startup
+ this@MacroUI.save()
}
}
@@ -101,18 +223,19 @@ class MacroUI {
button.blur()
}
+
+ fun requestFocus() {
+ button.requestFocus()
+ }
+
@Bind
fun delete() {
parent.combo.removeIf { it === this }
parent.combo.update()
}
-
- fun requestFocus() {
- button.requestFocus()
- }
}
- class ActionEditor(val action: ComboKeyAction, val parent: MacroUI.Combos) {
+ class ActionEditor(val action: ComboKeyAction, val parent: Combos) {
fun asSaveable(): ComboKeyAction {
return ComboKeyAction(
CommandAction(command),
@@ -145,9 +268,10 @@ class MacroUI {
parent.actions.removeIf { it === this }
parent.actions.update()
}
+
@Bind
fun edit() {
- MC.screen = MoulConfigUtils.loadScreen("config/macros/editor", this, MC.screen)
+ MC.screen = MoulConfigUtils.loadScreen("config/macros/editor_combo", this, MC.screen)
}
}
}
diff --git a/src/main/kotlin/features/macros/RadialMenu.kt b/src/main/kotlin/features/macros/RadialMenu.kt
new file mode 100644
index 0000000..2e09c44
--- /dev/null
+++ b/src/main/kotlin/features/macros/RadialMenu.kt
@@ -0,0 +1,149 @@
+package moe.nea.firmament.features.macros
+
+import org.joml.Vector2f
+import util.render.CustomRenderLayers
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+import net.minecraft.client.gui.DrawContext
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.HudRenderEvent
+import moe.nea.firmament.events.TickEvent
+import moe.nea.firmament.events.WorldKeyboardEvent
+import moe.nea.firmament.events.WorldMouseMoveEvent
+import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenu
+import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenuOption
+import moe.nea.firmament.keybindings.SavedKeyBinding
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.render.RenderCircleProgress
+import moe.nea.firmament.util.render.drawLine
+import moe.nea.firmament.util.render.lerpAngle
+import moe.nea.firmament.util.render.wrapAngle
+import moe.nea.firmament.util.render.τ
+
+object RadialMenuViewer {
+ interface RadialMenu {
+ val key: SavedKeyBinding
+ val options: List<RadialMenuOption>
+ }
+
+ interface RadialMenuOption {
+ val isEnabled: Boolean
+ fun resolve()
+ fun renderSlice(drawContext: DrawContext)
+ }
+
+ var activeMenu: RadialMenu? = null
+ set(value) {
+ field = value
+ delta = Vector2f(0F, 0F)
+ }
+ var delta = Vector2f(0F, 0F)
+ val maxSelectionSize = 100F
+
+ @Subscribe
+ fun onMouseMotion(event: WorldMouseMoveEvent) {
+ val menu = activeMenu ?: return
+ event.cancel()
+ delta.add(event.deltaX.toFloat(), event.deltaY.toFloat())
+ val m = delta.lengthSquared()
+ if (m > maxSelectionSize * maxSelectionSize) {
+ delta.mul(maxSelectionSize / sqrt(m))
+ }
+ }
+
+ val INNER_CIRCLE_RADIUS = 16
+
+ @Subscribe
+ fun onRender(event: HudRenderEvent) {
+ val menu = activeMenu ?: return
+ val mat = event.context.matrices
+ mat.push()
+ mat.translate(
+ (MC.window.scaledWidth) / 2F,
+ (MC.window.scaledHeight) / 2F,
+ 0F
+ )
+ val sliceWidth = (τ / menu.options.size).toFloat()
+ var selectedAngle = wrapAngle(atan2(delta.y, delta.x))
+ if (delta.lengthSquared() < INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS)
+ selectedAngle = Float.NaN
+ for ((idx, option) in menu.options.withIndex()) {
+ val range = (sliceWidth * idx)..(sliceWidth * (idx + 1))
+ mat.push()
+ mat.scale(64F, 64F, 1F)
+ val cutout = INNER_CIRCLE_RADIUS / 64F / 2
+ RenderCircleProgress.renderCircularSlice(
+ event.context,
+ CustomRenderLayers.TRANSLUCENT_CIRCLE_GUI,
+ 0F, 1F, 0F, 1F,
+ range,
+ color = if (selectedAngle in range) 0x70A0A0A0 else 0x70FFFFFF,
+ innerCutoutRadius = cutout
+ )
+ mat.pop()
+ mat.push()
+ val centreAngle = lerpAngle(range.start, range.endInclusive, 0.5F)
+ val vec = Vector2f(cos(centreAngle), sin(centreAngle)).mul(40F)
+ mat.translate(vec.x, vec.y, 0F)
+ option.renderSlice(event.context)
+ mat.pop()
+ }
+ event.context.drawLine(1, 1, delta.x.toInt(), delta.y.toInt(), me.shedaniel.math.Color.ofOpaque(0x00FF00))
+ mat.pop()
+ }
+
+ @Subscribe
+ fun onTick(event: TickEvent) {
+ val menu = activeMenu ?: return
+ if (!menu.key.isPressed(true)) {
+ val angle = atan2(delta.y, delta.x)
+
+ val choiceIndex = (wrapAngle(angle) * menu.options.size / τ).toInt()
+ val choice = menu.options[choiceIndex]
+ val selectedAny = delta.lengthSquared() > INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS
+ activeMenu = null
+ if (selectedAny)
+ choice.resolve()
+ }
+ }
+
+}
+
+object RadialMacros {
+ var wheels = MacroData.DConfig.data.wheels
+ private set
+
+ fun setWheels(wheels: List<MacroWheel>) {
+ this.wheels = wheels
+ RadialMenuViewer.activeMenu = null
+ }
+
+ @Subscribe
+ fun onOpen(event: WorldKeyboardEvent) {
+ if (RadialMenuViewer.activeMenu != null) return
+ wheels.forEach { wheel ->
+ if (event.matches(wheel.key, atLeast = true)) {
+ class R(val action: HotkeyAction) : RadialMenuOption {
+ override val isEnabled: Boolean
+ get() = true
+
+ override fun resolve() {
+ action.execute()
+ }
+
+ override fun renderSlice(drawContext: DrawContext) {
+ drawContext.drawCenteredTextWithShadow(MC.font, action.label, 0, 0, -1)
+ }
+ }
+ RadialMenuViewer.activeMenu = object : RadialMenu {
+ override val key: SavedKeyBinding
+ get() = wheel.key
+ override val options: List<RadialMenuOption> =
+ wheel.options.map { R(it) }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/keybindings/IKeyBinding.kt b/src/main/kotlin/keybindings/IKeyBinding.kt
index 1975361..9d9b106 100644
--- a/src/main/kotlin/keybindings/IKeyBinding.kt
+++ b/src/main/kotlin/keybindings/IKeyBinding.kt
@@ -6,24 +6,45 @@ import net.minecraft.client.option.KeyBinding
interface IKeyBinding {
fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean
+ fun matchesAtLeast(keyCode: Int, scanCode: Int, modifiers: Int): Boolean
fun withModifiers(wantedModifiers: Int): IKeyBinding {
val old = this
return object : IKeyBinding {
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
- return old.matches(keyCode, scanCode, modifiers) && (modifiers and wantedModifiers) == wantedModifiers
+ return old.matchesAtLeast(keyCode, scanCode, modifiers) && (modifiers and wantedModifiers) == wantedModifiers
}
- }
+
+ override fun matchesAtLeast(
+ keyCode: Int,
+ scanCode: Int,
+ modifiers: Int
+ ): Boolean {
+ return old.matchesAtLeast(keyCode, scanCode, modifiers) && (modifiers.inv() and wantedModifiers) == 0
+ }
+ }
}
companion object {
fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding {
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) =
keyBinding.matchesKey(keyCode, scanCode)
- }
+
+ override fun matchesAtLeast(
+ keyCode: Int,
+ scanCode: Int,
+ modifiers: Int
+ ): Boolean =
+ keyBinding.matchesKey(keyCode, scanCode)
+ }
fun ofKeyCode(wantedKeyCode: Int) = object : IKeyBinding {
- override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean = keyCode == wantedKeyCode
- }
+ override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean = keyCode == wantedKeyCode && modifiers == 0
+ override fun matchesAtLeast(
+ keyCode: Int,
+ scanCode: Int,
+ modifiers: Int
+ ): Boolean = keyCode == wantedKeyCode
+ }
}
}
diff --git a/src/main/kotlin/keybindings/SavedKeyBinding.kt b/src/main/kotlin/keybindings/SavedKeyBinding.kt
index 3ae0b89..fc0270d 100644
--- a/src/main/kotlin/keybindings/SavedKeyBinding.kt
+++ b/src/main/kotlin/keybindings/SavedKeyBinding.kt
@@ -7,6 +7,7 @@ import net.minecraft.client.util.InputUtil
import net.minecraft.text.Text
import moe.nea.firmament.util.MC
+// TODO: add support for mouse keybindings
@Serializable
data class SavedKeyBinding(
val keyCode: Int,
@@ -86,6 +87,12 @@ data class SavedKeyBinding(
(shift == this.shift)
}
+ override fun matchesAtLeast(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false
+ val (shift, ctrl, alt) = getMods(modifiers)
+ return keyCode == this.keyCode && this.shift <= shift && this.ctrl <= ctrl && this.alt <= alt
+ }
+
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false
return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt)
diff --git a/src/main/kotlin/util/collections/RangeUtil.kt b/src/main/kotlin/util/collections/RangeUtil.kt
new file mode 100644
index 0000000..a7029ac
--- /dev/null
+++ b/src/main/kotlin/util/collections/RangeUtil.kt
@@ -0,0 +1,40 @@
+package moe.nea.firmament.util.collections
+
+import kotlin.math.floor
+
+val ClosedFloatingPointRange<Float>.centre get() = (endInclusive + start) / 2
+
+fun ClosedFloatingPointRange<Float>.nonNegligibleSubSectionsAlignedWith(
+ interval: Float
+): Iterable<Float> {
+ require(interval.isFinite())
+ val range = this
+ return object : Iterable<Float> {
+ override fun iterator(): Iterator<Float> {
+ return object : FloatIterator() {
+ var polledValue: Float = range.start
+ var lastValue: Float = polledValue
+
+ override fun nextFloat(): Float {
+ if (!hasNext()) throw NoSuchElementException()
+ lastValue = polledValue
+ polledValue = Float.NaN
+ return lastValue
+ }
+
+ override fun hasNext(): Boolean {
+ if (!polledValue.isNaN()) {
+ return true
+ }
+ if (lastValue == range.endInclusive)
+ return false
+ polledValue = (floor(lastValue / interval) + 1) * interval
+ if (polledValue > range.endInclusive) {
+ polledValue = range.endInclusive
+ }
+ return true
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/util/math/Projections.kt b/src/main/kotlin/util/math/Projections.kt
new file mode 100644
index 0000000..359b21b
--- /dev/null
+++ b/src/main/kotlin/util/math/Projections.kt
@@ -0,0 +1,46 @@
+package moe.nea.firmament.util.math
+
+import kotlin.math.absoluteValue
+import kotlin.math.cos
+import kotlin.math.sin
+import net.minecraft.util.math.Vec2f
+import moe.nea.firmament.util.render.wrapAngle
+
+object Projections {
+ object Two {
+ val ε = 1e-6
+ val π = moe.nea.firmament.util.render.π
+ val τ = 2 * π
+
+ fun isNullish(float: Float) = float.absoluteValue < ε
+
+ fun xInterceptOfLine(origin: Vec2f, direction: Vec2f): Vec2f? {
+ if (isNullish(direction.x))
+ return Vec2f(origin.x, 0F)
+ if (isNullish(direction.y))
+ return null
+
+ val slope = direction.y / direction.x
+ return Vec2f(origin.x - origin.y / slope, 0F)
+ }
+
+ fun interceptAlongCardinal(distanceFromAxis: Float, slope: Float): Float? {
+ if (isNullish(slope))
+ return null
+ return -distanceFromAxis / slope
+ }
+
+ fun projectAngleOntoUnitBox(angleRadians: Double): Vec2f {
+ val angleRadians = wrapAngle(angleRadians)
+ val cx = cos(angleRadians)
+ val cy = sin(angleRadians)
+
+ val ex = 1 / cx.absoluteValue
+ val ey = 1 / cy.absoluteValue
+
+ val e = minOf(ex, ey)
+
+ return Vec2f((cx * e).toFloat(), (cy * e).toFloat())
+ }
+ }
+}
diff --git a/src/main/kotlin/util/render/CustomRenderLayers.kt b/src/main/kotlin/util/render/CustomRenderLayers.kt
index 7f3cdec..be0bbd7 100644
--- a/src/main/kotlin/util/render/CustomRenderLayers.kt
+++ b/src/main/kotlin/util/render/CustomRenderLayers.kt
@@ -1,10 +1,12 @@
package util.render
+import com.mojang.blaze3d.pipeline.BlendFunction
import com.mojang.blaze3d.pipeline.RenderPipeline
import com.mojang.blaze3d.platform.DepthTestFunction
import com.mojang.blaze3d.vertex.VertexFormat.DrawMode
import java.util.function.Function
import net.minecraft.client.gl.RenderPipelines
+import net.minecraft.client.gl.UniformType
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.VertexFormats
@@ -38,23 +40,33 @@ object CustomRenderPipelines {
.withCull(false)
.withDepthWrite(false)
.build()
+
+ val CIRCLE_FILTER_TRANSLUCENT_GUI_TRIS =
+ RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET)
+ .withVertexFormat(VertexFormats.POSITION_TEXTURE_COLOR, DrawMode.TRIANGLES)
+ .withLocation(Firmament.identifier("gui_textured_overlay_tris_circle"))
+ .withUniform("InnerCutoutRadius", UniformType.FLOAT)
+ .withFragmentShader(Firmament.identifier("circle_discard_color"))
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .build()
}
object CustomRenderLayers {
-
-
inline fun memoizeTextured(crossinline func: (Identifier) -> RenderLayer) = memoize(func)
inline fun <T, R> memoize(crossinline func: (T) -> R): Function<T, R> {
return Util.memoize { it: T -> func(it) }
}
val GUI_TEXTURED_NO_DEPTH_TRIS = memoizeTextured { texture ->
- RenderLayer.of("firmament_gui_textured_overlay_tris",
- RenderLayer.DEFAULT_BUFFER_SIZE,
- CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS,
- RenderLayer.MultiPhaseParameters.builder().texture(
- RenderPhase.Texture(texture, TriState.DEFAULT, false))
- .build(false))
+ RenderLayer.of(
+ "firmament_gui_textured_overlay_tris",
+ RenderLayer.DEFAULT_BUFFER_SIZE,
+ CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS,
+ RenderLayer.MultiPhaseParameters.builder().texture(
+ RenderPhase.Texture(texture, TriState.DEFAULT, false)
+ )
+ .build(false)
+ )
}
val LINES = RenderLayer.of(
"firmament_lines",
@@ -71,4 +83,13 @@ object CustomRenderLayers {
.lightmap(RenderPhase.DISABLE_LIGHTMAP)
.build(false)
)
+
+ val TRANSLUCENT_CIRCLE_GUI =
+ RenderLayer.of(
+ "firmament_circle_gui",
+ RenderLayer.DEFAULT_BUFFER_SIZE,
+ CustomRenderPipelines.CIRCLE_FILTER_TRANSLUCENT_GUI_TRIS,
+ RenderLayer.MultiPhaseParameters.builder()
+ .build(false)
+ )
}
diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt
index fa92cd7..a833c86 100644
--- a/src/main/kotlin/util/render/DrawContextExt.kt
+++ b/src/main/kotlin/util/render/DrawContextExt.kt
@@ -1,18 +1,12 @@
package moe.nea.firmament.util.render
-import com.mojang.blaze3d.pipeline.RenderPipeline
-import com.mojang.blaze3d.platform.DepthTestFunction
import com.mojang.blaze3d.systems.RenderSystem
-import com.mojang.blaze3d.vertex.VertexFormat.DrawMode
import me.shedaniel.math.Color
import org.joml.Matrix4f
import util.render.CustomRenderLayers
-import net.minecraft.client.gl.RenderPipelines
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.VertexFormats
import net.minecraft.util.Identifier
-import moe.nea.firmament.Firmament
import moe.nea.firmament.util.MC
fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
@@ -64,9 +58,10 @@ fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Colo
RenderSystem.lineWidth(MC.window.scaleFactor.toFloat())
draw { vertexConsumers ->
val buf = vertexConsumers.getBuffer(CustomRenderLayers.LINES)
- buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color)
+ val matrix = this.matrices.peek()
+ buf.vertex(matrix, fromX.toFloat(), fromY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
- buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color)
+ buf.vertex(matrix, toX.toFloat(), toY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
}
}
diff --git a/src/main/kotlin/util/render/LerpUtils.kt b/src/main/kotlin/util/render/LerpUtils.kt
index f2c2f25..63a13ec 100644
--- a/src/main/kotlin/util/render/LerpUtils.kt
+++ b/src/main/kotlin/util/render/LerpUtils.kt
@@ -1,33 +1,36 @@
-
package moe.nea.firmament.util.render
import me.shedaniel.math.Color
-val pi = Math.PI
-val tau = Math.PI * 2
-fun lerpAngle(a: Float, b: Float, progress: Float): Float {
- // TODO: there is at least 10 mods to many in here lol
- val shortestAngle = ((((b.mod(tau) - a.mod(tau)).mod(tau)) + tau + pi).mod(tau)) - pi
- return ((a + (shortestAngle) * progress).mod(tau)).toFloat()
+val π = Math.PI
+val τ = Math.PI * 2
+fun lerpAngle(a: Float, b: Float, progress: Float): Float {
+ // TODO: there is at least 10 mods to many in here lol
+ val shortestAngle = ((((b.mod(τ) - a.mod(τ)).mod(τ)) + τ + π).mod(τ)) - π
+ return ((a + (shortestAngle) * progress).mod(τ)).toFloat()
}
+fun wrapAngle(angle: Float): Float = (angle.mod(τ) + τ).mod(τ).toFloat()
+fun wrapAngle(angle: Double): Double = (angle.mod(τ) + τ).mod(τ)
+
fun lerp(a: Float, b: Float, progress: Float): Float {
- return a + (b - a) * progress
+ return a + (b - a) * progress
}
+
fun lerp(a: Int, b: Int, progress: Float): Int {
- return (a + (b - a) * progress).toInt()
+ return (a + (b - a) * progress).toInt()
}
fun ilerp(a: Float, b: Float, value: Float): Float {
- return (value - a) / (b - a)
+ return (value - a) / (b - a)
}
fun lerp(a: Color, b: Color, progress: Float): Color {
- return Color.ofRGBA(
- lerp(a.red, b.red, progress),
- lerp(a.green, b.green, progress),
- lerp(a.blue, b.blue, progress),
- lerp(a.alpha, b.alpha, progress),
- )
+ return Color.ofRGBA(
+ lerp(a.red, b.red, progress),
+ lerp(a.green, b.green, progress),
+ lerp(a.blue, b.blue, progress),
+ lerp(a.alpha, b.alpha, progress),
+ )
}
diff --git a/src/main/kotlin/util/render/RenderCircleProgress.kt b/src/main/kotlin/util/render/RenderCircleProgress.kt
index d759033..81dde6f 100644
--- a/src/main/kotlin/util/render/RenderCircleProgress.kt
+++ b/src/main/kotlin/util/render/RenderCircleProgress.kt
@@ -1,85 +1,101 @@
package moe.nea.firmament.util.render
+import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.VertexFormat
import io.github.notenoughupdates.moulconfig.platform.next
+import java.util.OptionalInt
import org.joml.Matrix4f
-import org.joml.Vector2f
import util.render.CustomRenderLayers
-import kotlin.math.atan2
-import kotlin.math.tan
import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.render.BufferBuilder
+import net.minecraft.client.render.RenderLayer
+import net.minecraft.client.util.BufferAllocator
import net.minecraft.util.Identifier
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.collections.nonNegligibleSubSectionsAlignedWith
+import moe.nea.firmament.util.math.Projections
object RenderCircleProgress {
- fun renderCircle(
+ fun renderCircularSlice(
drawContext: DrawContext,
- texture: Identifier,
- progress: Float,
+ layer: RenderLayer,
u1: Float,
u2: Float,
v1: Float,
v2: Float,
+ angleRadians: ClosedFloatingPointRange<Float>,
+ color: Int = -1,
+ innerCutoutRadius: Float = 0F
) {
- drawContext.draw {
- val bufferBuilder = it.getBuffer(CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture))
- val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
-
- val corners = listOf(
- Vector2f(0F, -1F),
- Vector2f(1F, -1F),
- Vector2f(1F, 0F),
- Vector2f(1F, 1F),
- Vector2f(0F, 1F),
- Vector2f(-1F, 1F),
- Vector2f(-1F, 0F),
- Vector2f(-1F, -1F),
- )
+ drawContext.draw()
+ val sections = angleRadians.nonNegligibleSubSectionsAlignedWith((τ / 8f).toFloat())
+ .zipWithNext().toList()
+ BufferAllocator(layer.vertexFormat.vertexSize * sections.size * 3).use { allocator ->
- for (i in (0 until 8)) {
- if (progress < i / 8F) {
- break
- }
- val second = corners[(i + 1) % 8]
- val first = corners[i]
- if (progress <= (i + 1) / 8F) {
- val internalProgress = 1 - (progress - i / 8F) * 8F
- val angle = lerpAngle(
- atan2(second.y, second.x),
- atan2(first.y, first.x),
- internalProgress
- )
- if (angle < tau / 8 || angle >= tau * 7 / 8) {
- second.set(1F, tan(angle))
- } else if (angle < tau * 3 / 8) {
- second.set(1 / tan(angle), 1F)
- } else if (angle < tau * 5 / 8) {
- second.set(-1F, -tan(angle))
- } else {
- second.set(-1 / tan(angle), -1F)
- }
- }
+ val bufferBuilder = BufferBuilder(allocator, VertexFormat.DrawMode.TRIANGLES, layer.vertexFormat)
+ val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
+ for ((sectionStart, sectionEnd) in sections) {
+ val firstPoint = Projections.Two.projectAngleOntoUnitBox(sectionStart.toDouble())
+ val secondPoint = Projections.Two.projectAngleOntoUnitBox(sectionEnd.toDouble())
fun ilerp(f: Float): Float =
ilerp(-1f, 1f, f)
bufferBuilder
- .vertex(matrix, second.x, second.y, 0F)
- .texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y)))
- .color(-1)
+ .vertex(matrix, secondPoint.x, secondPoint.y, 0F)
+ .texture(lerp(u1, u2, ilerp(secondPoint.x)), lerp(v1, v2, ilerp(secondPoint.y)))
+ .color(color)
.next()
bufferBuilder
- .vertex(matrix, first.x, first.y, 0F)
- .texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
- .color(-1)
+ .vertex(matrix, firstPoint.x, firstPoint.y, 0F)
+ .texture(lerp(u1, u2, ilerp(firstPoint.x)), lerp(v1, v2, ilerp(firstPoint.y)))
+ .color(color)
.next()
bufferBuilder
.vertex(matrix, 0F, 0F, 0F)
.texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
- .color(-1)
+ .color(color)
.next()
}
+
+ bufferBuilder.end().use { buffer ->
+ // TODO: write a better utility to pass uniforms :sob: ill even take a mixin at this point
+ if (innerCutoutRadius <= 0) {
+ layer.draw(buffer)
+ return
+ }
+ val vertexBuffer = layer.vertexFormat.uploadImmediateVertexBuffer(buffer.buffer)
+ val indexBufferConstructor = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.TRIANGLES)
+ val indexBuffer = indexBufferConstructor.getIndexBuffer(buffer.drawParameters.indexCount)
+ RenderSystem.getDevice().createCommandEncoder().createRenderPass(
+ MC.instance.framebuffer.colorAttachment,
+ OptionalInt.empty(),
+ ).use { renderPass ->
+ renderPass.setPipeline(layer.pipeline)
+ renderPass.setUniform("InnerCutoutRadius", innerCutoutRadius)
+ renderPass.setIndexBuffer(indexBuffer, indexBufferConstructor.indexType)
+ renderPass.setVertexBuffer(0, vertexBuffer)
+ renderPass.drawIndexed(0, buffer.drawParameters.indexCount)
+ }
+ }
}
}
-
+ fun renderCircle(
+ drawContext: DrawContext,
+ texture: Identifier,
+ progress: Float,
+ u1: Float,
+ u2: Float,
+ v1: Float,
+ v2: Float,
+ ) {
+ renderCircularSlice(
+ drawContext,
+ CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture),
+ u1, u2, v1, v2,
+ (-τ / 4).toFloat()..(progress * τ - τ / 4).toFloat()
+ )
+ }
}