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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
data class ComboKeyAction(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,87 @@
|
||||
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 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(
|
||||
drawContext: DrawContext,
|
||||
texture: Identifier,
|
||||
@@ -20,66 +91,11 @@ object RenderCircleProgress {
|
||||
v1: Float,
|
||||
v2: Float,
|
||||
) {
|
||||
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),
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
renderCircularSlice(
|
||||
drawContext,
|
||||
CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture),
|
||||
u1, u2, v1, v2,
|
||||
(-τ / 4).toFloat()..(progress * τ - τ / 4).toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
>
|
||||
<Panel background="TRANSPARENT" insets="10">
|
||||
<Column>
|
||||
<Meta beforeClose="@beforeClose"/>
|
||||
<ScrollPanel width="380" height="300">
|
||||
<Align horizontal="CENTER">
|
||||
<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" ?>
|
||||
<Root xmlns="http://notenoughupdates.org/moulconfig"
|
||||
xmlns:firm="http://firmament.nea.moe/moulconfig">
|
||||
>
|
||||
<Center>
|
||||
<Tabs>
|
||||
<Tab>
|
||||
<Tab.Header>
|
||||
<Text text="Combo Macros"/>
|
||||
</Tab.Header>
|
||||
<Tab.Body>
|
||||
<Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/>
|
||||
</Tab.Body>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Row>
|
||||
<Tabs>
|
||||
<Tab>
|
||||
<Tab.Header>
|
||||
<Text text="Combo Macros"/>
|
||||
</Tab.Header>
|
||||
<Tab.Body>
|
||||
<Fragment value="firmament:gui/config/macros/combos.xml" bind="@combos"/>
|
||||
</Tab.Body>
|
||||
</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>
|
||||
</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