From 41f68813d81bb7838940dda61329c89c3b0051cc Mon Sep 17 00:00:00 2001 From: Alwinfy <20421383+Alwinfy@users.noreply.github.com> Date: Tue, 28 Dec 2021 12:44:56 -0500 Subject: [PATCH] Implement a vertex-based lightning effect on nodes TODO: write a shader to make it look fancier --- gradlew | 0 .../petrak/hex/client/gui/GuiSpellcasting.kt | 85 ++++++++++++++++--- 2 files changed, 73 insertions(+), 12 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt b/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt index f3343817..cea7ab17 100644 --- a/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt +++ b/src/main/java/at/petrak/hex/client/gui/GuiSpellcasting.kt @@ -23,6 +23,8 @@ import net.minecraft.network.chat.TextComponent import net.minecraft.util.Mth import net.minecraft.world.InteractionHand import net.minecraft.world.phys.Vec2 +import net.minecraft.world.level.levelgen.XoroshiroRandomSource +import net.minecraft.world.level.levelgen.synth.PerlinNoise import kotlin.math.atan2 import kotlin.math.roundToInt @@ -32,6 +34,10 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text private var patterns: MutableList> = mutableListOf() private var drawState: PatternDrawState = PatternDrawState.BetweenPatterns + companion object { + val NOISE = PerlinNoise.create(XoroshiroRandomSource(9001L), listOf(0, 1, 2, 3, 4)) + } + override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean { if (super.mouseClicked(pMouseX, pMouseY, pButton)) { return true @@ -137,11 +143,60 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text override fun render(poseStack: PoseStack, pMouseX: Int, pMouseY: Int, pPartialTick: Float) { super.render(poseStack, pMouseX, pMouseY, pPartialTick) - fun drawLineSeq(mat: Matrix4f, points: List, r: Int, g: Int, b: Int, a: Int) { + // Split up a sequence of lines with a lightning effect + // hops: rough number of points to subdivide each segment into + // speed: rate at which the lightning effect should move/shake/etc + fun makeZappy(points: List, hops: Float, variance: Float, speed: Float): List { + // Nothing in, nothing out + if (points.isEmpty()) { + return emptyList() + } + val zSeed = (Minecraft.getInstance().getFrameTime().toDouble() + Minecraft.getInstance().level!!.getLevelData().getGameTime()) * speed + // Create our output list of zap points + val zappyPts = mutableListOf(points[0]) + // For each segment in the original... + for ((i, pair) in points.zipWithNext().withIndex()) { + val (src, target) = pair + // Compute distance-squared to the destination, then scale it down by # of hops + // to know how long each individual hop should be (squared) + val hopDistSqr = src.distanceToSqr(target) / (hops * hops) + // Then take the square root to find the actual hop distance + val hopDist = Mth.sqrt(hopDistSqr) + // Compute how big the radius of variance should be + val maxVariance = hopDist * variance + + var position = src + var j = 0 + while (position.distanceToSqr(target) > hopDistSqr) { + // Add the next hop... + val hop = target.add(position.negated()).normalized().scale(hopDist) + // as well as some random variance... + // (We use i, j (segment #, subsegment #) as seeds for the Perlin noise, + // and zSeed (i.e. time elapsed) to perturb the shape gradually over time) + val theta = (3 * NOISE.getValue(i.toDouble(), j.toDouble(), zSeed) * TAU).toFloat() + val r = NOISE.getValue(i.inv().toDouble(), j.toDouble(), zSeed).toFloat() * maxVariance + val randomHop = Vec2(r * Mth.cos(theta), r * Mth.sin(theta)) + position = position.add(hop).add(randomHop) + // Then record the new location. + zappyPts.add(position) + j += 1 + } + // Finally, we hit the destination, add that too + zappyPts.add(target) + } + return zappyPts + } + + fun drawLineSeq(mat: Matrix4f, points: List, width: Float, z: Float, r: Int, g: Int, b: Int, a: Int) { // they spell it wrong at mojang lmao val tess = Tesselator.getInstance() val buf = tess.builder + // We use one single TRIANGLE_STRIP + // in order to connect adjacent segments together and not get the weird hinge effect. + // There's still some artifacting but this is passable, at least. + buf.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR) + for ((p1, p2) in points.zipWithNext()) { // https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L163 // GuiComponent::innerFill line 52 @@ -151,20 +206,26 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text // normal x and y, presumably? val nx = -dy val ny = dx - val width = 1.5f // thickness? val tlen = Mth.sqrt(nx * nx + ny * ny) / (width * 0.5f) val tx = nx / tlen val ty = ny / tlen - buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) - buf.vertex(mat, p1.x + tx, p1.y + ty, 0f).color(r, g, b, a).endVertex() - buf.vertex(mat, p2.x + tx, p2.y + ty, 0f).color(r, g, b, a).endVertex() - buf.vertex(mat, p2.x - tx, p2.y - ty, 0f).color(r, g, b, a).endVertex() - buf.vertex(mat, p1.x - tx, p1.y - ty, 0f).color(r, g, b, a).endVertex() - - tess.end() + buf.vertex(mat, p1.x + tx, p1.y + ty, z).color(r, g, b, a).endVertex() + buf.vertex(mat, p1.x - tx, p1.y - ty, z).color(r, g, b, a).endVertex() + buf.vertex(mat, p2.x + tx, p2.y + ty, z).color(r, g, b, a).endVertex() + buf.vertex(mat, p2.x - tx, p2.y - ty, z).color(r, g, b, a).endVertex() } + + tess.end() + } + + fun drawPattern(mat: Matrix4f, points: List, r: Int, g: Int, b: Int, a: Int) { + fun screen(n: Int): Int { return (n + 255) / 2 } + + val zappyPts = makeZappy(points, 10f, 2.5f, 0.1f) + drawLineSeq(mat, zappyPts, 5f, 0f, r, g, b, a) + drawLineSeq(mat, zappyPts, 2f, 1f, screen(r), screen(g), screen(b), a) } fun drawSpot(mat: Matrix4f, point: Vec2, r: Int, g: Int, b: Int, a: Int) { @@ -197,7 +258,7 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text RenderSystem.disableCull() for ((pat, origin) in this.patterns) { - drawLineSeq(mat, pat.positions().map { pos -> this.coordToPx(pos, origin) }, 127, 127, 255, 200) + drawPattern(mat, pat.positions().map { pos -> this.coordToPx(pos, origin) }, 127, 127, 255, 200) } // Now draw the currently WIP pattern @@ -230,7 +291,7 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text } points.add(Vec2(pMouseX.toFloat(), pMouseY.toFloat())) - drawLineSeq(mat, points, 200, 200, 255, 255) + drawPattern(mat, points, 100, 200, 255, 255) for (dir in dirs) { val pos = this.coordToPx(dir.asDelta(), spotAnchor) @@ -268,4 +329,4 @@ class GuiSpellcasting(private val handOpenedWith: InteractionHand) : Screen(Text /** We've started drawing a pattern for real. */ data class Drawing(val start: Vec2, var current: Vec2, val wipPattern: HexPattern) : PatternDrawState() } -} \ No newline at end of file +}