feat: Add macro wheels

This commit is contained in:
Linnea Gräf
2025-06-17 20:59:45 +02:00
parent 775933d516
commit 4b9e966ca7
24 changed files with 753 additions and 147 deletions

View 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
}
}
}
}
}

View 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())
}
}
}

View File

@@ -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)
)
}

View File

@@ -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)
}
}

View File

@@ -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),
)
}

View File

@@ -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()
)
}
}