feat: Add macro wheels
This commit is contained in:
BIN
docs/firmament_logo_256_trans.webp
Normal file
BIN
docs/firmament_logo_256_trans.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
BIN
docs/firmament_logo_trans.webp
Normal file
BIN
docs/firmament_logo_trans.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||||
|
import moe.nea.firmament.events.WorldMouseMoveEvent;
|
||||||
|
import net.minecraft.client.Mouse;
|
||||||
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
|
@Mixin(Mouse.class)
|
||||||
|
public class DispatchMouseInputEventsPatch {
|
||||||
|
@WrapWithCondition(method = "updateMouse", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;changeLookDirection(DD)V"))
|
||||||
|
public boolean onRotatePlayer(ClientPlayerEntity instance, double deltaX, double deltaY) {
|
||||||
|
var event = WorldMouseMoveEvent.Companion.publish(new WorldMouseMoveEvent(deltaX, deltaY));
|
||||||
|
return !event.getCancelled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
package moe.nea.firmament.events
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
import net.minecraft.client.option.KeyBinding
|
import net.minecraft.client.option.KeyBinding
|
||||||
import moe.nea.firmament.keybindings.IKeyBinding
|
import moe.nea.firmament.keybindings.IKeyBinding
|
||||||
|
|
||||||
data class WorldKeyboardEvent(val keyCode: Int, val scanCode: Int, val modifiers: Int) : FirmamentEvent.Cancellable() {
|
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 {
|
fun matches(keyBinding: KeyBinding): Boolean {
|
||||||
return matches(IKeyBinding.minecraft(keyBinding))
|
return matches(IKeyBinding.minecraft(keyBinding))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun matches(keyBinding: IKeyBinding): Boolean {
|
fun matches(keyBinding: IKeyBinding, atLeast: Boolean = false): Boolean {
|
||||||
return keyBinding.matches(keyCode, scanCode, modifiers)
|
return if (atLeast) keyBinding.matchesAtLeast(keyCode, scanCode, modifiers) else
|
||||||
}
|
keyBinding.matches(keyCode, scanCode, modifiers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/main/kotlin/events/WorldMouseMoveEvent.kt
Normal file
5
src/main/kotlin/events/WorldMouseMoveEvent.kt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
|
data class WorldMouseMoveEvent(val deltaX: Double, val deltaY: Double) : FirmamentEvent.Cancellable() {
|
||||||
|
companion object : FirmamentEventBus<WorldMouseMoveEvent>()
|
||||||
|
}
|
||||||
@@ -44,6 +44,11 @@ sealed interface KeyComboTrie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MacroWheel(
|
||||||
|
val key: SavedKeyBinding,
|
||||||
|
val options: List<HotkeyAction>
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ComboKeyAction(
|
data class ComboKeyAction(
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import moe.nea.firmament.util.data.DataHolder
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MacroData(
|
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)
|
object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,142 @@ class MacroUI {
|
|||||||
@field:Bind("combos")
|
@field:Bind("combos")
|
||||||
val combos = 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")
|
@field:Bind("actions")
|
||||||
val actions: ObservableList<ActionEditor> = ObservableList(
|
val actions: ObservableList<ActionEditor> = ObservableList(
|
||||||
MacroData.DConfig.data.comboActions.mapTo(mutableListOf()) {
|
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
|
@Bind
|
||||||
fun addCommand() {
|
fun addCommand() {
|
||||||
actions.add(
|
actions.add(
|
||||||
@@ -64,21 +190,17 @@ class MacroUI {
|
|||||||
|
|
||||||
@Bind
|
@Bind
|
||||||
fun discard() {
|
fun discard() {
|
||||||
dontSave = true
|
this@MacroUI.discard()
|
||||||
MC.screen?.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bind
|
@Bind
|
||||||
fun saveAndClose() {
|
fun saveAndClose() {
|
||||||
save()
|
this@MacroUI.discard()
|
||||||
MC.screen?.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bind
|
@Bind
|
||||||
fun save() {
|
fun save() {
|
||||||
MacroData.DConfig.data.comboActions = actions.map { it.asSaveable() }
|
this@MacroUI.save()
|
||||||
MacroData.DConfig.markDirty()
|
|
||||||
ComboProcessor.setActions(MacroData.DConfig.data.comboActions) // TODO: automatically reload those from the config on startup
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,18 +223,19 @@ class MacroUI {
|
|||||||
button.blur()
|
button.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun requestFocus() {
|
||||||
|
button.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
@Bind
|
@Bind
|
||||||
fun delete() {
|
fun delete() {
|
||||||
parent.combo.removeIf { it === this }
|
parent.combo.removeIf { it === this }
|
||||||
parent.combo.update()
|
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 {
|
fun asSaveable(): ComboKeyAction {
|
||||||
return ComboKeyAction(
|
return ComboKeyAction(
|
||||||
CommandAction(command),
|
CommandAction(command),
|
||||||
@@ -145,9 +268,10 @@ class MacroUI {
|
|||||||
parent.actions.removeIf { it === this }
|
parent.actions.removeIf { it === this }
|
||||||
parent.actions.update()
|
parent.actions.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bind
|
@Bind
|
||||||
fun edit() {
|
fun edit() {
|
||||||
MC.screen = MoulConfigUtils.loadScreen("config/macros/editor", this, MC.screen)
|
MC.screen = MoulConfigUtils.loadScreen("config/macros/editor_combo", this, MC.screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/main/kotlin/features/macros/RadialMenu.kt
Normal file
149
src/main/kotlin/features/macros/RadialMenu.kt
Normal file
@@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,24 +6,45 @@ import net.minecraft.client.option.KeyBinding
|
|||||||
|
|
||||||
interface IKeyBinding {
|
interface IKeyBinding {
|
||||||
fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean
|
fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean
|
||||||
|
fun matchesAtLeast(keyCode: Int, scanCode: Int, modifiers: Int): Boolean
|
||||||
|
|
||||||
fun withModifiers(wantedModifiers: Int): IKeyBinding {
|
fun withModifiers(wantedModifiers: Int): IKeyBinding {
|
||||||
val old = this
|
val old = this
|
||||||
return object : IKeyBinding {
|
return object : IKeyBinding {
|
||||||
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
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 {
|
companion object {
|
||||||
fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding {
|
fun minecraft(keyBinding: KeyBinding) = object : IKeyBinding {
|
||||||
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) =
|
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int) =
|
||||||
keyBinding.matchesKey(keyCode, scanCode)
|
keyBinding.matchesKey(keyCode, scanCode)
|
||||||
}
|
|
||||||
|
override fun matchesAtLeast(
|
||||||
|
keyCode: Int,
|
||||||
|
scanCode: Int,
|
||||||
|
modifiers: Int
|
||||||
|
): Boolean =
|
||||||
|
keyBinding.matchesKey(keyCode, scanCode)
|
||||||
|
}
|
||||||
|
|
||||||
fun ofKeyCode(wantedKeyCode: Int) = object : IKeyBinding {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import net.minecraft.client.util.InputUtil
|
|||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
|
|
||||||
|
// TODO: add support for mouse keybindings
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SavedKeyBinding(
|
data class SavedKeyBinding(
|
||||||
val keyCode: Int,
|
val keyCode: Int,
|
||||||
@@ -86,6 +87,12 @@ data class SavedKeyBinding(
|
|||||||
(shift == this.shift)
|
(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 {
|
override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||||
if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false
|
if (this.keyCode == GLFW.GLFW_KEY_UNKNOWN) return false
|
||||||
return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt)
|
return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt)
|
||||||
|
|||||||
40
src/main/kotlin/util/collections/RangeUtil.kt
Normal file
40
src/main/kotlin/util/collections/RangeUtil.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/kotlin/util/math/Projections.kt
Normal file
46
src/main/kotlin/util/math/Projections.kt
Normal file
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package util.render
|
package util.render
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.BlendFunction
|
||||||
import com.mojang.blaze3d.pipeline.RenderPipeline
|
import com.mojang.blaze3d.pipeline.RenderPipeline
|
||||||
import com.mojang.blaze3d.platform.DepthTestFunction
|
import com.mojang.blaze3d.platform.DepthTestFunction
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat.DrawMode
|
import com.mojang.blaze3d.vertex.VertexFormat.DrawMode
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
import net.minecraft.client.gl.RenderPipelines
|
import net.minecraft.client.gl.RenderPipelines
|
||||||
|
import net.minecraft.client.gl.UniformType
|
||||||
import net.minecraft.client.render.RenderLayer
|
import net.minecraft.client.render.RenderLayer
|
||||||
import net.minecraft.client.render.RenderPhase
|
import net.minecraft.client.render.RenderPhase
|
||||||
import net.minecraft.client.render.VertexFormats
|
import net.minecraft.client.render.VertexFormats
|
||||||
@@ -38,23 +40,33 @@ object CustomRenderPipelines {
|
|||||||
.withCull(false)
|
.withCull(false)
|
||||||
.withDepthWrite(false)
|
.withDepthWrite(false)
|
||||||
.build()
|
.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 {
|
object CustomRenderLayers {
|
||||||
|
|
||||||
|
|
||||||
inline fun memoizeTextured(crossinline func: (Identifier) -> RenderLayer) = memoize(func)
|
inline fun memoizeTextured(crossinline func: (Identifier) -> RenderLayer) = memoize(func)
|
||||||
inline fun <T, R> memoize(crossinline func: (T) -> R): Function<T, R> {
|
inline fun <T, R> memoize(crossinline func: (T) -> R): Function<T, R> {
|
||||||
return Util.memoize { it: T -> func(it) }
|
return Util.memoize { it: T -> func(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val GUI_TEXTURED_NO_DEPTH_TRIS = memoizeTextured { texture ->
|
val GUI_TEXTURED_NO_DEPTH_TRIS = memoizeTextured { texture ->
|
||||||
RenderLayer.of("firmament_gui_textured_overlay_tris",
|
RenderLayer.of(
|
||||||
RenderLayer.DEFAULT_BUFFER_SIZE,
|
"firmament_gui_textured_overlay_tris",
|
||||||
CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS,
|
RenderLayer.DEFAULT_BUFFER_SIZE,
|
||||||
RenderLayer.MultiPhaseParameters.builder().texture(
|
CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS,
|
||||||
RenderPhase.Texture(texture, TriState.DEFAULT, false))
|
RenderLayer.MultiPhaseParameters.builder().texture(
|
||||||
.build(false))
|
RenderPhase.Texture(texture, TriState.DEFAULT, false)
|
||||||
|
)
|
||||||
|
.build(false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val LINES = RenderLayer.of(
|
val LINES = RenderLayer.of(
|
||||||
"firmament_lines",
|
"firmament_lines",
|
||||||
@@ -71,4 +83,13 @@ object CustomRenderLayers {
|
|||||||
.lightmap(RenderPhase.DISABLE_LIGHTMAP)
|
.lightmap(RenderPhase.DISABLE_LIGHTMAP)
|
||||||
.build(false)
|
.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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package moe.nea.firmament.util.render
|
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.systems.RenderSystem
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat.DrawMode
|
|
||||||
import me.shedaniel.math.Color
|
import me.shedaniel.math.Color
|
||||||
import org.joml.Matrix4f
|
import org.joml.Matrix4f
|
||||||
import util.render.CustomRenderLayers
|
import util.render.CustomRenderLayers
|
||||||
import net.minecraft.client.gl.RenderPipelines
|
|
||||||
import net.minecraft.client.gui.DrawContext
|
import net.minecraft.client.gui.DrawContext
|
||||||
import net.minecraft.client.render.RenderLayer
|
import net.minecraft.client.render.RenderLayer
|
||||||
import net.minecraft.client.render.VertexFormats
|
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import moe.nea.firmament.Firmament
|
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
|
|
||||||
fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
|
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())
|
RenderSystem.lineWidth(MC.window.scaleFactor.toFloat())
|
||||||
draw { vertexConsumers ->
|
draw { vertexConsumers ->
|
||||||
val buf = vertexConsumers.getBuffer(CustomRenderLayers.LINES)
|
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)
|
.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)
|
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,36 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.util.render
|
package moe.nea.firmament.util.render
|
||||||
|
|
||||||
import me.shedaniel.math.Color
|
import me.shedaniel.math.Color
|
||||||
|
|
||||||
val pi = Math.PI
|
val π = Math.PI
|
||||||
val tau = Math.PI * 2
|
val τ = Math.PI * 2
|
||||||
fun lerpAngle(a: Float, b: Float, progress: Float): Float {
|
fun lerpAngle(a: Float, b: Float, progress: Float): Float {
|
||||||
// TODO: there is at least 10 mods to many in here lol
|
// 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
|
val shortestAngle = ((((b.mod(τ) - a.mod(τ)).mod(τ)) + τ + π).mod(τ)) - π
|
||||||
return ((a + (shortestAngle) * progress).mod(tau)).toFloat()
|
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 {
|
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 {
|
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 {
|
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 {
|
fun lerp(a: Color, b: Color, progress: Float): Color {
|
||||||
return Color.ofRGBA(
|
return Color.ofRGBA(
|
||||||
lerp(a.red, b.red, progress),
|
lerp(a.red, b.red, progress),
|
||||||
lerp(a.green, b.green, progress),
|
lerp(a.green, b.green, progress),
|
||||||
lerp(a.blue, b.blue, progress),
|
lerp(a.blue, b.blue, progress),
|
||||||
lerp(a.alpha, b.alpha, progress),
|
lerp(a.alpha, b.alpha, progress),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,87 @@
|
|||||||
package moe.nea.firmament.util.render
|
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 io.github.notenoughupdates.moulconfig.platform.next
|
||||||
|
import java.util.OptionalInt
|
||||||
import org.joml.Matrix4f
|
import org.joml.Matrix4f
|
||||||
import org.joml.Vector2f
|
|
||||||
import util.render.CustomRenderLayers
|
import util.render.CustomRenderLayers
|
||||||
import kotlin.math.atan2
|
|
||||||
import kotlin.math.tan
|
|
||||||
import net.minecraft.client.gui.DrawContext
|
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 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 {
|
object RenderCircleProgress {
|
||||||
|
|
||||||
|
fun renderCircularSlice(
|
||||||
|
drawContext: DrawContext,
|
||||||
|
layer: RenderLayer,
|
||||||
|
u1: Float,
|
||||||
|
u2: Float,
|
||||||
|
v1: Float,
|
||||||
|
v2: Float,
|
||||||
|
angleRadians: ClosedFloatingPointRange<Float>,
|
||||||
|
color: Int = -1,
|
||||||
|
innerCutoutRadius: Float = 0F
|
||||||
|
) {
|
||||||
|
drawContext.draw()
|
||||||
|
val sections = angleRadians.nonNegligibleSubSectionsAlignedWith((τ / 8f).toFloat())
|
||||||
|
.zipWithNext().toList()
|
||||||
|
BufferAllocator(layer.vertexFormat.vertexSize * sections.size * 3).use { allocator ->
|
||||||
|
|
||||||
|
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, secondPoint.x, secondPoint.y, 0F)
|
||||||
|
.texture(lerp(u1, u2, ilerp(secondPoint.x)), lerp(v1, v2, ilerp(secondPoint.y)))
|
||||||
|
.color(color)
|
||||||
|
.next()
|
||||||
|
bufferBuilder
|
||||||
|
.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(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(
|
fun renderCircle(
|
||||||
drawContext: DrawContext,
|
drawContext: DrawContext,
|
||||||
texture: Identifier,
|
texture: Identifier,
|
||||||
@@ -20,66 +91,11 @@ object RenderCircleProgress {
|
|||||||
v1: Float,
|
v1: Float,
|
||||||
v2: Float,
|
v2: Float,
|
||||||
) {
|
) {
|
||||||
drawContext.draw {
|
renderCircularSlice(
|
||||||
val bufferBuilder = it.getBuffer(CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture))
|
drawContext,
|
||||||
val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
|
CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture),
|
||||||
|
u1, u2, v1, v2,
|
||||||
val corners = listOf(
|
(-τ / 4).toFloat()..(progress * τ - τ / 4).toFloat()
|
||||||
Vector2f(0F, -1F),
|
)
|
||||||
Vector2f(1F, -1F),
|
|
||||||
Vector2f(1F, 0F),
|
|
||||||
Vector2f(1F, 1F),
|
|
||||||
Vector2f(0F, 1F),
|
|
||||||
Vector2f(-1F, 1F),
|
|
||||||
Vector2f(-1F, 0F),
|
|
||||||
Vector2f(-1F, -1F),
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
.next()
|
|
||||||
bufferBuilder
|
|
||||||
.vertex(matrix, first.x, first.y, 0F)
|
|
||||||
.texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
|
|
||||||
.color(-1)
|
|
||||||
.next()
|
|
||||||
bufferBuilder
|
|
||||||
.vertex(matrix, 0F, 0F, 0F)
|
|
||||||
.texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
|
|
||||||
.color(-1)
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
>
|
>
|
||||||
<Panel background="TRANSPARENT" insets="10">
|
<Panel background="TRANSPARENT" insets="10">
|
||||||
<Column>
|
<Column>
|
||||||
<Meta beforeClose="@beforeClose"/>
|
|
||||||
<ScrollPanel width="380" height="300">
|
<ScrollPanel width="380" height="300">
|
||||||
<Align horizontal="CENTER">
|
<Align horizontal="CENTER">
|
||||||
<Array data="@actions">
|
<Array data="@actions">
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig"
|
||||||
|
>
|
||||||
|
<Center>
|
||||||
|
<Panel background="VANILLA" insets="10">
|
||||||
|
<Column>
|
||||||
|
<Row>
|
||||||
|
<firm:Button onClick="@back">
|
||||||
|
<Text text="←"/>
|
||||||
|
</firm:Button>
|
||||||
|
<Text text="Editing wheel macro"/>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Text text="Key (Hold):"/>
|
||||||
|
<Align horizontal="RIGHT">
|
||||||
|
<firm:Fixed width="160">
|
||||||
|
<Indirect value="@button"/>
|
||||||
|
</firm:Fixed>
|
||||||
|
</Align>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Text text="Menu Options:"/>
|
||||||
|
<Align horizontal="RIGHT">
|
||||||
|
<firm:Button onClick="@addOption">
|
||||||
|
<Text text="+"/>
|
||||||
|
</firm:Button>
|
||||||
|
</Align>
|
||||||
|
</Row>
|
||||||
|
<Array data="@editableCommands">
|
||||||
|
<Row>
|
||||||
|
<Text text="/"/>
|
||||||
|
<TextField value="@text" width="160"/>
|
||||||
|
<Align horizontal="RIGHT">
|
||||||
|
<firm:Button onClick="@delete">
|
||||||
|
<Text text="Delete"/>
|
||||||
|
</firm:Button>
|
||||||
|
</Align>
|
||||||
|
</Row>
|
||||||
|
</Array>
|
||||||
|
</Column>
|
||||||
|
</Panel>
|
||||||
|
</Center>
|
||||||
|
</Root>
|
||||||
@@ -1,16 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<Root xmlns="http://notenoughupdates.org/moulconfig"
|
<Root xmlns="http://notenoughupdates.org/moulconfig"
|
||||||
xmlns:firm="http://firmament.nea.moe/moulconfig">
|
>
|
||||||
<Center>
|
<Center>
|
||||||
<Tabs>
|
<Row>
|
||||||
<Tab>
|
<Tabs>
|
||||||
<Tab.Header>
|
<Tab>
|
||||||
<Text text="Combo Macros"/>
|
<Tab.Header>
|
||||||
</Tab.Header>
|
<Text text="Combo Macros"/>
|
||||||
<Tab.Body>
|
</Tab.Header>
|
||||||
<Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/>
|
<Tab.Body>
|
||||||
</Tab.Body>
|
<Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/>
|
||||||
</Tab>
|
</Tab.Body>
|
||||||
</Tabs>
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
<Tab.Header>
|
||||||
|
<Text text="Macro Wheel"/>
|
||||||
|
</Tab.Header>
|
||||||
|
<Tab.Body>
|
||||||
|
<Fragment value="firmament:gui/config/macros/wheel.xml" bind="@wheels"/>
|
||||||
|
</Tab.Body>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
<Meta beforeClose="@beforeClose"/>
|
||||||
|
</Row>
|
||||||
</Center>
|
</Center>
|
||||||
</Root>
|
</Root>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:firm="http://firmament.nea.moe/moulconfig"
|
||||||
|
>
|
||||||
|
<Panel background="TRANSPARENT" insets="10">
|
||||||
|
<Column>
|
||||||
|
<ScrollPanel width="380" height="300">
|
||||||
|
<Align horizontal="CENTER">
|
||||||
|
<Array data="@wheels">
|
||||||
|
<Panel background="TRANSPARENT" insets="3">
|
||||||
|
<Panel background="VANILLA" insets="6">
|
||||||
|
<Column>
|
||||||
|
<Row>
|
||||||
|
<Text text="@keyCombo" width="250"/>
|
||||||
|
<Align horizontal="RIGHT">
|
||||||
|
<Row>
|
||||||
|
<firm:Button onClick="@edit">
|
||||||
|
<Text text="Edit"/>
|
||||||
|
</firm:Button>
|
||||||
|
<Spacer width="12"/>
|
||||||
|
<firm:Button onClick="@delete">
|
||||||
|
<Text text="Delete"/>
|
||||||
|
</firm:Button>
|
||||||
|
</Row>
|
||||||
|
</Align>
|
||||||
|
</Row>
|
||||||
|
<Array data="@commands">
|
||||||
|
<Text text="@text" width="280"/>
|
||||||
|
</Array>
|
||||||
|
</Column>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
</Panel>
|
||||||
|
</Array>
|
||||||
|
</Align>
|
||||||
|
</ScrollPanel>
|
||||||
|
<Align horizontal="RIGHT">
|
||||||
|
<Row>
|
||||||
|
<firm:Button onClick="@discard">
|
||||||
|
<Text text="Discard Changes"/>
|
||||||
|
</firm:Button>
|
||||||
|
<firm:Button onClick="@saveAndClose">
|
||||||
|
<Text text="Save & Close"/>
|
||||||
|
</firm:Button>
|
||||||
|
<firm:Button onClick="@save">
|
||||||
|
<Text text="Save"/>
|
||||||
|
</firm:Button>
|
||||||
|
<firm:Button onClick="@addWheel">
|
||||||
|
<Text text="Add Wheel"/>
|
||||||
|
</firm:Button>
|
||||||
|
</Row>
|
||||||
|
</Align>
|
||||||
|
</Column>
|
||||||
|
</Panel>
|
||||||
|
</Root>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#version 150
|
||||||
|
|
||||||
|
in vec4 vertexColor;
|
||||||
|
in vec2 texCoord0;
|
||||||
|
|
||||||
|
uniform vec4 ColorModulator;
|
||||||
|
uniform float InnerCutoutRadius;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color = vertexColor;
|
||||||
|
if (color.a == 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
float d = length(texCoord0 - vec2(0.5));
|
||||||
|
if (d > 0.5 || d < InnerCutoutRadius)
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
fragColor = color * ColorModulator;
|
||||||
|
}
|
||||||
28
src/test/kotlin/util/math/ProjectionsBoxTest.kt
Normal file
28
src/test/kotlin/util/math/ProjectionsBoxTest.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package moe.nea.firmament.test.util.math
|
||||||
|
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.DynamicTest
|
||||||
|
import org.junit.jupiter.api.TestFactory
|
||||||
|
import kotlin.streams.asStream
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
import moe.nea.firmament.util.math.Projections
|
||||||
|
|
||||||
|
class ProjectionsBoxTest {
|
||||||
|
val Double.degrees get() = Math.toRadians(this)
|
||||||
|
|
||||||
|
@TestFactory
|
||||||
|
fun testProjections(): Stream<DynamicTest> {
|
||||||
|
return sequenceOf(
|
||||||
|
0.0.degrees to Vec2f(1F, 0F),
|
||||||
|
63.4349.degrees to Vec2f(0.5F, 1F),
|
||||||
|
).map { (angle, expected) ->
|
||||||
|
DynamicTest.dynamicTest("ProjectionsBoxTest::projectAngleOntoUnitBox(${angle})") {
|
||||||
|
val actual = Projections.Two.projectAngleOntoUnitBox(angle)
|
||||||
|
fun msg() = "Expected (${expected.x}, ${expected.y}) got (${actual.x}, ${actual.y})"
|
||||||
|
Assertions.assertEquals(expected.x, actual.x, 0.0001F, ::msg)
|
||||||
|
Assertions.assertEquals(expected.y, actual.y, 0.0001F, ::msg)
|
||||||
|
}
|
||||||
|
}.asStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user