diff --git a/TestMod/build.gradle b/TestMod/build.gradle index 5a08ca4..658bedb 100644 --- a/TestMod/build.gradle +++ b/TestMod/build.gradle @@ -5,6 +5,18 @@ configurations { libImpl } +repositories { + maven { + name = 'Ladysnake Mods' + url = 'https://maven.ladysnake.org/releases' + content { + includeGroup 'io.github.ladysnake' + includeGroup 'org.ladysnake' + includeGroupByRegex 'dev\\.onyxstudios.*' + } + } +} + dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" @@ -30,6 +42,7 @@ dependencies { exclude group: "commons-io", module: "commons-io" } api(group: "de.javagl", name: "obj", version: "0.4.0") + modImplementation "io.github.ladysnake:satin:1.13.0" // modImpl(files("rt-mods/render-explorer-1.0.0.jar")) configurations.modImpl.dependencies.each { diff --git a/TestMod/src/main/java/me/x150/testmod/Animation.java b/TestMod/src/main/java/me/x150/testmod/Animation.java new file mode 100644 index 0000000..709fa8d --- /dev/null +++ b/TestMod/src/main/java/me/x150/testmod/Animation.java @@ -0,0 +1,168 @@ +package me.x150.testmod; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; + +import java.time.Duration; + +public class Animation { + private float start; + private float end; + private long startTime; + private Duration dur; + private Easing easing = Easing.LINEAR; + private boolean forward = true; + public Animation(float start, float end, Duration duration) { + this.start = start; + this.end = end; + this.dur = duration; + this.startTime = System.nanoTime(); + } + + public Animation(float start, float end, Duration duration, Easing easing) { + this(start, end, duration); + this.easing = easing; + } + + public void setStart(float start) { + this.start = start; + } + + public void setEnd(float end) { + this.end = end; + } + + public void setDuration(Duration dur) { + this.dur = dur; + } + + public void reset() { + this.startTime = System.nanoTime(); + } + + public void setEasing(Easing easing) { + this.easing = easing; + } + + public boolean isForward() { + return forward; + } + + public void setForward(boolean forward) { + this.forward = forward; + } + + public float get() { + long currentTime = System.nanoTime(); + long delta = currentTime - startTime; + long nanoDuration = dur.toNanos(); + float animDelta = (float) delta / nanoDuration; + animDelta = Math.max(0, Math.min(1, animDelta)); + if (!forward) { + animDelta = 1 - animDelta; + } + animDelta = easing.apply(animDelta); + return start + (end - start) * animDelta; + } + + public boolean isDone() { + return System.nanoTime() - startTime >= dur.toNanos(); + } + + enum Easing { + LINEAR(x -> x), + SINE_IN(x -> 1 - Math.cos(x * Math.PI / 2)), + SINE_OUT(x -> Math.sin(x * Math.PI / 2)), + SINE_IN_OUT(x -> -(Math.cos(Math.PI * x) - 1) / 2), + + CUBIC_IN(x -> Math.pow(x, 3)), + CUBIC_OUT(x -> 1 - Math.pow(1 - x, 3)), + CUBIC_IN_OUT(x -> x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2), + + QUINT_IN(x -> Math.pow(x, 5)), + QUINT_OUT(x -> 1 - Math.pow(1 - x, 5)), + QUINT_IN_OUT(x -> x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2), + + CIRC_IN(x -> 1 - Math.sqrt(1 - Math.pow(x, 2))), + CIRC_OUT(x -> Math.sqrt(1 - Math.pow(x - 1, 2))), + CIRC_IN_OUT(x -> x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt( + 1 - Math.pow(-2 * x + 2, 2)) + 1) / 2), + + ELASTIC_IN(x -> { + double c4 = 2 * Math.PI / 3; + + return x == 0 ? 0 : x == 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); + }), + ELASTIC_OUT(x -> { + double c4 = 2 * Math.PI / 3; + + return x == 0 ? 0 : x == 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; + }), + ELASTIC_IN_OUT(x -> { + double c5 = 2 * Math.PI / 4.5; + + double sin = Math.sin((20 * x - 11.125) * c5); + return x == 0 ? 0 : x == 1 ? 1 : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * sin) / 2 : Math.pow(2, + -20 * x + 10) * sin / 2 + 1; + }), + + QUAD_IN(x -> x * x), + QUAD_OUT(x -> 1 - (1 - x) * (1 - x)), + QUAD_IN_OUT(x -> x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2), + + QUART_IN(x -> x * x * x * x), + QUART_OUT(x -> 1 - Math.pow(1 - x, 4)), + QUART_IN_OUT(x -> x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2), + + EXPO_IN(x -> x == 0 ? 0 : Math.pow(2, 10 * x - 10)), + EXPO_OUT(x -> x == 1 ? 1 : 1 - Math.pow(2, -10 * x)), + EXPO_IN_OUT(x -> x == 0 ? 0 : x == 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, + -20 * x + 10)) / 2), + + BACK_IN(x -> { + double c1 = 1.70158; + double c3 = c1 + 1; + + return c3 * x * x * x - c1 * x * x; + }), + BACK_OUT(x -> { + double c1 = 1.70158; + double c3 = c1 + 1; + + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }), + BACK_IN_OUT(x -> { + double c1 = 1.70158; + double c2 = c1 * 1.525; + + return x < 0.5 ? Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2) / 2 : (Math.pow(2 * x - 2, + 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + }), + + BOUNCE_OUT(x -> { + double n1 = 7.5625; + double d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + }), + BOUNCE_IN(x -> 1 - Easing.BOUNCE_OUT.apply(x)), + BOUNCE_IN_OUT(x -> x < 0.5 ? (1 - BOUNCE_OUT.apply(1 - 2 * x)) / 2 : (1 + BOUNCE_OUT.apply(2 * x - 1)) / 2); + + final Double2DoubleFunction floatFunction; + + Easing(Double2DoubleFunction function) { + floatFunction = function; + } + + public float apply(double f) { + return (float) floatFunction.get(f); + } + } +} \ No newline at end of file diff --git a/TestMod/src/main/java/me/x150/testmod/Handler.java b/TestMod/src/main/java/me/x150/testmod/Handler.java index 27da475..9ff06e3 100644 --- a/TestMod/src/main/java/me/x150/testmod/Handler.java +++ b/TestMod/src/main/java/me/x150/testmod/Handler.java @@ -3,47 +3,59 @@ import lombok.SneakyThrows; import me.x150.renderer.font.FontRenderer; import me.x150.renderer.objfile.ObjFile; -import me.x150.renderer.render.MSAAFramebuffer; import me.x150.renderer.render.OutlineFramebuffer; import me.x150.renderer.render.Renderer2d; import me.x150.renderer.render.Renderer3d; +import me.x150.renderer.util.RendererUtils; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.Identifier; +import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; import org.joml.Matrix4f; -import org.lwjgl.opengl.GL30; -import java.awt.Color; -import java.awt.Font; +import java.awt.*; import java.nio.file.Path; public class Handler { + static FontRenderer fr; + static FontRenderer fr1; + private static ObjFile ob; - static FontRenderer fr; - static FontRenderer fr1; - private static ObjFile ob; + @SneakyThrows + public static void world(MatrixStack stack) { + if (ob == null) { + ob = new ObjFile("untitled.obj", + ObjFile.ResourceProvider.ofPath(Path.of("/media/x150/stuff/Dev/Java/RenderLib2/run/"))); + } + ob.draw(stack, new Matrix4f(), new Vec3d(0, 400, 0)); + OutlineFramebuffer.useAndDraw(() -> Renderer3d.renderFilled(stack, Color.WHITE, new Vec3d(0, 300, 0), new Vec3d(5, 5, 5)), 1f, Color.GREEN, Color.BLACK); + } - @SneakyThrows - public static void world(MatrixStack stack) { - if (ob == null) { - ob = new ObjFile("untitled.obj", ObjFile.ResourceProvider.ofPath(Path.of("/media/x150/stuff/Dev/Java/RenderLib2/run/"))); - } - OutlineFramebuffer.useAndDraw(() -> { - ob.draw(stack, new Matrix4f(), new Vec3d(0, 400, 0)); - }, 1f, Color.RED, Color.BLUE); - OutlineFramebuffer.useAndDraw(() -> { - Renderer3d.renderFilled(stack, Color.WHITE, new Vec3d(0, 300, 0), new Vec3d(5, 5, 5)); - }, 1f, Color.GREEN, Color.BLACK); - } - public static void hud(MatrixStack stack) { - if (fr == null) { - fr = new FontRenderer(new Font[] { - new Font("Ubuntu", Font.PLAIN, 8) - }, 9f); - fr1 = new FontRenderer(new Font[] { - new Font("Ubuntu", Font.BOLD, 8) - }, 9f*3); - } - } + public static void hud(DrawContext matrices) { + if (fr == null) { + fr = new FontRenderer(new Font[]{ + new Font("Ubuntu", Font.PLAIN, 8) + }, 9f); + fr1 = new FontRenderer(new Font[]{ + new Font("Ubuntu", Font.BOLD, 8) + }, 9f * 3); + } + MatrixStack fs = RendererUtils.getEmptyMatrixStack(); + fs.push(); + String n = """ + This is a rendering library. + It supports TTF font rendering. + I can type äöü, it will render it. + It also supports newlines. + """.trim(); + float stringWidth = fr.getStringWidth(n); + float stringHeight = fr.getStringHeight(n); + fs.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees((System.currentTimeMillis() % 5000) / 5000f * 360f), + 30 + (stringWidth + 5) / 2, 30 + (stringHeight + 5) / 2, 0); + Renderer2d.renderRoundedQuad(fs, Color.BLACK, 30 - 5, 30 - 5, 30 + stringWidth + 5, 30 + stringHeight + 5, 5, + 5); + fr.drawString(fs, n, 30, 30, 1f, 1f, 1f, 1f); + fs.pop(); + } } diff --git a/TestMod/src/main/java/me/x150/testmod/TestMod.java b/TestMod/src/main/java/me/x150/testmod/TestMod.java index 99d2e85..7d10a9d 100644 --- a/TestMod/src/main/java/me/x150/testmod/TestMod.java +++ b/TestMod/src/main/java/me/x150/testmod/TestMod.java @@ -3,11 +3,11 @@ import net.fabricmc.api.ModInitializer; public class TestMod implements ModInitializer { - /** - * Runs the mod initializer. - */ - @Override - public void onInitialize() { + /** + * Runs the mod initializer. + */ + @Override + public void onInitialize() { - } + } } diff --git a/TestMod/src/main/java/me/x150/testmod/client/TestModClient.java b/TestMod/src/main/java/me/x150/testmod/client/TestModClient.java index ad598ab..805d11c 100644 --- a/TestMod/src/main/java/me/x150/testmod/client/TestModClient.java +++ b/TestMod/src/main/java/me/x150/testmod/client/TestModClient.java @@ -6,12 +6,12 @@ import net.fabricmc.api.ClientModInitializer; public class TestModClient implements ClientModInitializer { - /** - * Runs the mod initializer on the client environment. - */ - @Override - public void onInitializeClient() { - RenderEvents.HUD.register(Handler::hud); - RenderEvents.WORLD.register(Handler::world); - } + /** + * Runs the mod initializer on the client environment. + */ + @Override + public void onInitializeClient() { + RenderEvents.HUD.register(Handler::hud); + RenderEvents.WORLD.register(Handler::world); + } } diff --git a/TestMod/src/main/java/me/x150/testmod/mixin/ChatScreenMixin.java b/TestMod/src/main/java/me/x150/testmod/mixin/ChatScreenMixin.java deleted file mode 100644 index cb2cf77..0000000 --- a/TestMod/src/main/java/me/x150/testmod/mixin/ChatScreenMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.x150.testmod.mixin; - -import net.minecraft.client.gui.screen.ChatScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(ChatScreen.class) -public class ChatScreenMixin extends Screen { - protected ChatScreenMixin(Text title) { - super(title); - } - - @Inject(method = "init", at = @At("RETURN")) - void postInit(CallbackInfo ci) { - // ButtonWidget lidar = ButtonWidget.builder(Text.of("lidar"), button -> { - // Handler.lidar(); - // }).dimensions(5, 5, 100, 20).build(); - // this.addDrawableChild(lidar); - } -} diff --git a/TestMod/src/main/resources/TestMod.mixins.json b/TestMod/src/main/resources/TestMod.mixins.json index c43bacb..aa21a49 100644 --- a/TestMod/src/main/resources/TestMod.mixins.json +++ b/TestMod/src/main/resources/TestMod.mixins.json @@ -6,7 +6,7 @@ "mixins": [ ], "client": [ - "ChatScreenMixin" + ], "injectors": { "defaultRequire": 1 diff --git a/build.gradle b/build.gradle index 6be4703..d3e5a5f 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,15 @@ repositories { // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. + maven { + name = 'Ladysnake Mods' + url = 'https://maven.ladysnake.org/releases' + content { + includeGroup 'io.github.ladysnake' + includeGroup 'org.ladysnake' + includeGroupByRegex 'dev\\.onyxstudios.*' + } + } } configurations { @@ -97,6 +106,8 @@ dependencies { } api(group: "de.javagl", name: "obj", version: "0.4.0") + modImplementation "io.github.ladysnake:satin:1.13.0" + configurations.modImpl.dependencies.each { modImplementation(it) } diff --git a/src/main/java/me/x150/renderer/Renderer.java b/src/main/java/me/x150/renderer/Renderer.java index 8ae71cd..ae70d36 100644 --- a/src/main/java/me/x150/renderer/Renderer.java +++ b/src/main/java/me/x150/renderer/Renderer.java @@ -3,8 +3,8 @@ import net.fabricmc.api.ModInitializer; public class Renderer implements ModInitializer { - @Override - public void onInitialize() { + @Override + public void onInitialize() { - } + } } diff --git a/src/main/java/me/x150/renderer/client/RendererMain.java b/src/main/java/me/x150/renderer/client/RendererMain.java index da02806..af41fc1 100644 --- a/src/main/java/me/x150/renderer/client/RendererMain.java +++ b/src/main/java/me/x150/renderer/client/RendererMain.java @@ -8,9 +8,9 @@ @Environment(EnvType.CLIENT) public class RendererMain implements ClientModInitializer { - public static final Logger LOGGER = LoggerFactory.getLogger("Renderer"); + public static final Logger LOGGER = LoggerFactory.getLogger("Renderer"); - @Override - public void onInitializeClient() { - } + @Override + public void onInitializeClient() { + } } diff --git a/src/main/java/me/x150/renderer/event/RenderEvents.java b/src/main/java/me/x150/renderer/event/RenderEvents.java index b4593f1..c7fab4d 100644 --- a/src/main/java/me/x150/renderer/event/RenderEvents.java +++ b/src/main/java/me/x150/renderer/event/RenderEvents.java @@ -2,22 +2,23 @@ import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.util.math.MatrixStack; public class RenderEvents { - public static final Event WORLD = create(); - public static final Event HUD = create(); + public static final Event> WORLD = create(); + public static final Event> HUD = create(); - private static Event create() { - return EventFactory.createArrayBacked(RenderEvent.class, listeners -> matrixStack -> { - for (RenderEvent listener : listeners) { - listener.rendered(matrixStack); - } - }); - } + private static Event> create() { + return EventFactory.createArrayBacked(RenderEvent.class, listeners -> element -> { + for (RenderEvent listener : listeners) { + listener.rendered(element); + } + }); + } - @FunctionalInterface - public interface RenderEvent { - void rendered(MatrixStack matrixStack); - } + @FunctionalInterface + public interface RenderEvent { + void rendered(T matrixStack); + } } diff --git a/src/main/java/me/x150/renderer/font/FontRenderer.java b/src/main/java/me/x150/renderer/font/FontRenderer.java index d00abd4..870a30a 100644 --- a/src/main/java/me/x150/renderer/font/FontRenderer.java +++ b/src/main/java/me/x150/renderer/font/FontRenderer.java @@ -20,7 +20,7 @@ import org.joml.Matrix4f; import org.lwjgl.opengl.GL11; -import java.awt.Font; +import java.awt.*; import java.io.Closeable; import java.util.List; @@ -31,290 +31,287 @@ * if they have a glyph corresponding to the one being drawn. If none of them have it, the missing "square" is drawn. */ public class FontRenderer implements Closeable { - private static final Char2IntArrayMap colorCodes = new Char2IntArrayMap() {{ - put('0', 0x000000); - put('1', 0x0000AA); - put('2', 0x00AA00); - put('3', 0x00AAAA); - put('4', 0xAA0000); - put('5', 0xAA00AA); - put('6', 0xFFAA00); - put('7', 0xAAAAAA); - put('8', 0x555555); - put('9', 0x5555FF); - put('A', 0x55FF55); - put('B', 0x55FFFF); - put('C', 0xFF5555); - put('D', 0xFF55FF); - put('E', 0xFFFF55); - put('F', 0xFFFFFF); - }}; - private static final int BLOCK_SIZE = 256; - private static final Object2ObjectArrayMap> GLYPH_PAGE_CACHE = new Object2ObjectArrayMap<>(); - private final float originalSize; - private final ObjectList maps = new ObjectArrayList<>(); - private final Char2ObjectArrayMap allGlyphs = new Char2ObjectArrayMap<>(); - private int scaleMul = 0; - private Font[] fonts; - private int previousGameScale = -1; + private static final Char2IntArrayMap colorCodes = new Char2IntArrayMap() {{ + put('0', 0x000000); + put('1', 0x0000AA); + put('2', 0x00AA00); + put('3', 0x00AAAA); + put('4', 0xAA0000); + put('5', 0xAA00AA); + put('6', 0xFFAA00); + put('7', 0xAAAAAA); + put('8', 0x555555); + put('9', 0x5555FF); + put('A', 0x55FF55); + put('B', 0x55FFFF); + put('C', 0xFF5555); + put('D', 0xFF55FF); + put('E', 0xFFFF55); + put('F', 0xFFFFFF); + }}; + private static final int BLOCK_SIZE = 256; + private static final Object2ObjectArrayMap> GLYPH_PAGE_CACHE = new Object2ObjectArrayMap<>(); + private final float originalSize; + private final ObjectList maps = new ObjectArrayList<>(); + private final Char2ObjectArrayMap allGlyphs = new Char2ObjectArrayMap<>(); + private int scaleMul = 0; + private Font[] fonts; + private int previousGameScale = -1; - /** - * Initializes a new FontRenderer with the specified fonts - * - * @param fonts The fonts to use. The font renderer will go over each font in this array, search for the glyph, and render it if found. If no font has the specified glyph, it will draw the missing font symbol. - * @param sizePx The size of the font in minecraft pixel units. One pixel unit = `guiScale` pixels - */ - public FontRenderer(Font[] fonts, float sizePx) { - Preconditions.checkArgument(fonts.length > 0, "fonts.length == 0"); - this.originalSize = sizePx; - init(fonts, sizePx); - } + /** + * Initializes a new FontRenderer with the specified fonts + * + * @param fonts The fonts to use. The font renderer will go over each font in this array, search for the glyph, and render it if found. If no font has the specified glyph, it will draw the missing font symbol. + * @param sizePx The size of the font in minecraft pixel units. One pixel unit = `guiScale` pixels + */ + public FontRenderer(Font[] fonts, float sizePx) { + Preconditions.checkArgument(fonts.length > 0, "fonts.length == 0"); + this.originalSize = sizePx; + init(fonts, sizePx); + } - private static int floorNearestMulN(int x, int n) { - return n * (int) Math.floor((double) x / (double) n); - } + private static int floorNearestMulN(int x, int n) { + return n * (int) Math.floor((double) x / (double) n); + } - /** - * Strips all characters prefixed with a § from the given string - * - * @param text The string to strip - * - * @return The stripped string - */ - public static String stripControlCodes(String text) { - char[] chars = text.toCharArray(); - StringBuilder f = new StringBuilder(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - if (c == '§') { - i++; - continue; - } - f.append(c); - } - return f.toString(); - } + /** + * Strips all characters prefixed with a § from the given string + * + * @param text The string to strip + * @return The stripped string + */ + public static String stripControlCodes(String text) { + char[] chars = text.toCharArray(); + StringBuilder f = new StringBuilder(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c == '§') { + i++; + continue; + } + f.append(c); + } + return f.toString(); + } - private void sizeCheck() { - int gs = RendererUtils.getGuiScale(); - if (gs != this.previousGameScale) { - close(); // delete glyphs and cache - init(this.fonts, this.originalSize); // re-init - } - } + private void sizeCheck() { + int gs = RendererUtils.getGuiScale(); + if (gs != this.previousGameScale) { + close(); // delete glyphs and cache + init(this.fonts, this.originalSize); // re-init + } + } - private void init(Font[] fonts, float sizePx) { - this.previousGameScale = RendererUtils.getGuiScale(); - this.scaleMul = this.previousGameScale; - this.fonts = new Font[fonts.length]; - for (int i = 0; i < fonts.length; i++) { - this.fonts[i] = fonts[i].deriveFont(sizePx * this.scaleMul); - } - } + private void init(Font[] fonts, float sizePx) { + this.previousGameScale = RendererUtils.getGuiScale(); + this.scaleMul = this.previousGameScale; + this.fonts = new Font[fonts.length]; + for (int i = 0; i < fonts.length; i++) { + this.fonts[i] = fonts[i].deriveFont(sizePx * this.scaleMul); + } + } - private GlyphMap generateMap(char from, char to) { - GlyphMap gm = new GlyphMap(from, to, this.fonts, RendererUtils.randomIdentifier()); - maps.add(gm); - return gm; - } + private GlyphMap generateMap(char from, char to) { + GlyphMap gm = new GlyphMap(from, to, this.fonts, RendererUtils.randomIdentifier()); + maps.add(gm); + return gm; + } - private Glyph locateGlyph0(char glyph) { - for (GlyphMap map : maps) { // go over existing ones - if (map.contains(glyph)) { // do they have it? good - return map.getGlyph(glyph); - } - } - int base = floorNearestMulN(glyph, BLOCK_SIZE); // if not, generate a new page and return the generated glyph - GlyphMap glyphMap = generateMap((char) base, (char) (base + BLOCK_SIZE)); - return glyphMap.getGlyph(glyph); - } + private Glyph locateGlyph0(char glyph) { + for (GlyphMap map : maps) { // go over existing ones + if (map.contains(glyph)) { // do they have it? good + return map.getGlyph(glyph); + } + } + int base = floorNearestMulN(glyph, BLOCK_SIZE); // if not, generate a new page and return the generated glyph + GlyphMap glyphMap = generateMap((char) base, (char) (base + BLOCK_SIZE)); + return glyphMap.getGlyph(glyph); + } - private Glyph locateGlyph1(char glyph) { - return allGlyphs.computeIfAbsent(glyph, this::locateGlyph0); - } + private Glyph locateGlyph1(char glyph) { + return allGlyphs.computeIfAbsent(glyph, this::locateGlyph0); + } - /** - * Draws a string - * - * @param stack The MatrixStack - * @param s The string to draw - * @param x X coordinate to draw at - * @param y Y coordinate to draw at - * @param r Red color component of the text to draw - * @param g Green color component of the text to draw - * @param b Blue color component of the text to draw - * @param a Alpha color component of the text to draw - */ - public void drawString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) { - sizeCheck(); - float r2 = r, g2 = g, b2 = b; - stack.push(); - stack.translate(x, y, 0); - stack.scale(1f / this.scaleMul, 1f / this.scaleMul, 1f); + /** + * Draws a string + * + * @param stack The MatrixStack + * @param s The string to draw + * @param x X coordinate to draw at + * @param y Y coordinate to draw at + * @param r Red color component of the text to draw + * @param g Green color component of the text to draw + * @param b Blue color component of the text to draw + * @param a Alpha color component of the text to draw + */ + public void drawString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) { + sizeCheck(); + float r2 = r, g2 = g, b2 = b; + stack.push(); + stack.translate(x, y, 0); + stack.scale(1f / this.scaleMul, 1f / this.scaleMul, 1f); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.disableCull(); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableCull(); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - RenderSystem.setShader(GameRenderer::getPositionTexColorProgram); - BufferBuilder bb = Tessellator.getInstance().getBuffer(); - Matrix4f mat = stack.peek().getPositionMatrix(); - char[] chars = s.toCharArray(); - float xOffset = 0; - float yOffset = 0; - boolean inSel = false; - int lineStart = 0; - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - if (inSel) { - inSel = false; - char c1 = Character.toUpperCase(c); - if (colorCodes.containsKey(c1)) { - int ii = colorCodes.get(c1); - int[] col = Colors.RGBIntToRGB(ii); - r2 = col[0] / 255f; - g2 = col[1] / 255f; - b2 = col[2] / 255f; - } else if (c1 == 'R') { - r2 = r; - g2 = g; - b2 = b; - } - continue; - } - if (c == '§') { - inSel = true; - continue; - } else if (c == '\n') { - yOffset += getStringHeight(s.substring(lineStart, i)) * scaleMul; - xOffset = 0; - lineStart = i + 1; - continue; - } - Glyph glyph = locateGlyph1(c); - if (glyph.value() != ' ') { // we only need to really draw the glyph if it's not blank, otherwise we can just skip its width and that'll be it - Identifier i1 = glyph.owner().bindToTexture; - DrawEntry entry = new DrawEntry(xOffset, yOffset, r2, g2, b2, glyph); - GLYPH_PAGE_CACHE.computeIfAbsent(i1, integer -> new ObjectArrayList<>()).add(entry); - } - xOffset += glyph.width(); - } - for (Identifier identifier : GLYPH_PAGE_CACHE.keySet()) { - RenderSystem.setShaderTexture(0, identifier); - List objects = GLYPH_PAGE_CACHE.get(identifier); + RenderSystem.setShader(GameRenderer::getPositionTexColorProgram); + BufferBuilder bb = Tessellator.getInstance().getBuffer(); + Matrix4f mat = stack.peek().getPositionMatrix(); + char[] chars = s.toCharArray(); + float xOffset = 0; + float yOffset = 0; + boolean inSel = false; + int lineStart = 0; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (inSel) { + inSel = false; + char c1 = Character.toUpperCase(c); + if (colorCodes.containsKey(c1)) { + int ii = colorCodes.get(c1); + int[] col = Colors.RGBIntToRGB(ii); + r2 = col[0] / 255f; + g2 = col[1] / 255f; + b2 = col[2] / 255f; + } else if (c1 == 'R') { + r2 = r; + g2 = g; + b2 = b; + } + continue; + } + if (c == '§') { + inSel = true; + continue; + } else if (c == '\n') { + yOffset += getStringHeight(s.substring(lineStart, i)) * scaleMul; + xOffset = 0; + lineStart = i + 1; + continue; + } + Glyph glyph = locateGlyph1(c); + if (glyph.value() != ' ') { // we only need to really draw the glyph if it's not blank, otherwise we can just skip its width and that'll be it + Identifier i1 = glyph.owner().bindToTexture; + DrawEntry entry = new DrawEntry(xOffset, yOffset, r2, g2, b2, glyph); + GLYPH_PAGE_CACHE.computeIfAbsent(i1, integer -> new ObjectArrayList<>()).add(entry); + } + xOffset += glyph.width(); + } + for (Identifier identifier : GLYPH_PAGE_CACHE.keySet()) { + RenderSystem.setShaderTexture(0, identifier); + List objects = GLYPH_PAGE_CACHE.get(identifier); - bb.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR); + bb.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR); - for (DrawEntry object : objects) { - float xo = object.atX; - float yo = object.atY; - float cr = object.r; - float cg = object.g; - float cb = object.b; - Glyph glyph = object.toDraw; - GlyphMap owner = glyph.owner(); - float w = glyph.width(); - float h = glyph.height(); - float u1 = (float) glyph.u() / owner.width; - float v1 = (float) glyph.v() / owner.height; - float u2 = (float) (glyph.u() + glyph.width()) / owner.width; - float v2 = (float) (glyph.v() + glyph.height()) / owner.height; + for (DrawEntry object : objects) { + float xo = object.atX; + float yo = object.atY; + float cr = object.r; + float cg = object.g; + float cb = object.b; + Glyph glyph = object.toDraw; + GlyphMap owner = glyph.owner(); + float w = glyph.width(); + float h = glyph.height(); + float u1 = (float) glyph.u() / owner.width; + float v1 = (float) glyph.v() / owner.height; + float u2 = (float) (glyph.u() + glyph.width()) / owner.width; + float v2 = (float) (glyph.v() + glyph.height()) / owner.height; - bb.vertex(mat, xo + 0, yo + h, 0).texture(u1, v2).color(cr, cg, cb, a).next(); - bb.vertex(mat, xo + w, yo + h, 0).texture(u2, v2).color(cr, cg, cb, a).next(); - bb.vertex(mat, xo + w, yo + 0, 0).texture(u2, v1).color(cr, cg, cb, a).next(); - bb.vertex(mat, xo + 0, yo + 0, 0).texture(u1, v1).color(cr, cg, cb, a).next(); - } - BufferUtils.draw(bb); - } + bb.vertex(mat, xo + 0, yo + h, 0).texture(u1, v2).color(cr, cg, cb, a).next(); + bb.vertex(mat, xo + w, yo + h, 0).texture(u2, v2).color(cr, cg, cb, a).next(); + bb.vertex(mat, xo + w, yo + 0, 0).texture(u2, v1).color(cr, cg, cb, a).next(); + bb.vertex(mat, xo + 0, yo + 0, 0).texture(u1, v1).color(cr, cg, cb, a).next(); + } + BufferUtils.draw(bb); + } - stack.pop(); - GLYPH_PAGE_CACHE.clear(); - } + stack.pop(); + GLYPH_PAGE_CACHE.clear(); + } - /** - * Draws a string centered on the X coordinate - * - * @param stack The MatrixStack - * @param s The string to draw - * @param x X center coordinate of the text to draw - * @param y Y coordinate of the text to draw - * @param r Red color component - * @param g Green color component - * @param b Blue color component - * @param a Alpha color component - */ - public void drawCenteredString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) { - drawString(stack, s, x - getStringWidth(s) / 2f, y, r, g, b, a); - } + /** + * Draws a string centered on the X coordinate + * + * @param stack The MatrixStack + * @param s The string to draw + * @param x X center coordinate of the text to draw + * @param y Y coordinate of the text to draw + * @param r Red color component + * @param g Green color component + * @param b Blue color component + * @param a Alpha color component + */ + public void drawCenteredString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) { + drawString(stack, s, x - getStringWidth(s) / 2f, y, r, g, b, a); + } - /** - * Calculates the width of the string, if it were drawn on the screen - * - * @param text The text to simulate - * - * @return The width of the string if it'd be drawn on the screen - */ - public float getStringWidth(String text) { - char[] c = stripControlCodes(text).toCharArray(); - float currentLine = 0; - float maxPreviousLines = 0; - for (char c1 : c) { - if (c1 == '\n') { - maxPreviousLines = Math.max(currentLine, maxPreviousLines); - currentLine = 0; - continue; - } - Glyph glyph = locateGlyph1(c1); - currentLine += glyph.width() / (float) this.scaleMul; - } - return Math.max(currentLine, maxPreviousLines); - } + /** + * Calculates the width of the string, if it were drawn on the screen + * + * @param text The text to simulate + * @return The width of the string if it'd be drawn on the screen + */ + public float getStringWidth(String text) { + char[] c = stripControlCodes(text).toCharArray(); + float currentLine = 0; + float maxPreviousLines = 0; + for (char c1 : c) { + if (c1 == '\n') { + maxPreviousLines = Math.max(currentLine, maxPreviousLines); + currentLine = 0; + continue; + } + Glyph glyph = locateGlyph1(c1); + currentLine += glyph.width() / (float) this.scaleMul; + } + return Math.max(currentLine, maxPreviousLines); + } - /** - * Calculates the height of the string, if it were drawn on the screen. This is necessary, because the fonts in this FontRenderer might have a different height for each char. - * - * @param text The text to simulate - * - * @return The height of the string if it'd be drawn on the screen - */ - public float getStringHeight(String text) { - char[] c = stripControlCodes(text).toCharArray(); - if (c.length == 0) { - c = new char[] { ' ' }; - } - float currentLine = 0; - float previous = 0; - for (char c1 : c) { - if (c1 == '\n') { - if (currentLine == 0) { - // empty line, assume space - currentLine = locateGlyph1(' ').height() / (float) this.scaleMul; - } - previous += currentLine; - currentLine = 0; - continue; - } - Glyph glyph = locateGlyph1(c1); - currentLine = Math.max(glyph.height() / (float) this.scaleMul, currentLine); - } - return currentLine + previous; - } + /** + * Calculates the height of the string, if it were drawn on the screen. This is necessary, because the fonts in this FontRenderer might have a different height for each char. + * + * @param text The text to simulate + * @return The height of the string if it'd be drawn on the screen + */ + public float getStringHeight(String text) { + char[] c = stripControlCodes(text).toCharArray(); + if (c.length == 0) { + c = new char[]{' '}; + } + float currentLine = 0; + float previous = 0; + for (char c1 : c) { + if (c1 == '\n') { + if (currentLine == 0) { + // empty line, assume space + currentLine = locateGlyph1(' ').height() / (float) this.scaleMul; + } + previous += currentLine; + currentLine = 0; + continue; + } + Glyph glyph = locateGlyph1(c1); + currentLine = Math.max(glyph.height() / (float) this.scaleMul, currentLine); + } + return currentLine + previous; + } - /** - * Clears all glyph maps, and unlinks them. The font can continue to be used, but it will have to regenerate the maps. - */ - @Override - public void close() { - for (GlyphMap map : maps) { - map.destroy(); - } - maps.clear(); - allGlyphs.clear(); - } + /** + * Clears all glyph maps, and unlinks them. The font can continue to be used, but it will have to regenerate the maps. + */ + @Override + public void close() { + for (GlyphMap map : maps) { + map.destroy(); + } + maps.clear(); + allGlyphs.clear(); + } - record DrawEntry(float atX, float atY, float r, float g, float b, Glyph toDraw) { - } + record DrawEntry(float atX, float atY, float r, float g, float b, Glyph toDraw) { + } } diff --git a/src/main/java/me/x150/renderer/font/GlyphMap.java b/src/main/java/me/x150/renderer/font/GlyphMap.java index edcbca9..d6ab688 100644 --- a/src/main/java/me/x150/renderer/font/GlyphMap.java +++ b/src/main/java/me/x150/renderer/font/GlyphMap.java @@ -6,11 +6,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; +import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; @@ -20,98 +16,99 @@ @RequiredArgsConstructor class GlyphMap { - private static final int PADDING = 5; // 5 px padding per char - final char fromIncl, toExcl; - final Font[] font; - final Identifier bindToTexture; - private final Char2ObjectArrayMap glyphs = new Char2ObjectArrayMap<>(); - int width, height; + private static final int PADDING = 5; // 5 px padding per char + final char fromIncl, toExcl; + final Font[] font; + final Identifier bindToTexture; + private final Char2ObjectArrayMap glyphs = new Char2ObjectArrayMap<>(); + int width, height; - boolean generated = false; + boolean generated = false; - public Glyph getGlyph(char c) { - if (!generated) { - generate(); - } - return glyphs.get(c); - } + public Glyph getGlyph(char c) { + if (!generated) { + generate(); + } + return glyphs.get(c); + } - public void destroy() { - MinecraftClient.getInstance().getTextureManager().destroyTexture(this.bindToTexture); - this.glyphs.clear(); - this.width = -1; - this.height = -1; - generated = false; - } + public void destroy() { + MinecraftClient.getInstance().getTextureManager().destroyTexture(this.bindToTexture); + this.glyphs.clear(); + this.width = -1; + this.height = -1; + generated = false; + } - public boolean contains(char c) { - return c >= fromIncl && c < toExcl; - } + public boolean contains(char c) { + return c >= fromIncl && c < toExcl; + } - private Font getFontForGlyph(char c) { - for (Font font1 : this.font) { - if (font1.canDisplay(c)) { - return font1; - } - } - return this.font[0]; // no font can display it, so it doesn't matter which one we pick; it'll always be missing - } + private Font getFontForGlyph(char c) { + for (Font font1 : this.font) { + if (font1.canDisplay(c)) { + return font1; + } + } + return this.font[0]; // no font can display it, so it doesn't matter which one we pick; it'll always be missing + } - public void generate() { - if (generated) { - return; - } - int range = toExcl - fromIncl - 1; - int charsVert = (int) (Math.ceil(Math.sqrt(range)) * 1.5); // double as many chars wide as high - glyphs.clear(); - int generatedChars = 0; - int charNX = 0; - int maxX = 0, maxY = 0; - int currentX = 0, currentY = 0; - int currentRowMaxY = 0; - List glyphs1 = new ArrayList<>(); - AffineTransform af = new AffineTransform(); - FontRenderContext frc = new FontRenderContext(af, true, false); - while (generatedChars <= range) { - char currentChar = (char) (fromIncl + generatedChars); - Font font = getFontForGlyph(currentChar); - Rectangle2D stringBounds = font.getStringBounds(String.valueOf(currentChar), frc); + public void generate() { + if (generated) { + return; + } + int range = toExcl - fromIncl - 1; + int charsVert = (int) (Math.ceil(Math.sqrt(range)) * 1.5); // double as many chars wide as high + glyphs.clear(); + int generatedChars = 0; + int charNX = 0; + int maxX = 0, maxY = 0; + int currentX = 0, currentY = 0; + int currentRowMaxY = 0; + List glyphs1 = new ArrayList<>(); + AffineTransform af = new AffineTransform(); + FontRenderContext frc = new FontRenderContext(af, true, false); + while (generatedChars <= range) { + char currentChar = (char) (fromIncl + generatedChars); + Font font = getFontForGlyph(currentChar); + Rectangle2D stringBounds = font.getStringBounds(String.valueOf(currentChar), frc); - int width = (int) Math.ceil(stringBounds.getWidth()); - int height = (int) Math.ceil(stringBounds.getHeight()); - generatedChars++; - maxX = Math.max(maxX, currentX + width); - maxY = Math.max(maxY, currentY + height); - if (charNX >= charsVert) { - currentX = 0; - currentY += currentRowMaxY + PADDING; // add height of highest glyph, and reset - charNX = 0; - currentRowMaxY = 0; - } - currentRowMaxY = Math.max(currentRowMaxY, height); // calculate the highest glyph in this row - glyphs1.add(new Glyph(currentX, currentY, width, height, currentChar, this)); - currentX += width + PADDING; - charNX++; - } - BufferedImage bi = new BufferedImage(Math.max(maxX + PADDING, 1), Math.max(maxY + PADDING, 1), BufferedImage.TYPE_INT_ARGB); - width = bi.getWidth(); - height = bi.getHeight(); - Graphics2D g2d = bi.createGraphics(); - g2d.setColor(new Color(255, 255, 255, 0)); - g2d.fillRect(0, 0, width, height); - g2d.setColor(Color.WHITE); + int width = (int) Math.ceil(stringBounds.getWidth()); + int height = (int) Math.ceil(stringBounds.getHeight()); + generatedChars++; + maxX = Math.max(maxX, currentX + width); + maxY = Math.max(maxY, currentY + height); + if (charNX >= charsVert) { + currentX = 0; + currentY += currentRowMaxY + PADDING; // add height of highest glyph, and reset + charNX = 0; + currentRowMaxY = 0; + } + currentRowMaxY = Math.max(currentRowMaxY, height); // calculate the highest glyph in this row + glyphs1.add(new Glyph(currentX, currentY, width, height, currentChar, this)); + currentX += width + PADDING; + charNX++; + } + BufferedImage bi = new BufferedImage(Math.max(maxX + PADDING, 1), Math.max(maxY + PADDING, 1), + BufferedImage.TYPE_INT_ARGB); + width = bi.getWidth(); + height = bi.getHeight(); + Graphics2D g2d = bi.createGraphics(); + g2d.setColor(new Color(255, 255, 255, 0)); + g2d.fillRect(0, 0, width, height); + g2d.setColor(Color.WHITE); - g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - for (Glyph glyph : glyphs1) { - g2d.setFont(getFontForGlyph(glyph.value())); - FontMetrics fontMetrics = g2d.getFontMetrics(); - g2d.drawString(String.valueOf(glyph.value()), glyph.u(), glyph.v() + fontMetrics.getAscent()); - glyphs.put(glyph.value(), glyph); - } - RendererUtils.registerBufferedImageTexture(bindToTexture, bi); - generated = true; - } + for (Glyph glyph : glyphs1) { + g2d.setFont(getFontForGlyph(glyph.value())); + FontMetrics fontMetrics = g2d.getFontMetrics(); + g2d.drawString(String.valueOf(glyph.value()), glyph.u(), glyph.v() + fontMetrics.getAscent()); + glyphs.put(glyph.value(), glyph); + } + RendererUtils.registerBufferedImageTexture(bindToTexture, bi); + generated = true; + } } diff --git a/src/main/java/me/x150/renderer/mixin/DebugHudMixin.java b/src/main/java/me/x150/renderer/mixin/DebugHudMixin.java index 313ce9b..dd049be 100644 --- a/src/main/java/me/x150/renderer/mixin/DebugHudMixin.java +++ b/src/main/java/me/x150/renderer/mixin/DebugHudMixin.java @@ -15,17 +15,19 @@ @Mixin(DebugHud.class) public class DebugHudMixin { - @Shadow - @Final - private MinecraftClient client; + @Shadow + @Final + private MinecraftClient client; - @Inject(method = "getLeftText", at = @At("RETURN")) - void addLeftText(CallbackInfoReturnable> cir) { - int currentFps = this.client.getCurrentFps(); - float currentFrameTime = 1000f * 1e+6f / currentFps; // current frame time in ns - for (Entry allTickTime : RenderProfiler.getAllTickTimes()) { - long t = allTickTime.end() - allTickTime.start(); - cir.getReturnValue().add(String.format("[Renderer bench] %s: %07d ns (%02.2f%% of frame)", allTickTime.name(), t, t / currentFrameTime * 100f)); - } - } + @Inject(method = "getLeftText", at = @At("RETURN")) + void addLeftText(CallbackInfoReturnable> cir) { + int currentFps = this.client.getCurrentFps(); + float currentFrameTime = 1000f * 1e+6f / currentFps; // current frame time in ns + for (Entry allTickTime : RenderProfiler.getAllTickTimes()) { + long t = allTickTime.end() - allTickTime.start(); + cir.getReturnValue() + .add(String.format("[Renderer bench] %s: %07d ns (%02.2f%% of frame)", allTickTime.name(), t, + t / currentFrameTime * 100f)); + } + } } diff --git a/src/main/java/me/x150/renderer/mixin/GameRendererMixin.java b/src/main/java/me/x150/renderer/mixin/GameRendererMixin.java index 37190a0..85febe2 100644 --- a/src/main/java/me/x150/renderer/mixin/GameRendererMixin.java +++ b/src/main/java/me/x150/renderer/mixin/GameRendererMixin.java @@ -16,17 +16,17 @@ @Mixin(GameRenderer.class) public abstract class GameRendererMixin { - @SuppressWarnings("SpellCheckingInspection") - @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/GameRenderer;renderHand:Z", opcode = Opcodes.GETFIELD, ordinal = 0), method = "renderWorld") - void renderer_postWorldRender(float tickDelta, long limitTime, MatrixStack matrix, CallbackInfo ci) { - RenderProfiler.begin("World"); + @SuppressWarnings("SpellCheckingInspection") + @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/GameRenderer;renderHand:Z", opcode = Opcodes.GETFIELD, ordinal = 0), method = "renderWorld") + void renderer_postWorldRender(float tickDelta, long limitTime, MatrixStack matrix, CallbackInfo ci) { + RenderProfiler.begin("World"); - RendererUtils.lastProjMat.set(RenderSystem.getProjectionMatrix()); - RendererUtils.lastModMat.set(RenderSystem.getModelViewMatrix()); - RendererUtils.lastWorldSpaceMatrix.set(matrix.peek().getPositionMatrix()); - RenderEvents.WORLD.invoker().rendered(matrix); - Renderer3d.renderFadingBlocks(matrix); + RendererUtils.lastProjMat.set(RenderSystem.getProjectionMatrix()); + RendererUtils.lastModMat.set(RenderSystem.getModelViewMatrix()); + RendererUtils.lastWorldSpaceMatrix.set(matrix.peek().getPositionMatrix()); + RenderEvents.WORLD.invoker().rendered(matrix); + Renderer3d.renderFadingBlocks(matrix); - RenderProfiler.pop(); - } + RenderProfiler.pop(); + } } diff --git a/src/main/java/me/x150/renderer/mixin/InGameHudMixin.java b/src/main/java/me/x150/renderer/mixin/InGameHudMixin.java index d308d80..1f18c40 100644 --- a/src/main/java/me/x150/renderer/mixin/InGameHudMixin.java +++ b/src/main/java/me/x150/renderer/mixin/InGameHudMixin.java @@ -4,7 +4,6 @@ import me.x150.renderer.util.RenderProfiler; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; -import net.minecraft.client.util.math.MatrixStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -12,10 +11,10 @@ @Mixin(InGameHud.class) public class InGameHudMixin { - @Inject(method = "render", at = @At("RETURN")) - void renderer_postHud(DrawContext context, float tickDelta, CallbackInfo ci) { - RenderProfiler.begin("Hud"); - RenderEvents.HUD.invoker().rendered(context.getMatrices()); - RenderProfiler.pop(); - } + @Inject(method = "render", at = @At("RETURN")) + void renderer_postHud(DrawContext context, float tickDelta, CallbackInfo ci) { + RenderProfiler.begin("Hud"); + RenderEvents.HUD.invoker().rendered(context); + RenderProfiler.pop(); + } } diff --git a/src/main/java/me/x150/renderer/mixin/JsonEffectShaderProgramMixin.java b/src/main/java/me/x150/renderer/mixin/JsonEffectShaderProgramMixin.java deleted file mode 100644 index 2974b04..0000000 --- a/src/main/java/me/x150/renderer/mixin/JsonEffectShaderProgramMixin.java +++ /dev/null @@ -1,46 +0,0 @@ -package me.x150.renderer.mixin; - -import net.minecraft.client.gl.JsonEffectShaderProgram; -import net.minecraft.client.gl.ShaderStage.Type; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(JsonEffectShaderProgram.class) -public abstract class JsonEffectShaderProgramMixin { - - @Redirect(at = @At(value = "NEW", target = "(Ljava/lang/String;)Lnet/minecraft/util/Identifier;"), method = "loadEffect") - private static Identifier constructProgramIdentifier(String arg, ResourceManager unused, Type shaderType, String id) { - if (!arg.contains(":")) { - return new Identifier(arg); - } - Identifier split = new Identifier(id); - return new Identifier(split.getNamespace(), "shaders/program/" + split.getPath() + shaderType.getFileExtension()); - } - - /** - * Fix identifier creation to allow different namespaces - * - *

Why a redirect ? - *

    - *
  • Because letting the identifier be built will throw an exception, so no ModifyVariable
  • - *
  • Because we need to access the original id, so no ModifyArg (alternatively we could use 2 injections and a ThreadLocal but:)
  • - *
  • Because we assume other people who may want to do the same change can use this library
  • - *
- * - * @param arg the string passed to the redirected Identifier constructor - * @param id the actual id passed as an argument to the method - * - * @return a new Identifier - */ - @Redirect(at = @At(value = "NEW", target = "(Ljava/lang/String;)Lnet/minecraft/util/Identifier;"), method = "") - Identifier constructProgramIdentifier(String arg, ResourceManager unused, String id) { - if (!id.contains(":")) { - return new Identifier(arg); - } - Identifier split = new Identifier(id); - return new Identifier(split.getNamespace(), "shaders/program/" + split.getPath() + ".json"); - } -} diff --git a/src/main/java/me/x150/renderer/mixin/PostProcessShaderMixin.java b/src/main/java/me/x150/renderer/mixin/PostEffectPassAccessor.java similarity index 56% rename from src/main/java/me/x150/renderer/mixin/PostProcessShaderMixin.java rename to src/main/java/me/x150/renderer/mixin/PostEffectPassAccessor.java index 2b2ff32..533fa41 100644 --- a/src/main/java/me/x150/renderer/mixin/PostProcessShaderMixin.java +++ b/src/main/java/me/x150/renderer/mixin/PostEffectPassAccessor.java @@ -7,12 +7,12 @@ import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(PostEffectPass.class) -public interface PostProcessShaderMixin { - @Mutable - @Accessor("input") - void renderer_setInput(Framebuffer framebuffer); +public interface PostEffectPassAccessor { + @Mutable + @Accessor("input") + void renderer_setInput(Framebuffer framebuffer); - @Mutable - @Accessor("output") - void renderer_setOutput(Framebuffer framebuffer); + @Mutable + @Accessor("output") + void renderer_setOutput(Framebuffer framebuffer); } diff --git a/src/main/java/me/x150/renderer/mixin/PostEffectProcessorMixin.java b/src/main/java/me/x150/renderer/mixin/PostEffectProcessorMixin.java new file mode 100644 index 0000000..e9c9f8d --- /dev/null +++ b/src/main/java/me/x150/renderer/mixin/PostEffectProcessorMixin.java @@ -0,0 +1,66 @@ +package me.x150.renderer.mixin; + +import me.x150.renderer.mixinUtil.ShaderEffectDuck; +import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gl.PostEffectPass; +import net.minecraft.client.gl.PostEffectProcessor; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mixin(PostEffectProcessor.class) +public class PostEffectProcessorMixin implements ShaderEffectDuck { + + @Unique + private final List renderer$fakedBuffers = new ArrayList<>(); + @Shadow + @Final + private Map targetsByName; + @Shadow + @Final + private List passes; + + @Override + public List renderer$getPasses() { + return passes; + } + + @Override + public void renderer$addFakeTarget(String name, Framebuffer buffer) { + Framebuffer previousFramebuffer = this.targetsByName.get(name); + if (previousFramebuffer == buffer) { + return; // no need to do anything + } + if (previousFramebuffer != null) { + for (PostEffectPass pass : this.passes) { + // replace input and output of each pass to our new framebuffer, if they reference the one we're replacing + if (pass.input == previousFramebuffer) { + ((PostEffectPassAccessor) pass).renderer_setInput(buffer); + } + if (pass.output == previousFramebuffer) { + ((PostEffectPassAccessor) pass).renderer_setOutput(buffer); + } + } + this.targetsByName.remove(name); + this.renderer$fakedBuffers.remove(name); + } + + this.targetsByName.put(name, buffer); + this.renderer$fakedBuffers.add(name); + } + + @Inject(method = "close", at = @At("HEAD")) + void renderer_deleteFakeBuffers(CallbackInfo ci) { + for (String fakedBufferName : renderer$fakedBuffers) { + targetsByName.remove(fakedBufferName); // remove without closing + } + } +} diff --git a/src/main/java/me/x150/renderer/mixin/ShaderEffectMixin.java b/src/main/java/me/x150/renderer/mixin/ShaderEffectMixin.java deleted file mode 100644 index 518211a..0000000 --- a/src/main/java/me/x150/renderer/mixin/ShaderEffectMixin.java +++ /dev/null @@ -1,64 +0,0 @@ -package me.x150.renderer.mixin; - -import me.x150.renderer.mixinUtil.ShaderEffectDuck; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.gl.PostEffectPass; -import net.minecraft.client.gl.PostEffectProcessor; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@Mixin(PostEffectProcessor.class) -public class ShaderEffectMixin implements ShaderEffectDuck { - - private final List fakedBufferNames = new ArrayList<>(); - @Shadow - @Final - private Map targetsByName; - @Shadow - @Final - private List passes; - - @Override - public List getPasses() { - return passes; - } - - @Override - public void addFakeTarget(String name, Framebuffer buffer) { - Framebuffer previousFramebuffer = this.targetsByName.get(name); - if (previousFramebuffer == buffer) { - return; // no need to do anything - } - if (previousFramebuffer != null) { - for (PostEffectPass pass : this.passes) { - // replace input and output of each pass to our new framebuffer, if they reference the one we're replacing - if (pass.input == previousFramebuffer) { - ((PostProcessShaderMixin) pass).renderer_setInput(buffer); - } - if (pass.output == previousFramebuffer) { - ((PostProcessShaderMixin) pass).renderer_setOutput(buffer); - } - } - this.targetsByName.remove(name); - this.fakedBufferNames.remove(name); - } - - this.targetsByName.put(name, buffer); - this.fakedBufferNames.add(name); - } - - @Inject(method = "close", at = @At("HEAD")) - void renderer_deleteFakeBuffers(CallbackInfo ci) { - for (String fakedBufferName : fakedBufferNames) { - targetsByName.remove(fakedBufferName); // remove without closing - } - } -} diff --git a/src/main/java/me/x150/renderer/mixinUtil/ShaderEffectDuck.java b/src/main/java/me/x150/renderer/mixinUtil/ShaderEffectDuck.java index 34e6d09..430730f 100644 --- a/src/main/java/me/x150/renderer/mixinUtil/ShaderEffectDuck.java +++ b/src/main/java/me/x150/renderer/mixinUtil/ShaderEffectDuck.java @@ -6,7 +6,7 @@ import java.util.List; public interface ShaderEffectDuck { - void addFakeTarget(String name, Framebuffer buffer); + void renderer$addFakeTarget(String name, Framebuffer buffer); - List getPasses(); + List renderer$getPasses(); } diff --git a/src/main/java/me/x150/renderer/objfile/ObjFile.java b/src/main/java/me/x150/renderer/objfile/ObjFile.java index a6d3482..b347d4f 100644 --- a/src/main/java/me/x150/renderer/objfile/ObjFile.java +++ b/src/main/java/me/x150/renderer/objfile/ObjFile.java @@ -1,26 +1,13 @@ package me.x150.renderer.objfile; import com.mojang.blaze3d.systems.RenderSystem; -import de.javagl.obj.FloatTuple; -import de.javagl.obj.Mtl; -import de.javagl.obj.MtlReader; -import de.javagl.obj.Obj; -import de.javagl.obj.ObjFace; -import de.javagl.obj.ObjReader; -import de.javagl.obj.ObjSplitting; -import de.javagl.obj.ObjUtils; +import de.javagl.obj.*; import me.x150.renderer.util.BufferUtils; import me.x150.renderer.util.RendererUtils; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.ShaderProgram; import net.minecraft.client.gl.VertexBuffer; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.client.render.Tessellator; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.VertexFormat; -import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.render.*; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec3d; @@ -33,11 +20,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Supplier; /** @@ -61,208 +44,206 @@ * To render a loaded ObjFile, call {@link #draw(MatrixStack, Matrix4f, Vec3d)}. */ public class ObjFile implements Closeable { - private final ResourceProvider provider; - private final String name; - Map buffers = new HashMap<>(); - Map boundTextures = new HashMap<>(); - Map materialNameObjMap; - private List allMaterials; - private boolean baked = false; - private boolean closed = false; + private final ResourceProvider provider; + private final String name; + final Map buffers = new HashMap<>(); + final Map boundTextures = new HashMap<>(); + Map materialNameObjMap; + private List allMaterials; + private boolean baked = false; + private boolean closed = false; - /** - * Creates a new ObjFile - * - * @param name Filename of the target .obj, resolved by the {@link ResourceProvider} {@code provider} - * @param provider The resource provider to use - * - * @throws IOException When reading the .obj fails - */ - public ObjFile(String name, ResourceProvider provider) throws IOException { - this.name = name; - this.provider = provider; - read(); - } + /** + * Creates a new ObjFile + * + * @param name Filename of the target .obj, resolved by the {@link ResourceProvider} {@code provider} + * @param provider The resource provider to use + * @throws IOException When reading the .obj fails + */ + public ObjFile(String name, ResourceProvider provider) throws IOException { + this.name = name; + this.provider = provider; + read(); + } - private static Vec3d transformVec3d(Vec3d in) { - Camera camera = MinecraftClient.getInstance().gameRenderer.getCamera(); - Vec3d camPos = camera.getPos(); - return in.subtract(camPos); - } + private static Vec3d transformVec3d(Vec3d in) { + Camera camera = MinecraftClient.getInstance().gameRenderer.getCamera(); + Vec3d camPos = camera.getPos(); + return in.subtract(camPos); + } - private void read() throws IOException { - try (InputStream reader = provider.open(name)) { - Obj r = ObjUtils.convertToRenderable(ObjReader.read(reader)); - allMaterials = new ArrayList<>(); - for (String mtlFileName : r.getMtlFileNames()) { - try (InputStream openReaderTo = provider.open(mtlFileName)) { - List read = MtlReader.read(openReaderTo); - allMaterials.addAll(read); - } - } - materialNameObjMap = ObjSplitting.splitByMaterialGroups(r); + private void read() throws IOException { + try (InputStream reader = provider.open(name)) { + Obj r = ObjUtils.convertToRenderable(ObjReader.read(reader)); + allMaterials = new ArrayList<>(); + for (String mtlFileName : r.getMtlFileNames()) { + try (InputStream openReaderTo = provider.open(mtlFileName)) { + List read = MtlReader.read(openReaderTo); + allMaterials.addAll(read); + } + } + materialNameObjMap = ObjSplitting.splitByMaterialGroups(r); - } - } + } + } - private Identifier createTex0(String s) { - try (InputStream reader = this.provider.open(s)) { - Identifier identifier = RendererUtils.randomIdentifier(); - BufferedImage read1 = ImageIO.read(reader); - RendererUtils.registerBufferedImageTexture(identifier, read1); - return identifier; - } catch (IOException e) { - throw new RuntimeException(e); - } - } + private Identifier createTex0(String s) { + try (InputStream reader = this.provider.open(s)) { + Identifier identifier = RendererUtils.randomIdentifier(); + BufferedImage read1 = ImageIO.read(reader); + RendererUtils.registerBufferedImageTexture(identifier, read1); + return identifier; + } catch (IOException e) { + throw new RuntimeException(e); + } + } - private void bake() { - BufferBuilder b = Tessellator.getInstance().getBuffer(); - for (Map.Entry stringObjEntry : materialNameObjMap.entrySet()) { - String materialName = stringObjEntry.getKey(); - Obj objToDraw = stringObjEntry.getValue(); - Mtl material = allMaterials.stream().filter(f -> f.getName().equals(materialName)).findFirst().orElse(null); - boolean hasTexture = material != null && material.getMapKd() != null; - if (hasTexture) { - String mapKd = material.getMapKd(); - System.out.println(mapKd); - boundTextures.computeIfAbsent(mapKd, this::createTex0); - } - VertexFormat vmf; - if (material != null) { - vmf = hasTexture ? VertexFormats.POSITION_TEXTURE_COLOR_NORMAL : VertexFormats.POSITION_COLOR; - } else { - vmf = VertexFormats.POSITION; - } - b.begin(VertexFormat.DrawMode.TRIANGLES, vmf); - for (int i = 0; i < objToDraw.getNumFaces(); i++) { - ObjFace face = objToDraw.getFace(i); - boolean hasNormals = face.containsNormalIndices(); - boolean hasUV = face.containsTexCoordIndices(); - for (int i1 = 0; i1 < face.getNumVertices(); i1++) { - FloatTuple xyz = objToDraw.getVertex(face.getVertexIndex(i1)); - VertexConsumer vertex = b.vertex(xyz.getX(), xyz.getY(), xyz.getZ()); - if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL) { - if (!hasUV) { - throw new IllegalStateException("Diffuse texture present, vertex doesn't have UV coordinates. File corrupted?"); - } - if (!hasNormals) { - throw new IllegalStateException("Diffuse texture present, vertex doesn't have normal coordinates. File corrupted?"); - } - FloatTuple uvs = objToDraw.getTexCoord(face.getTexCoordIndex(i1)); - vertex.texture(uvs.getX(), 1 - uvs.getY()); - } - if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL || vmf == VertexFormats.POSITION_COLOR) { - Objects.requireNonNull(material); - FloatTuple kd = material.getKd(); - if (kd != null) { - vertex.color(kd.getX(), kd.getY(), kd.getZ(), 1f); - } else { - vertex.color(1f, 1f, 1f, 1f); - } - } - if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL) { - FloatTuple normals = objToDraw.getNormal(face.getNormalIndex(i1)); - vertex.normal(normals.getX(), normals.getY(), normals.getZ()); - } - vertex.next(); - } - } - BufferBuilder.BuiltBuffer end = b.end(); - buffers.put(objToDraw, BufferUtils.createVbo(end, VertexBuffer.Usage.STATIC)); - } - baked = true; - } + private void bake() { + BufferBuilder b = Tessellator.getInstance().getBuffer(); + for (Map.Entry stringObjEntry : materialNameObjMap.entrySet()) { + String materialName = stringObjEntry.getKey(); + Obj objToDraw = stringObjEntry.getValue(); + Mtl material = allMaterials.stream().filter(f -> f.getName().equals(materialName)).findFirst().orElse(null); + boolean hasTexture = material != null && material.getMapKd() != null; + if (hasTexture) { + String mapKd = material.getMapKd(); + boundTextures.computeIfAbsent(mapKd, this::createTex0); + } + VertexFormat vmf; + if (material != null) { + vmf = hasTexture ? VertexFormats.POSITION_TEXTURE_COLOR_NORMAL : VertexFormats.POSITION_COLOR; + } else { + vmf = VertexFormats.POSITION; + } + b.begin(VertexFormat.DrawMode.TRIANGLES, vmf); + for (int i = 0; i < objToDraw.getNumFaces(); i++) { + ObjFace face = objToDraw.getFace(i); + boolean hasNormals = face.containsNormalIndices(); + boolean hasUV = face.containsTexCoordIndices(); + for (int i1 = 0; i1 < face.getNumVertices(); i1++) { + FloatTuple xyz = objToDraw.getVertex(face.getVertexIndex(i1)); + VertexConsumer vertex = b.vertex(xyz.getX(), xyz.getY(), xyz.getZ()); + if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL) { + if (!hasUV) { + throw new IllegalStateException( + "Diffuse texture present, vertex doesn't have UV coordinates. File corrupted?"); + } + if (!hasNormals) { + throw new IllegalStateException( + "Diffuse texture present, vertex doesn't have normal coordinates. File corrupted?"); + } + FloatTuple uvs = objToDraw.getTexCoord(face.getTexCoordIndex(i1)); + vertex.texture(uvs.getX(), 1 - uvs.getY()); + } + if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL || vmf == VertexFormats.POSITION_COLOR) { + Objects.requireNonNull(material); + FloatTuple kd = material.getKd(); + if (kd != null) { + vertex.color(kd.getX(), kd.getY(), kd.getZ(), 1f); + } else { + vertex.color(1f, 1f, 1f, 1f); + } + } + if (vmf == VertexFormats.POSITION_TEXTURE_COLOR_NORMAL) { + FloatTuple normals = objToDraw.getNormal(face.getNormalIndex(i1)); + vertex.normal(normals.getX(), normals.getY(), normals.getZ()); + } + vertex.next(); + } + } + BufferBuilder.BuiltBuffer end = b.end(); + buffers.put(objToDraw, BufferUtils.createVbo(end, VertexBuffer.Usage.STATIC)); + } + baked = true; + } - /** - * Draws this ObjFile. Calls {@link #bake()} if necessary. - * - * @param stack MatrixStack - * @param viewMatrix View matrix to apply to this ObjFile, independent of any other matrix. - * @param origin Origin point to draw at - */ - public void draw(MatrixStack stack, Matrix4f viewMatrix, Vec3d origin) { - if (closed) { - throw new IllegalStateException("Closed"); - } - if (!baked) { - bake(); - } - Vec3d o = transformVec3d(origin); - Matrix4f projectionMatrix = RenderSystem.getProjectionMatrix(); - Matrix4f m4f = new Matrix4f(stack.peek().getPositionMatrix()); - m4f.translate((float) o.x, (float) o.y, (float) o.z); - m4f.mul(viewMatrix); + /** + * Draws this ObjFile. Calls {@link #bake()} if necessary. + * + * @param stack MatrixStack + * @param viewMatrix View matrix to apply to this ObjFile, independent of any other matrix. + * @param origin Origin point to draw at + */ + public void draw(MatrixStack stack, Matrix4f viewMatrix, Vec3d origin) { + if (closed) { + throw new IllegalStateException("Closed"); + } + if (!baked) { + bake(); + } + Vec3d o = transformVec3d(origin); + Matrix4f projectionMatrix = RenderSystem.getProjectionMatrix(); + Matrix4f m4f = new Matrix4f(stack.peek().getPositionMatrix()); + m4f.translate((float) o.x, (float) o.y, (float) o.z); + m4f.mul(viewMatrix); - RendererUtils.setupRender(); - RenderSystem.enableCull(); - for (Map.Entry stringObjEntry : materialNameObjMap.entrySet()) { - String materialName = stringObjEntry.getKey(); - Obj obj = stringObjEntry.getValue(); - Mtl material = allMaterials.stream().filter(f -> f.getName().equals(materialName)).findFirst().orElse(null); - boolean hasTexture = material != null && material.getMapKd() != null; - if (hasTexture) { - String mapKd = material.getMapKd(); - Identifier identifier = boundTextures.get(mapKd); - RenderSystem.setShaderTexture(0, identifier); - } - Supplier shader; - if (material != null) { - shader = hasTexture ? GameRenderer::getPositionTexColorNormalProgram : GameRenderer::getPositionColorProgram; - } else { - shader = GameRenderer::getPositionProgram; - } - VertexBuffer vertexBuffer = buffers.get(obj); - vertexBuffer.bind(); - vertexBuffer.draw(m4f, projectionMatrix, shader.get()); - } - VertexBuffer.unbind(); - RendererUtils.endRender(); - } + RendererUtils.setupRender(); + RenderSystem.enableCull(); + for (Map.Entry stringObjEntry : materialNameObjMap.entrySet()) { + String materialName = stringObjEntry.getKey(); + Obj obj = stringObjEntry.getValue(); + Mtl material = allMaterials.stream().filter(f -> f.getName().equals(materialName)).findFirst().orElse(null); + boolean hasTexture = material != null && material.getMapKd() != null; + if (hasTexture) { + String mapKd = material.getMapKd(); + Identifier identifier = boundTextures.get(mapKd); + RenderSystem.setShaderTexture(0, identifier); + } + Supplier shader; + if (material != null) { + shader = hasTexture ? GameRenderer::getPositionTexColorNormalProgram : GameRenderer::getPositionColorProgram; + } else { + shader = GameRenderer::getPositionProgram; + } + VertexBuffer vertexBuffer = buffers.get(obj); + vertexBuffer.bind(); + vertexBuffer.draw(m4f, projectionMatrix, shader.get()); + } + VertexBuffer.unbind(); + RendererUtils.endRender(); + } - /** - * Clears all associated VertexBuffers, removes every linked texture and closes this ObjFile. All subsequent calls to any method will fail. - */ - @Override - public void close() { - for (VertexBuffer buffer : buffers.values()) { - buffer.close(); - } - buffers.clear(); - for (Identifier value : boundTextures.values()) { - MinecraftClient.getInstance().getTextureManager().destroyTexture(value); - } - boundTextures.clear(); - allMaterials.clear(); - closed = true; - } + /** + * Clears all associated VertexBuffers, removes every linked texture and closes this ObjFile. All subsequent calls to any method will fail. + */ + @Override + public void close() { + for (VertexBuffer buffer : buffers.values()) { + buffer.close(); + } + buffers.clear(); + for (Identifier value : boundTextures.values()) { + MinecraftClient.getInstance().getTextureManager().destroyTexture(value); + } + boundTextures.clear(); + allMaterials.clear(); + closed = true; + } - /** - * A function, which maps a resource name found in an .obj file to an InputStream - */ - @FunctionalInterface - public interface ResourceProvider { - /** - * Appends the filename to read to a Path, then tries to load the resulting file. - * - * @param parent Parent path of all files - * - * @return New ResourceProvider - */ - static ResourceProvider ofPath(Path parent) { - return name -> { - Path resolve = parent.resolve(name); - return Files.newInputStream(resolve); - }; - } + /** + * A function, which maps a resource name found in an .obj file to an InputStream + */ + @FunctionalInterface + public interface ResourceProvider { + /** + * Appends the filename to read to a Path, then tries to load the resulting file. + * + * @param parent Parent path of all files + * @return New ResourceProvider + */ + static ResourceProvider ofPath(Path parent) { + return name -> { + Path resolve = parent.resolve(name); + return Files.newInputStream(resolve); + }; + } - /** - * Opens {@code name} as InputStream - * - * @param name Filename to open - * - * @return The opened InputStream. Closed by the library when done. - */ - InputStream open(String name) throws IOException; - } + /** + * Opens {@code name} as InputStream + * + * @param name Filename to open + * @return The opened InputStream. Closed by the library when done. + */ + InputStream open(String name) throws IOException; + } } diff --git a/src/main/java/me/x150/renderer/render/ClipStack.java b/src/main/java/me/x150/renderer/render/ClipStack.java index 639b13b..6976d35 100644 --- a/src/main/java/me/x150/renderer/render/ClipStack.java +++ b/src/main/java/me/x150/renderer/render/ClipStack.java @@ -13,88 +13,88 @@ * A class used for defining clipping rectangles */ public class ClipStack { - static final Stack clipStack = new Stack<>(); + static final Stack clipStack = new Stack<>(); - /** - *

Adds a clipping window to the stack

- *

All new rendered elements will only be rendered if they conform to this rectangle and the others above it

- * Always call {@link #popWindow()} after you're done rendering with this - * - * @param stack The context MatrixStack - * @param rect The new clipping rectangle to enlist - */ - public static void addWindow(MatrixStack stack, Rectangle rect) { - Matrix4f matrix = stack.peek().getPositionMatrix(); - Vector4f start = new Vector4f((float) rect.getX(), (float) rect.getY(), 0, 1); - Vector4f end = new Vector4f((float) rect.getX1(), (float) rect.getY1(), 0, 1); - start.mul(matrix); - end.mul(matrix); - double x0 = start.x(); - double y0 = start.y(); - double x1 = end.x(); - double y1 = end.y(); - Rectangle transformed = new Rectangle(x0, y0, x1, y1); - if (clipStack.empty()) { - clipStack.push(transformed); - Renderer2d.beginScissor(transformed.getX(), transformed.getY(), transformed.getX1(), transformed.getY1()); - } else { - Rectangle lastClip = clipStack.peek(); - double lx0 = lastClip.getX(); - double ly0 = lastClip.getY(); - double lx1 = lastClip.getX1(); - double ly1 = lastClip.getY1(); - double nx0 = MathHelper.clamp(transformed.getX(), lx0, lx1); - double ny0 = MathHelper.clamp(transformed.getY(), ly0, ly1); - double nx1 = MathHelper.clamp(transformed.getX1(), nx0, lx1); - double ny1 = MathHelper.clamp(transformed.getY1(), ny0, ly1); - clipStack.push(new Rectangle(nx0, ny0, nx1, ny1)); - Renderer2d.beginScissor(nx0, ny0, nx1, ny1); - } - } + /** + *

Adds a clipping window to the stack

+ *

All new rendered elements will only be rendered if they conform to this rectangle and the others above it

+ * Always call {@link #popWindow()} after you're done rendering with this + * + * @param stack The context MatrixStack + * @param rect The new clipping rectangle to enlist + */ + public static void addWindow(MatrixStack stack, Rectangle rect) { + Matrix4f matrix = stack.peek().getPositionMatrix(); + Vector4f start = new Vector4f((float) rect.getX(), (float) rect.getY(), 0, 1); + Vector4f end = new Vector4f((float) rect.getX1(), (float) rect.getY1(), 0, 1); + start.mul(matrix); + end.mul(matrix); + double x0 = start.x(); + double y0 = start.y(); + double x1 = end.x(); + double y1 = end.y(); + Rectangle transformed = new Rectangle(x0, y0, x1, y1); + if (clipStack.empty()) { + clipStack.push(transformed); + Renderer2d.beginScissor(transformed.getX(), transformed.getY(), transformed.getX1(), transformed.getY1()); + } else { + Rectangle lastClip = clipStack.peek(); + double lx0 = lastClip.getX(); + double ly0 = lastClip.getY(); + double lx1 = lastClip.getX1(); + double ly1 = lastClip.getY1(); + double nx0 = MathHelper.clamp(transformed.getX(), lx0, lx1); + double ny0 = MathHelper.clamp(transformed.getY(), ly0, ly1); + double nx1 = MathHelper.clamp(transformed.getX1(), nx0, lx1); + double ny1 = MathHelper.clamp(transformed.getY1(), ny0, ly1); + clipStack.push(new Rectangle(nx0, ny0, nx1, ny1)); + Renderer2d.beginScissor(nx0, ny0, nx1, ny1); + } + } - /** - * Adds a window using {@link #addWindow(MatrixStack, Rectangle)}, calls renderAction, then removes the previously added window automatically. - *

- * You can replace this by separate {@link #addWindow(MatrixStack, Rectangle)} and {@link #popWindow()} calls, although using this method will do that for you. - * - * @param stack The context MatrixStack - * @param clippingRect The clipping rectangle that should be applied to the renderAction - * @param renderAction The actual render method, that renders the content - */ - public static void use(MatrixStack stack, Rectangle clippingRect, Runnable renderAction) { - addWindow(stack, clippingRect); - renderAction.run(); - popWindow(); - } + /** + * Adds a window using {@link #addWindow(MatrixStack, Rectangle)}, calls renderAction, then removes the previously added window automatically. + *

+ * You can replace this by separate {@link #addWindow(MatrixStack, Rectangle)} and {@link #popWindow()} calls, although using this method will do that for you. + * + * @param stack The context MatrixStack + * @param clippingRect The clipping rectangle that should be applied to the renderAction + * @param renderAction The actual render method, that renders the content + */ + public static void use(MatrixStack stack, Rectangle clippingRect, Runnable renderAction) { + addWindow(stack, clippingRect); + renderAction.run(); + popWindow(); + } - /** - *

Pops the latest added window from the stack

- */ - public static void popWindow() { - clipStack.pop(); - if (clipStack.empty()) { - Renderer2d.endScissor(); - } else { - Rectangle r = clipStack.peek(); - Renderer2d.beginScissor(r.getX(), r.getY(), r.getX1(), r.getY1()); - } - } + /** + *

Pops the latest added window from the stack

+ */ + public static void popWindow() { + clipStack.pop(); + if (clipStack.empty()) { + Renderer2d.endScissor(); + } else { + Rectangle r = clipStack.peek(); + Renderer2d.beginScissor(r.getX(), r.getY(), r.getX1(), r.getY1()); + } + } - /** - *

Renders something outside of the currently applied clipping rectangle stack

- * - * @param e The runnable to run outside the clip stack - */ - public static void renderOutsideClipStack(Runnable e) { - if (clipStack.empty()) { - e.run(); - } else { - Renderer2d.endScissor(); - e.run(); - Rectangle r = clipStack.peek(); - Renderer2d.beginScissor(r.getX(), r.getY(), r.getX1(), r.getY1()); - } - } + /** + *

Renders something outside of the currently applied clipping rectangle stack

+ * + * @param e The runnable to run outside the clip stack + */ + public static void renderOutsideClipStack(Runnable e) { + if (clipStack.empty()) { + e.run(); + } else { + Renderer2d.endScissor(); + e.run(); + Rectangle r = clipStack.peek(); + Renderer2d.beginScissor(r.getX(), r.getY(), r.getX1(), r.getY1()); + } + } } diff --git a/src/main/java/me/x150/renderer/render/MSAAFramebuffer.java b/src/main/java/me/x150/renderer/render/MSAAFramebuffer.java index d761d8d..5e8cd5a 100644 --- a/src/main/java/me/x150/renderer/render/MSAAFramebuffer.java +++ b/src/main/java/me/x150/renderer/render/MSAAFramebuffer.java @@ -16,205 +16,208 @@ * A framebuffer that uses MSAA to smooth the things rendered inside it */ public class MSAAFramebuffer extends Framebuffer { - /** - * The minimum amount of legal samples - */ - public static final int MIN_SAMPLES = 2; - /** - * The maximum amount of legal samples - */ - public static final int MAX_SAMPLES = GL30.glGetInteger(GL30C.GL_MAX_SAMPLES); - - private static final Map INSTANCES = new HashMap<>(); - private static final List ACTIVE_INSTANCES = new ArrayList<>(); - - private final int samples; - private int rboColor; - private int rboDepth; - private boolean inUse; - - private MSAAFramebuffer(int samples) { - super(true); - if (samples < MIN_SAMPLES || samples > MAX_SAMPLES) { - throw new IllegalArgumentException(String.format("The number of samples should be >= %s and <= %s.", MIN_SAMPLES, MAX_SAMPLES)); - } - if ((samples & samples - 1) != 0) { - throw new IllegalArgumentException("The number of samples must be a power of two."); - } - - this.samples = samples; - this.setClearColor(1F, 1F, 1F, 0F); - } - - /** - * Checks if any instance is currently being used - * - * @return If any instance is currently being used - */ - public static boolean framebufferInUse() { - return !ACTIVE_INSTANCES.isEmpty(); - } - - /** - * Gets an instance with the sample count provided - * - * @param samples The desired sample count - * - * @return The framebuffer instance - */ - public static MSAAFramebuffer getInstance(int samples) { - return INSTANCES.computeIfAbsent(samples, x -> new MSAAFramebuffer(samples)); - } - - /** - *

Uses the framebuffer with the rendering calls in the action specified

- *

Switching frame buffers is not particularly efficient. Use with caution

- * - * @param samples The desired amount of samples to be used. While everything up to (and including) MAX_SAMPLES is accepted, anything above 16 is generally overkill. - * @param drawAction The runnable that gets executed within the framebuffer. Render your things there. - */ - public static void use(int samples, Runnable drawAction) { - use(samples, MinecraftClient.getInstance().getFramebuffer(), drawAction); - } - - /** - *

Uses the framebuffer with the rendering calls in the action specified

- *

If you do not know what you're doing, use {@link #use(int, Runnable)} instead

- * - * @param samples The desired amount of samples to be used - * @param mainBuffer The main framebuffer to read and write to once the buffer finishes - * @param drawAction The runnable that gets executed within the framebuffer. Render your things there. - */ - public static void use(int samples, Framebuffer mainBuffer, Runnable drawAction) { - RenderSystem.assertOnRenderThreadOrInit(); - MSAAFramebuffer msaaBuffer = MSAAFramebuffer.getInstance(samples); - msaaBuffer.resize(mainBuffer.textureWidth, mainBuffer.textureHeight, true); - - GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, mainBuffer.fbo); - GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, msaaBuffer.fbo); - GlStateManager._glBlitFrameBuffer( - 0, - 0, - msaaBuffer.textureWidth, - msaaBuffer.textureHeight, - 0, - 0, - msaaBuffer.textureWidth, - msaaBuffer.textureHeight, - GL30C.GL_COLOR_BUFFER_BIT, - GL30C.GL_LINEAR - ); - - msaaBuffer.beginWrite(true); - drawAction.run(); - msaaBuffer.endWrite(); - - GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, msaaBuffer.fbo); - GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, mainBuffer.fbo); - GlStateManager._glBlitFrameBuffer( - 0, - 0, - msaaBuffer.textureWidth, - msaaBuffer.textureHeight, - 0, - 0, - msaaBuffer.textureWidth, - msaaBuffer.textureHeight, - GL30C.GL_COLOR_BUFFER_BIT, - GL30C.GL_LINEAR - ); - - msaaBuffer.clear(true); - mainBuffer.beginWrite(false); - } - - @Override - public void resize(int width, int height, boolean getError) { - if (this.textureWidth != width || this.textureHeight != height) { - super.resize(width, height, getError); - } - } - - @Override - public void initFbo(int width, int height, boolean getError) { - RenderSystem.assertOnRenderThreadOrInit(); - int maxSize = RenderSystem.maxSupportedTextureSize(); - if (width <= 0 || width > maxSize || height <= 0 || height > maxSize) { - throw new IllegalArgumentException("Window " + width + "x" + height + " size out of bounds (max. size: " + maxSize + ")"); - } - - this.viewportWidth = width; - this.viewportHeight = height; - this.textureWidth = width; - this.textureHeight = height; - - this.fbo = GlStateManager.glGenFramebuffers(); - GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, this.fbo); - - this.rboColor = GlStateManager.glGenRenderbuffers(); - GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, this.rboColor); - GL30.glRenderbufferStorageMultisample(GL30C.GL_RENDERBUFFER, samples, GL30C.GL_RGBA8, width, height); - GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, 0); - - this.rboDepth = GlStateManager.glGenRenderbuffers(); - GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, this.rboDepth); - GL30.glRenderbufferStorageMultisample(GL30C.GL_RENDERBUFFER, samples, GL30C.GL_DEPTH_COMPONENT, width, height); - GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, 0); - - GL30.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL30C.GL_RENDERBUFFER, this.rboColor); - GL30.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_ATTACHMENT, GL30C.GL_RENDERBUFFER, this.rboDepth); - - this.colorAttachment = MinecraftClient.getInstance().getFramebuffer().getColorAttachment(); - this.depthAttachment = MinecraftClient.getInstance().getFramebuffer().getDepthAttachment(); - - this.checkFramebufferStatus(); - this.clear(getError); - this.endRead(); - } - - @Override - public void delete() { - RenderSystem.assertOnRenderThreadOrInit(); - this.endRead(); - this.endWrite(); - - if (this.fbo > -1) { - GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0); - GlStateManager._glDeleteFramebuffers(this.fbo); - this.fbo = -1; - } - - if (this.rboColor > -1) { - GlStateManager._glDeleteRenderbuffers(this.rboColor); - this.rboColor = -1; - } - - if (this.rboDepth > -1) { - GlStateManager._glDeleteRenderbuffers(this.rboDepth); - this.rboDepth = -1; - } - - this.colorAttachment = -1; - this.depthAttachment = -1; - this.textureWidth = -1; - this.textureHeight = -1; - } - - @Override - public void beginWrite(boolean setViewport) { - super.beginWrite(setViewport); - if (!this.inUse) { - ACTIVE_INSTANCES.add(this); - this.inUse = true; - } - } - - @Override - public void endWrite() { - super.endWrite(); - if (this.inUse) { - this.inUse = false; - ACTIVE_INSTANCES.remove(this); - } - } + /** + * The minimum amount of legal samples + */ + public static final int MIN_SAMPLES = 2; + /** + * The maximum amount of legal samples + */ + public static final int MAX_SAMPLES = GL30.glGetInteger(GL30C.GL_MAX_SAMPLES); + + private static final Map INSTANCES = new HashMap<>(); + private static final List ACTIVE_INSTANCES = new ArrayList<>(); + + private final int samples; + private int rboColor; + private int rboDepth; + private boolean inUse; + + private MSAAFramebuffer(int samples) { + super(true); + if (samples < MIN_SAMPLES || samples > MAX_SAMPLES) { + throw new IllegalArgumentException( + String.format("The number of samples should be >= %s and <= %s.", MIN_SAMPLES, MAX_SAMPLES)); + } + if ((samples & samples - 1) != 0) { + throw new IllegalArgumentException("The number of samples must be a power of two."); + } + + this.samples = samples; + this.setClearColor(1F, 1F, 1F, 0F); + } + + /** + * Checks if any instance is currently being used + * + * @return If any instance is currently being used + */ + public static boolean framebufferInUse() { + return !ACTIVE_INSTANCES.isEmpty(); + } + + /** + * Gets an instance with the sample count provided + * + * @param samples The desired sample count + * @return The framebuffer instance + */ + public static MSAAFramebuffer getInstance(int samples) { + return INSTANCES.computeIfAbsent(samples, x -> new MSAAFramebuffer(samples)); + } + + /** + *

Uses the framebuffer with the rendering calls in the action specified

+ *

Switching frame buffers is not particularly efficient. Use with caution

+ * + * @param samples The desired amount of samples to be used. While everything up to (and including) MAX_SAMPLES is accepted, anything above 16 is generally overkill. + * @param drawAction The runnable that gets executed within the framebuffer. Render your things there. + */ + public static void use(int samples, Runnable drawAction) { + use(samples, MinecraftClient.getInstance().getFramebuffer(), drawAction); + } + + /** + *

Uses the framebuffer with the rendering calls in the action specified

+ *

If you do not know what you're doing, use {@link #use(int, Runnable)} instead

+ * + * @param samples The desired amount of samples to be used + * @param mainBuffer The main framebuffer to read and write to once the buffer finishes + * @param drawAction The runnable that gets executed within the framebuffer. Render your things there. + */ + public static void use(int samples, Framebuffer mainBuffer, Runnable drawAction) { + RenderSystem.assertOnRenderThreadOrInit(); + MSAAFramebuffer msaaBuffer = MSAAFramebuffer.getInstance(samples); + msaaBuffer.resize(mainBuffer.textureWidth, mainBuffer.textureHeight, true); + + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, mainBuffer.fbo); + GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, msaaBuffer.fbo); + GlStateManager._glBlitFrameBuffer( + 0, + 0, + msaaBuffer.textureWidth, + msaaBuffer.textureHeight, + 0, + 0, + msaaBuffer.textureWidth, + msaaBuffer.textureHeight, + GL30C.GL_COLOR_BUFFER_BIT, + GL30C.GL_LINEAR + ); + + msaaBuffer.beginWrite(true); + drawAction.run(); + msaaBuffer.endWrite(); + + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, msaaBuffer.fbo); + GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, mainBuffer.fbo); + GlStateManager._glBlitFrameBuffer( + 0, + 0, + msaaBuffer.textureWidth, + msaaBuffer.textureHeight, + 0, + 0, + msaaBuffer.textureWidth, + msaaBuffer.textureHeight, + GL30C.GL_COLOR_BUFFER_BIT, + GL30C.GL_LINEAR + ); + + msaaBuffer.clear(true); + mainBuffer.beginWrite(false); + } + + @Override + public void resize(int width, int height, boolean getError) { + if (this.textureWidth != width || this.textureHeight != height) { + super.resize(width, height, getError); + } + } + + @Override + public void initFbo(int width, int height, boolean getError) { + RenderSystem.assertOnRenderThreadOrInit(); + int maxSize = RenderSystem.maxSupportedTextureSize(); + if (width <= 0 || width > maxSize || height <= 0 || height > maxSize) { + throw new IllegalArgumentException( + "Window " + width + "x" + height + " size out of bounds (max. size: " + maxSize + ")"); + } + + this.viewportWidth = width; + this.viewportHeight = height; + this.textureWidth = width; + this.textureHeight = height; + + this.fbo = GlStateManager.glGenFramebuffers(); + GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, this.fbo); + + this.rboColor = GlStateManager.glGenRenderbuffers(); + GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, this.rboColor); + GL30.glRenderbufferStorageMultisample(GL30C.GL_RENDERBUFFER, samples, GL30C.GL_RGBA8, width, height); + GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, 0); + + this.rboDepth = GlStateManager.glGenRenderbuffers(); + GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, this.rboDepth); + GL30.glRenderbufferStorageMultisample(GL30C.GL_RENDERBUFFER, samples, GL30C.GL_DEPTH_COMPONENT, width, height); + GlStateManager._glBindRenderbuffer(GL30C.GL_RENDERBUFFER, 0); + + GL30.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL30C.GL_RENDERBUFFER, + this.rboColor); + GL30.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_ATTACHMENT, GL30C.GL_RENDERBUFFER, + this.rboDepth); + + this.colorAttachment = MinecraftClient.getInstance().getFramebuffer().getColorAttachment(); + this.depthAttachment = MinecraftClient.getInstance().getFramebuffer().getDepthAttachment(); + + this.checkFramebufferStatus(); + this.clear(getError); + this.endRead(); + } + + @Override + public void delete() { + RenderSystem.assertOnRenderThreadOrInit(); + this.endRead(); + this.endWrite(); + + if (this.fbo > -1) { + GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0); + GlStateManager._glDeleteFramebuffers(this.fbo); + this.fbo = -1; + } + + if (this.rboColor > -1) { + GlStateManager._glDeleteRenderbuffers(this.rboColor); + this.rboColor = -1; + } + + if (this.rboDepth > -1) { + GlStateManager._glDeleteRenderbuffers(this.rboDepth); + this.rboDepth = -1; + } + + this.colorAttachment = -1; + this.depthAttachment = -1; + this.textureWidth = -1; + this.textureHeight = -1; + } + + @Override + public void beginWrite(boolean setViewport) { + super.beginWrite(setViewport); + if (!this.inUse) { + ACTIVE_INSTANCES.add(this); + this.inUse = true; + } + } + + @Override + public void endWrite() { + super.endWrite(); + if (this.inUse) { + this.inUse = false; + ACTIVE_INSTANCES.remove(this); + } + } } diff --git a/src/main/java/me/x150/renderer/render/OutlineFramebuffer.java b/src/main/java/me/x150/renderer/render/OutlineFramebuffer.java index b0afa43..4edcd9d 100644 --- a/src/main/java/me/x150/renderer/render/OutlineFramebuffer.java +++ b/src/main/java/me/x150/renderer/render/OutlineFramebuffer.java @@ -10,99 +10,104 @@ import net.minecraft.client.gl.Framebuffer; import org.lwjgl.opengl.GL30C; -import java.awt.Color; +import java.awt.*; +import java.util.Objects; /** * A framebuffer that draws everything in it outlined. Rendered content within this framebuffer isn't rendered as usual, but rather used as a mask. The color of the elements do not matter, but the alpha must be {@code 1} to be counted into the mask. */ public class OutlineFramebuffer extends Framebuffer { - private static OutlineFramebuffer instance; - - private OutlineFramebuffer(int width, int height) { - super(false); - RenderSystem.assertOnRenderThreadOrInit(); - this.resize(width, height, true); - this.setClearColor(0f, 0f, 0f, 0f); - } - - private static OutlineFramebuffer obtain() { - if (instance == null) { - instance = new OutlineFramebuffer(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight); - } - return instance; - } - - /** - * Draws to this framebuffer. See javadoc of this class for more information. - * - * @param r The action with rendering calls to write to this framebuffer - */ - public static void use(Runnable r) { - Framebuffer mainBuffer = MinecraftClient.getInstance().getFramebuffer(); - RenderSystem.assertOnRenderThreadOrInit(); - OutlineFramebuffer buffer = obtain(); - if (buffer.textureWidth != mainBuffer.textureWidth || buffer.textureHeight != mainBuffer.textureHeight) { - buffer.resize(mainBuffer.textureWidth, mainBuffer.textureHeight, false); - } - - GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, buffer.fbo); - - buffer.beginWrite(true); - r.run(); - buffer.endWrite(); - - GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, mainBuffer.fbo); - - mainBuffer.beginWrite(false); - } - - /** - * Processes the contents of this framebuffer, then draws them out to the main buffer. - * - * @param radius Outline radius. Performance decreases exponentially with a factor of 2, recommended to be kept at 1-4 - * @param innerColor Color of the "inner" part of an outline. May be transparent. - * @param outlineColor Color of the outline part. May be transparent. - */ - public static void draw(float radius, Color outlineColor, Color innerColor) { - Framebuffer mainBuffer = MinecraftClient.getInstance().getFramebuffer(); - OutlineFramebuffer buffer = obtain(); - - ((ShaderEffectDuck) ShaderManager.OUTLINE_SHADER.getShader()).addFakeTarget("inp", buffer); - // final buffer is written to here, including transparency - Framebuffer out = ShaderManager.OUTLINE_SHADER.getShader().getSecondaryTarget("out"); - - ShaderManager.OUTLINE_SHADER.setUniformF("Radius", radius); - ShaderManager.OUTLINE_SHADER.setUniformF("OutlineColor", outlineColor.getRed() / 255f, outlineColor.getGreen() / 255f, outlineColor.getBlue() / 255f, outlineColor.getAlpha() / 255f); - ShaderManager.OUTLINE_SHADER.setUniformF("InnerColor", innerColor.getRed() / 255f, innerColor.getGreen() / 255f, innerColor.getBlue() / 255f, innerColor.getAlpha() / 255f); - ShaderManager.OUTLINE_SHADER.setUniformF("Radius", radius); - - ShaderManager.OUTLINE_SHADER.render(MinecraftClient.getInstance().getTickDelta()); - - buffer.clear(false); - - mainBuffer.beginWrite(false); - - RenderSystem.enableBlend(); - RenderSystem.blendFuncSeparate(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA, SrcFactor.ZERO, DstFactor.ONE); - RenderSystem.backupProjectionMatrix(); - out.draw(out.textureWidth, out.textureHeight, false); - RenderSystem.restoreProjectionMatrix(); - RenderSystem.defaultBlendFunc(); - RenderSystem.disableBlend(); - } - - /** - * Uses this framebuffer, then draws it. This is equivalent to calling {@code use(r)}, followed by {@code draw(radius, outline, inner)}. - * - * @param r The action to run within this framebuffer - * @param radius Outline radius. Performance decreases exponentially with a factor of 2, recommended to be kept at 1-4 - * @param inner Color of the "inner" part of an outline. May be transparent. - * @param outline Color of the outline part. May be transparent. - * - * @see #use(Runnable) - */ - public static void useAndDraw(Runnable r, float radius, Color outline, Color inner) { - use(r); - draw(radius, outline, inner); - } + private static OutlineFramebuffer instance; + + private OutlineFramebuffer(int width, int height) { + super(false); + RenderSystem.assertOnRenderThreadOrInit(); + this.resize(width, height, true); + this.setClearColor(0f, 0f, 0f, 0f); + } + + private static OutlineFramebuffer obtain() { + if (instance == null) { + instance = new OutlineFramebuffer(MinecraftClient.getInstance().getFramebuffer().textureWidth, + MinecraftClient.getInstance().getFramebuffer().textureHeight); + } + return instance; + } + + /** + * Draws to this framebuffer. See javadoc of this class for more information. + * + * @param r The action with rendering calls to write to this framebuffer + */ + public static void use(Runnable r) { + Framebuffer mainBuffer = MinecraftClient.getInstance().getFramebuffer(); + RenderSystem.assertOnRenderThreadOrInit(); + OutlineFramebuffer buffer = obtain(); + if (buffer.textureWidth != mainBuffer.textureWidth || buffer.textureHeight != mainBuffer.textureHeight) { + buffer.resize(mainBuffer.textureWidth, mainBuffer.textureHeight, false); + } + + GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, buffer.fbo); + + buffer.beginWrite(true); + r.run(); + buffer.endWrite(); + + GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, mainBuffer.fbo); + + mainBuffer.beginWrite(false); + } + + /** + * Processes the contents of this framebuffer, then draws them out to the main buffer. + * + * @param radius Outline radius. Performance decreases exponentially with a factor of 2, recommended to be kept at 1-4 + * @param innerColor Color of the "inner" part of an outline. May be transparent. + * @param outlineColor Color of the outline part. May be transparent. + */ + public static void draw(float radius, Color outlineColor, Color innerColor) { + Framebuffer mainBuffer = MinecraftClient.getInstance().getFramebuffer(); + OutlineFramebuffer buffer = obtain(); + + ((ShaderEffectDuck) Objects.requireNonNull( + ShaderManager.OUTLINE_SHADER.getShaderEffect())).renderer$addFakeTarget("inp", buffer); + // final buffer is written to here, including transparency + Framebuffer out = ShaderManager.OUTLINE_SHADER.getShaderEffect().getSecondaryTarget("out"); + + ShaderManager.OUTLINE_SHADER.setUniformValue("Radius", radius); + ShaderManager.OUTLINE_SHADER.setUniformValue("OutlineColor", outlineColor.getRed() / 255f, + outlineColor.getGreen() / 255f, outlineColor.getBlue() / 255f, outlineColor.getAlpha() / 255f); + ShaderManager.OUTLINE_SHADER.setUniformValue("InnerColor", innerColor.getRed() / 255f, + innerColor.getGreen() / 255f, innerColor.getBlue() / 255f, innerColor.getAlpha() / 255f); + ShaderManager.OUTLINE_SHADER.setUniformValue("Radius", radius); + + ShaderManager.OUTLINE_SHADER.render(MinecraftClient.getInstance().getTickDelta()); + + buffer.clear(false); + + mainBuffer.beginWrite(false); + + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA, SrcFactor.ZERO, + DstFactor.ONE); + RenderSystem.backupProjectionMatrix(); + out.draw(out.textureWidth, out.textureHeight, false); + RenderSystem.restoreProjectionMatrix(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableBlend(); + } + + /** + * Uses this framebuffer, then draws it. This is equivalent to calling {@code use(r)}, followed by {@code draw(radius, outline, inner)}. + * + * @param r The action to run within this framebuffer + * @param radius Outline radius. Performance decreases exponentially with a factor of 2, recommended to be kept at 1-4 + * @param inner Color of the "inner" part of an outline. May be transparent. + * @param outline Color of the outline part. May be transparent. + * @see #use(Runnable) + */ + public static void useAndDraw(Runnable r, float radius, Color outline, Color inner) { + use(r); + draw(radius, outline, inner); + } } diff --git a/src/main/java/me/x150/renderer/render/Renderer2d.java b/src/main/java/me/x150/renderer/render/Renderer2d.java index e54d3e3..397dce7 100644 --- a/src/main/java/me/x150/renderer/render/Renderer2d.java +++ b/src/main/java/me/x150/renderer/render/Renderer2d.java @@ -16,7 +16,7 @@ import org.joml.Math; import org.joml.Matrix4f; -import java.awt.Color; +import java.awt.*; import static me.x150.renderer.render.Renderer3d.getColor; import static me.x150.renderer.util.RendererUtils.endRender; @@ -27,348 +27,366 @@ */ @SuppressWarnings("unused") public class Renderer2d { - /** - * Reference to the minecraft client - */ - private static final MinecraftClient client = MinecraftClient.getInstance(); - private static final float[][] roundedCache = new float[][] { new float[3], new float[3], new float[3], new float[3], }; - - static void beginScissor(double x, double y, double endX, double endY) { - double width = endX - x; - double height = endY - y; - width = Math.max(0, width); - height = Math.max(0, height); - float d = (float) client.getWindow().getScaleFactor(); - int ay = (int) ((client.getWindow().getScaledHeight() - (y + height)) * d); - RenderSystem.enableScissor((int) (x * d), ay, (int) (width * d), (int) (height * d)); - } - - static void endScissor() { - RenderSystem.disableScissor(); - } - - /** - *

Renders a texture

- *

Make sure to link your texture using {@link RenderSystem#setShaderTexture(int, Identifier)} before using this

- * - * @param matrices The context MatrixStack - * @param x0 The X coordinate - * @param y0 The Y coordinate - * @param width The width of the rendered area - * @param height The height of the rendered area - * @param u The U of the initial texture (0 for none) - * @param v The V of the initial texture (0 for none) - * @param regionWidth The UV Region width of the initial texture (can be width) - * @param regionHeight The UV Region width of the initial texture (can be height) - * @param textureWidth The texture width (can be width) - * @param textureHeight The texture height (can be height) - */ - public static void renderTexture(MatrixStack matrices, double x0, double y0, double width, double height, float u, float v, double regionWidth, double regionHeight, double textureWidth, - double textureHeight) { - double x1 = x0 + width; - double y1 = y0 + height; - double z = 0; - renderTexturedQuad( - matrices.peek().getPositionMatrix(), - x0, - x1, - y0, - y1, - z, - (u + 0.0F) / (float) textureWidth, - (u + (float) regionWidth) / (float) textureWidth, - (v + 0.0F) / (float) textureHeight, - (v + (float) regionHeight) / (float) textureHeight - ); - } - - private static void renderTexturedQuad(Matrix4f matrix, double x0, double x1, double y0, double y1, double z, float u0, float u1, float v0, float v1) { - BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - buffer.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); - buffer.vertex(matrix, (float) x0, (float) y1, (float) z).texture(u0, v1).next(); - buffer.vertex(matrix, (float) x1, (float) y1, (float) z).texture(u1, v1).next(); - buffer.vertex(matrix, (float) x1, (float) y0, (float) z).texture(u1, v0).next(); - buffer.vertex(matrix, (float) x0, (float) y0, (float) z).texture(u0, v0).next(); - - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - BufferUtils.draw(buffer); - } - - /** - *

Renders a texture

- *

Make sure to link your texture using {@link RenderSystem#setShaderTexture(int, Identifier)} before using this

- * - * @param matrices The context MatrixStack - * @param x The X coordinate - * @param y The Y coordinate - * @param width The width of the texture - * @param height The height of the texture - */ - public static void renderTexture(MatrixStack matrices, double x, double y, double width, double height) { - renderTexture(matrices, x, y, width, height, 0, 0, width, height, width, height); - } - - /** - *

Renders a texture

- *

Does the binding for you, call this instead of {@link #renderTexture(MatrixStack, double, double, double, double)} or {@link #renderTexture(MatrixStack, double, double, double, double, float, float, double, double, double, double)} for ease of use

- * - * @param matrices The context MatrixStack - * @param texture The texture to render - * @param x The X coordinate - * @param y The Y coordinate - * @param width The width of the texture - * @param height The height of the texture - */ - public static void renderTexture(MatrixStack matrices, Identifier texture, double x, double y, double width, double height) { - RenderSystem.setShaderTexture(0, texture); - renderTexture(matrices, x, y, width, height); - } - - /** - *

Renders a circle

- *

Best used inside of {@link MSAAFramebuffer#use(int, Runnable)}

- * - * @param matrices The context MatrixStack - * @param circleColor The color of the circle - * @param originX The center X coordinate - * @param originY The center Y coordinate - * @param rad The radius of the circle - * @param segments How many segments to use to render the circle (less = more performance, more = higher quality circle) - */ - public static void renderCircle(MatrixStack matrices, Color circleColor, double originX, double originY, double rad, @Range(from = 4, to = 360) int segments) { - segments = MathHelper.clamp(segments, 4, 360); - - Matrix4f matrix = matrices.peek().getPositionMatrix(); - - float[] colorFloat = getColor(circleColor); - - BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - buffer.begin(DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); - for (int i = 0; i < 360; i += Math.min(360d / segments, 360 - i)) { - double radians = Math.toRadians(i); - double sin = Math.sin(radians) * rad; - double cos = Math.cos(radians) * rad; - buffer.vertex(matrix, (float) (originX + sin), (float) (originY + cos), 0).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - } - setupRender(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - BufferUtils.draw(buffer); - endRender(); - } - - /** - * Renders a regular colored quad - * - * @param matrices The context MatrixStack - * @param color The color of the quad - * @param x1 The start X coordinate - * @param y1 The start Y coordinate - * @param x2 The end X coordinate - * @param y2 The end Y coordinate - */ - public static void renderQuad(MatrixStack matrices, Color color, double x1, double y1, double x2, double y2) { - double j; - if (x1 < x2) { - j = x1; - x1 = x2; - x2 = j; - } - - if (y1 < y2) { - j = y1; - y1 = y2; - y2 = j; - } - Matrix4f matrix = matrices.peek().getPositionMatrix(); - float[] colorFloat = getColor(color); - - BufferBuilder buffer = Tessellator.getInstance().getBuffer(); - buffer.begin(DrawMode.QUADS, VertexFormats.POSITION_COLOR); - buffer.vertex(matrix, (float) x1, (float) y2, 0.0F).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - buffer.vertex(matrix, (float) x2, (float) y2, 0.0F).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - buffer.vertex(matrix, (float) x2, (float) y1, 0.0F).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - buffer.vertex(matrix, (float) x1, (float) y1, 0.0F).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - - setupRender(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - BufferUtils.draw(buffer); - endRender(); - } - - private static void renderRoundedQuadInternal(Matrix4f matrix, float cr, float cg, float cb, float ca, float fromX, float fromY, float toX, float toY, float radC1, float radC2, float radC3, - float radC4, float samples) { - BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); - bufferBuilder.begin(DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); - - _populateRC(toX - radC4, toY - radC4, radC4, 0); - _populateRC(toX - radC2, fromY + radC2, radC2, 1); - _populateRC(fromX + radC1, fromY + radC1, radC1, 2); - _populateRC(fromX + radC3, toY - radC3, radC3, 3); - for (int i = 0; i < 4; i++) { - float[] current = roundedCache[i]; - float rad = current[2]; - for (float r = i * 90f; r <= (i + 1) * 90f; r += 90 / samples) { - float rad1 = Math.toRadians(r); - float sin = Math.sin(rad1) * rad; - float cos = Math.cos(rad1) * rad; - - bufferBuilder.vertex(matrix, current[0] + sin, current[1] + cos, 0.0F).color(cr, cg, cb, ca).next(); - } - } - BufferUtils.draw(bufferBuilder); - } - - /** - * Renders a rounded rectangle - * - * @param matrices MatrixStack - * @param c Color of the rect - * @param fromX X coordinate - * @param fromY Y coordinate - * @param toX End X coordinate - * @param toY End Y coordinate - * @param radTL Radius of the top left corner - * @param radTR Radius of the top right corner - * @param radBL Radius of the bottom left corner - * @param radBR Radius of the bottom right corner - * @param samples Samples per corner - */ - public static void renderRoundedQuad(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float radTL, float radTR, float radBL, float radBR, float samples) { - Matrix4f matrix = matrices.peek().getPositionMatrix(); - float[] color1 = getColor(c); - float r = color1[0]; - float g = color1[1]; - float b = color1[2]; - float a = color1[3]; - setupRender(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - - renderRoundedQuadInternal(matrix, r, g, b, a, (float) fromX, (float) fromY, (float) toX, (float) toY, radTL, radTR, radBL, radBR, samples); - endRender(); - } - - /** - * Renders a rounded rectangle - * - * @param stack MatrixStack - * @param c Color of the rect - * @param x X coordinate - * @param y Y coordinate - * @param x1 End X coordinate - * @param y1 End Y coordinate - * @param rad Radius of the corners - * @param samples Samples per corner - */ - public static void renderRoundedQuad(MatrixStack stack, Color c, double x, double y, double x1, double y1, float rad, float samples) { - renderRoundedQuad(stack, c, x, y, x1, y1, rad, rad, rad, rad, samples); - } - - private static void _populateRC(float a, float b, float c, int i) { - roundedCache[i][0] = a; - roundedCache[i][1] = b; - roundedCache[i][2] = c; - } - - private static void renderRoundedOutlineInternal(Matrix4f matrix, float cr, float cg, float cb, float ca, float fromX, float fromY, float toX, float toY, float radC1, float radC2, float radC3, - float radC4, float width, float samples) { - BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); - bufferBuilder.begin(DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); - - _populateRC(toX - radC4, toY - radC4, radC4, 0); - _populateRC(toX - radC2, fromY + radC2, radC2, 1); - _populateRC(fromX + radC1, fromY + radC1, radC1, 2); - _populateRC(fromX + radC3, toY - radC3, radC3, 3); - for (int i = 0; i < 4; i++) { - float[] current = roundedCache[i]; - float rad = current[2]; - for (float r = i * 90f; r <= (i + 1) * 90f; r += 90 / samples) { - float rad1 = Math.toRadians(r); - float sin1 = Math.sin(rad1); - float sin = sin1 * rad; - float cos1 = Math.cos(rad1); - float cos = cos1 * rad; - bufferBuilder.vertex(matrix, current[0] + sin, current[1] + cos, 0.0F).color(cr, cg, cb, ca).next(); - bufferBuilder.vertex(matrix, current[0] + sin + sin1 * width, current[1] + cos + cos1 * width, 0.0F).color(cr, cg, cb, ca).next(); - } - } - // last vertex connecting back to start - float[] current = roundedCache[0]; - float rad = current[2]; - bufferBuilder.vertex(matrix, current[0], current[1] + rad, 0.0F).color(cr, cg, cb, ca).next(); - bufferBuilder.vertex(matrix, current[0], current[1] + rad + width, 0.0F).color(cr, cg, cb, ca).next(); - BufferUtils.draw(bufferBuilder); - } - - /** - * Renders a round outline - * - * @param matrices MatrixStack - * @param c Color of the outline - * @param fromX From X coordinate - * @param fromY From Y coordinate - * @param toX To X coordinate - * @param toY To Y coordinate - * @param radTL Radius of the top left corner - * @param radTR Radius of the top right corner - * @param radBL Radius of the bottom left corner - * @param radBR Radius of the bottom right corner - * @param outlineWidth Width of the outline - * @param samples Amount of samples to use per corner - */ - public static void renderRoundedOutline(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float radTL, float radTR, float radBL, float radBR, float outlineWidth, - float samples) { - Matrix4f matrix = matrices.peek().getPositionMatrix(); - float[] color1 = getColor(c); - float r = color1[0]; - float g = color1[1]; - float b = color1[2]; - float a = color1[3]; - setupRender(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - - renderRoundedOutlineInternal(matrix, r, g, b, a, (float) fromX, (float) fromY, (float) toX, (float) toY, radTL, radTR, radBL, radBR, outlineWidth, samples); - endRender(); - } - - /** - * Renders a round outline - * - * @param matrices MatrixStack - * @param c Color of the outline - * @param fromX From X coordinate - * @param fromY From Y coordinate - * @param toX To X coordinate - * @param toY To Y coordinate - * @param rad Radius of the corners - * @param width Width of the outline - * @param samples Amount of samples to use per corner - */ - public static void renderRoundedOutline(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float rad, float width, float samples) { - renderRoundedOutline(matrices, c, fromX, fromY, toX, toY, rad, rad, rad, rad, width, samples); - } - - /** - * Renders a regular line between 2 points - * - * @param stack The context MatrixStack - * @param color The color of the line - * @param x The start X coordinate - * @param y The start Y coordinate - * @param x1 The end X coordinate - * @param y1 The end Y coordinate - */ - public static void renderLine(MatrixStack stack, Color color, double x, double y, double x1, double y1) { - float[] colorFloat = Colors.intArrayToFloatArray(Colors.ARGBIntToRGBA(color.getRGB())); - Matrix4f m = stack.peek().getPositionMatrix(); - - BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); - bufferBuilder.begin(DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); - bufferBuilder.vertex(m, (float) x, (float) y, 0f).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - bufferBuilder.vertex(m, (float) x1, (float) y1, 0f).color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]).next(); - - setupRender(); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - BufferUtils.draw(bufferBuilder); - endRender(); - } + /** + * Reference to the minecraft client + */ + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static final float[][] roundedCache = new float[][]{new float[3], new float[3], new float[3], new float[3],}; + + static void beginScissor(double x, double y, double endX, double endY) { + double width = endX - x; + double height = endY - y; + width = Math.max(0, width); + height = Math.max(0, height); + float d = (float) client.getWindow().getScaleFactor(); + int ay = (int) ((client.getWindow().getScaledHeight() - (y + height)) * d); + RenderSystem.enableScissor((int) (x * d), ay, (int) (width * d), (int) (height * d)); + } + + static void endScissor() { + RenderSystem.disableScissor(); + } + + /** + *

Renders a texture

+ *

Make sure to link your texture using {@link RenderSystem#setShaderTexture(int, Identifier)} before using this

+ * + * @param matrices The context MatrixStack + * @param x0 The X coordinate + * @param y0 The Y coordinate + * @param width The width of the rendered area + * @param height The height of the rendered area + * @param u The U of the initial texture (0 for none) + * @param v The V of the initial texture (0 for none) + * @param regionWidth The UV Region width of the initial texture (can be width) + * @param regionHeight The UV Region width of the initial texture (can be height) + * @param textureWidth The texture width (can be width) + * @param textureHeight The texture height (can be height) + */ + public static void renderTexture(MatrixStack matrices, double x0, double y0, double width, double height, float u, float v, double regionWidth, double regionHeight, double textureWidth, + double textureHeight) { + double x1 = x0 + width; + double y1 = y0 + height; + double z = 0; + renderTexturedQuad( + matrices.peek().getPositionMatrix(), + x0, + x1, + y0, + y1, + z, + (u + 0.0F) / (float) textureWidth, + (u + (float) regionWidth) / (float) textureWidth, + (v + 0.0F) / (float) textureHeight, + (v + (float) regionHeight) / (float) textureHeight + ); + } + + private static void renderTexturedQuad(Matrix4f matrix, double x0, double x1, double y0, double y1, double z, float u0, float u1, float v0, float v1) { + BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + buffer.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); + buffer.vertex(matrix, (float) x0, (float) y1, (float) z).texture(u0, v1).next(); + buffer.vertex(matrix, (float) x1, (float) y1, (float) z).texture(u1, v1).next(); + buffer.vertex(matrix, (float) x1, (float) y0, (float) z).texture(u1, v0).next(); + buffer.vertex(matrix, (float) x0, (float) y0, (float) z).texture(u0, v0).next(); + + RenderSystem.setShader(GameRenderer::getPositionTexProgram); + BufferUtils.draw(buffer); + } + + /** + *

Renders a texture

+ *

Make sure to link your texture using {@link RenderSystem#setShaderTexture(int, Identifier)} before using this

+ * + * @param matrices The context MatrixStack + * @param x The X coordinate + * @param y The Y coordinate + * @param width The width of the texture + * @param height The height of the texture + */ + public static void renderTexture(MatrixStack matrices, double x, double y, double width, double height) { + renderTexture(matrices, x, y, width, height, 0, 0, width, height, width, height); + } + + /** + *

Renders a texture

+ *

Does the binding for you, call this instead of {@link #renderTexture(MatrixStack, double, double, double, double)} or {@link #renderTexture(MatrixStack, double, double, double, double, float, float, double, double, double, double)} for ease of use

+ * + * @param matrices The context MatrixStack + * @param texture The texture to render + * @param x The X coordinate + * @param y The Y coordinate + * @param width The width of the texture + * @param height The height of the texture + */ + public static void renderTexture(MatrixStack matrices, Identifier texture, double x, double y, double width, double height) { + RenderSystem.setShaderTexture(0, texture); + renderTexture(matrices, x, y, width, height); + } + + /** + *

Renders a circle

+ *

Best used inside of {@link MSAAFramebuffer#use(int, Runnable)}

+ * + * @param matrices The context MatrixStack + * @param circleColor The color of the circle + * @param originX The center X coordinate + * @param originY The center Y coordinate + * @param rad The radius of the circle + * @param segments How many segments to use to render the circle (less = more performance, more = higher quality circle) + */ + public static void renderCircle(MatrixStack matrices, Color circleColor, double originX, double originY, double rad, @Range(from = 4, to = 360) int segments) { + segments = MathHelper.clamp(segments, 4, 360); + + Matrix4f matrix = matrices.peek().getPositionMatrix(); + + float[] colorFloat = getColor(circleColor); + + BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + buffer.begin(DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + for (int i = 0; i < 360; i += Math.min(360d / segments, 360 - i)) { + double radians = Math.toRadians(i); + double sin = Math.sin(radians) * rad; + double cos = Math.cos(radians) * rad; + buffer.vertex(matrix, (float) (originX + sin), (float) (originY + cos), 0) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + } + setupRender(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + BufferUtils.draw(buffer); + endRender(); + } + + /** + * Renders a regular colored quad + * + * @param matrices The context MatrixStack + * @param color The color of the quad + * @param x1 The start X coordinate + * @param y1 The start Y coordinate + * @param x2 The end X coordinate + * @param y2 The end Y coordinate + */ + public static void renderQuad(MatrixStack matrices, Color color, double x1, double y1, double x2, double y2) { + double j; + if (x1 < x2) { + j = x1; + x1 = x2; + x2 = j; + } + + if (y1 < y2) { + j = y1; + y1 = y2; + y2 = j; + } + Matrix4f matrix = matrices.peek().getPositionMatrix(); + float[] colorFloat = getColor(color); + + BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + buffer.begin(DrawMode.QUADS, VertexFormats.POSITION_COLOR); + buffer.vertex(matrix, (float) x1, (float) y2, 0.0F) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + buffer.vertex(matrix, (float) x2, (float) y2, 0.0F) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + buffer.vertex(matrix, (float) x2, (float) y1, 0.0F) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + buffer.vertex(matrix, (float) x1, (float) y1, 0.0F) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + + setupRender(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + BufferUtils.draw(buffer); + endRender(); + } + + private static void renderRoundedQuadInternal(Matrix4f matrix, float cr, float cg, float cb, float ca, float fromX, float fromY, float toX, float toY, float radC1, float radC2, float radC3, + float radC4, float samples) { + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); + bufferBuilder.begin(DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR); + + _populateRC(toX - radC4, toY - radC4, radC4, 0); + _populateRC(toX - radC2, fromY + radC2, radC2, 1); + _populateRC(fromX + radC1, fromY + radC1, radC1, 2); + _populateRC(fromX + radC3, toY - radC3, radC3, 3); + for (int i = 0; i < 4; i++) { + float[] current = roundedCache[i]; + float rad = current[2]; + for (float r = i * 90f; r <= (i + 1) * 90f; r += 90 / samples) { + float rad1 = Math.toRadians(r); + float sin = Math.sin(rad1) * rad; + float cos = Math.cos(rad1) * rad; + + bufferBuilder.vertex(matrix, current[0] + sin, current[1] + cos, 0.0F).color(cr, cg, cb, ca).next(); + } + } + BufferUtils.draw(bufferBuilder); + } + + /** + * Renders a rounded rectangle + * + * @param matrices MatrixStack + * @param c Color of the rect + * @param fromX X coordinate + * @param fromY Y coordinate + * @param toX End X coordinate + * @param toY End Y coordinate + * @param radTL Radius of the top left corner + * @param radTR Radius of the top right corner + * @param radBL Radius of the bottom left corner + * @param radBR Radius of the bottom right corner + * @param samples Samples per corner + */ + public static void renderRoundedQuad(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float radTL, float radTR, float radBL, float radBR, float samples) { + Matrix4f matrix = matrices.peek().getPositionMatrix(); + float[] color1 = getColor(c); + float r = color1[0]; + float g = color1[1]; + float b = color1[2]; + float a = color1[3]; + setupRender(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + + renderRoundedQuadInternal(matrix, r, g, b, a, (float) fromX, (float) fromY, (float) toX, (float) toY, radTL, + radTR, radBL, radBR, samples); + endRender(); + } + + /** + * Renders a rounded rectangle + * + * @param stack MatrixStack + * @param c Color of the rect + * @param x X coordinate + * @param y Y coordinate + * @param x1 End X coordinate + * @param y1 End Y coordinate + * @param rad Radius of the corners + * @param samples Samples per corner + */ + public static void renderRoundedQuad(MatrixStack stack, Color c, double x, double y, double x1, double y1, float rad, float samples) { + renderRoundedQuad(stack, c, x, y, x1, y1, rad, rad, rad, rad, samples); + } + + private static void _populateRC(float a, float b, float c, int i) { + roundedCache[i][0] = a; + roundedCache[i][1] = b; + roundedCache[i][2] = c; + } + + private static void renderRoundedOutlineInternal(Matrix4f matrix, float cr, float cg, float cb, float ca, float fromX, float fromY, float toX, float toY, float radC1, float radC2, float radC3, + float radC4, float width, float samples) { + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); + bufferBuilder.begin(DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR); + + _populateRC(toX - radC4, toY - radC4, radC4, 0); + _populateRC(toX - radC2, fromY + radC2, radC2, 1); + _populateRC(fromX + radC1, fromY + radC1, radC1, 2); + _populateRC(fromX + radC3, toY - radC3, radC3, 3); + for (int i = 0; i < 4; i++) { + float[] current = roundedCache[i]; + float rad = current[2]; + for (float r = i * 90f; r <= (i + 1) * 90f; r += 90 / samples) { + float rad1 = Math.toRadians(r); + float sin1 = Math.sin(rad1); + float sin = sin1 * rad; + float cos1 = Math.cos(rad1); + float cos = cos1 * rad; + bufferBuilder.vertex(matrix, current[0] + sin, current[1] + cos, 0.0F).color(cr, cg, cb, ca).next(); + bufferBuilder.vertex(matrix, current[0] + sin + sin1 * width, current[1] + cos + cos1 * width, 0.0F) + .color(cr, cg, cb, ca) + .next(); + } + } + // last vertex connecting back to start + float[] current = roundedCache[0]; + float rad = current[2]; + bufferBuilder.vertex(matrix, current[0], current[1] + rad, 0.0F).color(cr, cg, cb, ca).next(); + bufferBuilder.vertex(matrix, current[0], current[1] + rad + width, 0.0F).color(cr, cg, cb, ca).next(); + BufferUtils.draw(bufferBuilder); + } + + /** + * Renders a round outline + * + * @param matrices MatrixStack + * @param c Color of the outline + * @param fromX From X coordinate + * @param fromY From Y coordinate + * @param toX To X coordinate + * @param toY To Y coordinate + * @param radTL Radius of the top left corner + * @param radTR Radius of the top right corner + * @param radBL Radius of the bottom left corner + * @param radBR Radius of the bottom right corner + * @param outlineWidth Width of the outline + * @param samples Amount of samples to use per corner + */ + public static void renderRoundedOutline(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float radTL, float radTR, float radBL, float radBR, float outlineWidth, + float samples) { + Matrix4f matrix = matrices.peek().getPositionMatrix(); + float[] color1 = getColor(c); + float r = color1[0]; + float g = color1[1]; + float b = color1[2]; + float a = color1[3]; + setupRender(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + + renderRoundedOutlineInternal(matrix, r, g, b, a, (float) fromX, (float) fromY, (float) toX, (float) toY, radTL, + radTR, radBL, radBR, outlineWidth, samples); + endRender(); + } + + /** + * Renders a round outline + * + * @param matrices MatrixStack + * @param c Color of the outline + * @param fromX From X coordinate + * @param fromY From Y coordinate + * @param toX To X coordinate + * @param toY To Y coordinate + * @param rad Radius of the corners + * @param width Width of the outline + * @param samples Amount of samples to use per corner + */ + public static void renderRoundedOutline(MatrixStack matrices, Color c, double fromX, double fromY, double toX, double toY, float rad, float width, float samples) { + renderRoundedOutline(matrices, c, fromX, fromY, toX, toY, rad, rad, rad, rad, width, samples); + } + + /** + * Renders a regular line between 2 points + * + * @param stack The context MatrixStack + * @param color The color of the line + * @param x The start X coordinate + * @param y The start Y coordinate + * @param x1 The end X coordinate + * @param y1 The end Y coordinate + */ + public static void renderLine(MatrixStack stack, Color color, double x, double y, double x1, double y1) { + float[] colorFloat = Colors.intArrayToFloatArray(Colors.ARGBIntToRGBA(color.getRGB())); + Matrix4f m = stack.peek().getPositionMatrix(); + + BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); + bufferBuilder.begin(DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR); + bufferBuilder.vertex(m, (float) x, (float) y, 0f) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + bufferBuilder.vertex(m, (float) x1, (float) y1, 0f) + .color(colorFloat[0], colorFloat[1], colorFloat[2], colorFloat[3]) + .next(); + + setupRender(); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + BufferUtils.draw(bufferBuilder); + endRender(); + } } diff --git a/src/main/java/me/x150/renderer/render/Renderer3d.java b/src/main/java/me/x150/renderer/render/Renderer3d.java index 137ab71..778beae 100644 --- a/src/main/java/me/x150/renderer/render/Renderer3d.java +++ b/src/main/java/me/x150/renderer/render/Renderer3d.java @@ -5,13 +5,8 @@ import me.x150.renderer.util.BufferUtils; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.ShaderProgram; -import net.minecraft.client.render.BufferBuilder; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.client.render.Tessellator; -import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.*; import net.minecraft.client.render.VertexFormat.DrawMode; -import net.minecraft.client.render.VertexFormats; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -19,7 +14,7 @@ import org.joml.Matrix4f; import org.lwjgl.opengl.GL11; -import java.awt.Color; +import java.awt.*; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -30,386 +25,385 @@ */ @SuppressWarnings("unused") public class Renderer3d { - static final List fades = new CopyOnWriteArrayList<>(); - private static final MinecraftClient client = MinecraftClient.getInstance(); - private static boolean renderThroughWalls = false; - - /** - * Starts rendering through walls - */ - public static void renderThroughWalls() { - renderThroughWalls = true; - } - - /** - * Stops rendering through walls - */ - public static void stopRenderThroughWalls() { - renderThroughWalls = false; - } - - /** - * Returns true if the renderer is currently configured to render through walls - * - * @return True if the renderer is currently configured to render through walls - */ - public static boolean rendersThroughWalls() { - return renderThroughWalls; - } - - private static void setupRender() { - RenderSystem.enableBlend(); - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); - RenderSystem.enableDepthTest(); - RenderSystem.depthFunc(renderThroughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); - } - - private static void endRender() { - RenderSystem.enableCull(); - RenderSystem.disableBlend(); - } - - static float transformColor(float f) { - return AlphaOverride.compute(f); - } - - /** - * Renders a fading block, that gets more transparent with time - * - * @param outlineColor The color of the outline - * @param fillColor The color of the filling - * @param start Start coordinate of the block - * @param dimensions Dimensions of the block - * @param lifeTimeMs The lifetime of the block, in millis - */ - public static void renderFadingBlock(Color outlineColor, Color fillColor, Vec3d start, Vec3d dimensions, long lifeTimeMs) { - FadingBlock fb = new FadingBlock(outlineColor, fillColor, start, dimensions, System.currentTimeMillis(), lifeTimeMs); - - fades.removeIf(fadingBlock -> fadingBlock.start.equals(start) && fadingBlock.dimensions.equals(dimensions)); - fades.add(fb); - } - - /** - * Renders all fading blocks. For internal use only. You should have a good reason to call this yourself (don't). - * - * @param stack The MatrixStack - */ - @Internal - public static void renderFadingBlocks(MatrixStack stack) { - fades.removeIf(FadingBlock::isDead); - for (FadingBlock fade : fades) { - if (fade == null) { - continue; - } - long lifetimeLeft = fade.getLifeTimeLeft(); - double progress = lifetimeLeft / (double) fade.lifeTime; - progress = MathHelper.clamp(progress, 0, 1); - double ip = 1 - progress; - // stack.push(); - Color out = modifyColor(fade.outline, -1, -1, -1, (int) (fade.outline.getAlpha() * progress)); - Color fill = modifyColor(fade.fill, -1, -1, -1, (int) (fade.fill.getAlpha() * progress)); - renderEdged(stack, fill, out, fade.start.add(new Vec3d(0.2, 0.2, 0.2).multiply(ip)), fade.dimensions.subtract(new Vec3d(.4, .4, .4).multiply(ip))); - // stack.pop(); - } - } - - private static Vec3d transformVec3d(Vec3d in) { - Camera camera = client.gameRenderer.getCamera(); - Vec3d camPos = camera.getPos(); - return in.subtract(camPos); - } - - static float[] getColor(Color c) { - return new float[] { c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, transformColor(c.getAlpha() / 255f) }; - } - - private static void useBuffer(DrawMode mode, VertexFormat format, Supplier shader, Consumer runner) { - Tessellator t = Tessellator.getInstance(); - BufferBuilder bb = t.getBuffer(); - - bb.begin(mode, format); - - runner.accept(bb); - - setupRender(); - RenderSystem.setShader(shader); - BufferUtils.draw(bb); - endRender(); - } - - /** - * Renders a block outline - * - * @param stack The MatrixStack - * @param color The color of the outline - * @param start Start position of the block - * @param dimensions Dimensions of the block - */ - public static void renderOutline(MatrixStack stack, Color color, Vec3d start, Vec3d dimensions) { - Matrix4f m = stack.peek().getPositionMatrix(); - genericAABBRender( - DrawMode.DEBUG_LINES, - VertexFormats.POSITION_COLOR, - GameRenderer::getPositionColorProgram, - m, - start, - dimensions, - color, - (buffer, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, matrix) -> { - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - } - ); - } - - - /** - * Renders both a filled and outlined block - * - * @param stack The MatrixStack - * @param colorFill The color of the filling - * @param colorOutline The color of the outline - * @param start The start coordinate - * @param dimensions The dimensions - */ - public static void renderEdged(MatrixStack stack, Color colorFill, Color colorOutline, Vec3d start, Vec3d dimensions) { - Matrix4f matrix = stack.peek().getPositionMatrix(); - float[] fill = getColor(colorFill); - float[] outline = getColor(colorOutline); - - Vec3d vec3d = transformVec3d(start); - Vec3d end = vec3d.add(dimensions); - float x1 = (float) vec3d.x; - float y1 = (float) vec3d.y; - float z1 = (float) vec3d.z; - float x2 = (float) end.x; - float y2 = (float) end.y; - float z2 = (float) end.z; - float redFill = fill[0]; - float greenFill = fill[1]; - float blueFill = fill[2]; - float alphaFill = fill[3]; - float redOutline = outline[0]; - float greenOutline = outline[1]; - float blueOutline = outline[2]; - float alphaOutline = outline[3]; - useBuffer(DrawMode.QUADS, VertexFormats.POSITION_COLOR, GameRenderer::getPositionColorProgram, buffer -> { - buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - - buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - - buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - - buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - - buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - - buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); - }); - - useBuffer(DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR, GameRenderer::getPositionColorProgram, buffer -> { - buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - - buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - - buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - - buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - - buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - - buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); - }); - } - - private static void genericAABBRender(DrawMode mode, VertexFormat format, Supplier shader, Matrix4f stack, Vec3d start, Vec3d dimensions, Color color, - RenderAction action) { - float red = color.getRed() / 255f; - float green = color.getGreen() / 255f; - float blue = color.getBlue() / 255f; - float alpha = transformColor(color.getAlpha() / 255f); - // stack.push(); - Vec3d vec3d = transformVec3d(start); - Vec3d end = vec3d.add(dimensions); - float x1 = (float) vec3d.x; - float y1 = (float) vec3d.y; - float z1 = (float) vec3d.z; - float x2 = (float) end.x; - float y2 = (float) end.y; - float z2 = (float) end.z; - useBuffer(mode, format, shader, bufferBuilder -> action.run(bufferBuilder, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, stack)); - // stack.pop(); - } - - /** - * Renders a filled block - * - * @param stack The MatrixStack - * @param color The color of the filling - * @param start Start coordinates - * @param dimensions Dimensions - */ - public static void renderFilled(MatrixStack stack, Color color, Vec3d start, Vec3d dimensions) { - Matrix4f s = stack.peek().getPositionMatrix(); - genericAABBRender( - DrawMode.QUADS, - VertexFormats.POSITION_COLOR, - GameRenderer::getPositionColorProgram, - s, - start, - dimensions, - color, - (buffer, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, matrix) -> { - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); - - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); - } - ); - } - - /** - * Renders a simple line from {@code start} to {@code end} - * - * @param matrices The MatrixStack - * @param color The color of the line - * @param start The start coordinate - * @param end The end coordinate - */ - public static void renderLine(MatrixStack matrices, Color color, Vec3d start, Vec3d end) { - Matrix4f s = matrices.peek().getPositionMatrix(); - genericAABBRender( - DrawMode.DEBUG_LINES, - VertexFormats.POSITION_COLOR, - GameRenderer::getPositionColorProgram, - s, - start, - end.subtract(start), - color, - (buffer, x, y, z, x1, y1, z1, red, green, blue, alpha, matrix) -> { - buffer.vertex(matrix, x, y, z).color(red, green, blue, alpha).next(); - buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); - } - ); - } - - /** - * @param original the original color - * @param redOverwrite the new red (or -1 for original) - * @param greenOverwrite the new green (or -1 for original) - * @param blueOverwrite the new blue (or -1 for original) - * @param alphaOverwrite the new alpha (or -1 for original) - * - * @return the modified color - */ - public static Color modifyColor(Color original, int redOverwrite, int greenOverwrite, int blueOverwrite, int alphaOverwrite) { - return new Color( - redOverwrite == -1 ? original.getRed() : redOverwrite, - greenOverwrite == -1 ? original.getGreen() : greenOverwrite, - blueOverwrite == -1 ? original.getBlue() : blueOverwrite, - alphaOverwrite == -1 ? original.getAlpha() : alphaOverwrite - ); - } - - interface RenderAction { - void run(BufferBuilder buffer, float x, float y, float z, float x1, float y1, float z1, float red, float green, float blue, float alpha, Matrix4f matrix); - } - - record FadingBlock(Color outline, Color fill, Vec3d start, Vec3d dimensions, long created, long lifeTime) { - long getLifeTimeLeft() { - return Math.max(0, created - System.currentTimeMillis() + lifeTime); - } - - boolean isDead() { - return getLifeTimeLeft() == 0; - } - } + static final List fades = new CopyOnWriteArrayList<>(); + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static boolean renderThroughWalls = false; + + /** + * Starts rendering through walls + */ + public static void renderThroughWalls() { + renderThroughWalls = true; + } + + /** + * Stops rendering through walls + */ + public static void stopRenderThroughWalls() { + renderThroughWalls = false; + } + + /** + * Returns true if the renderer is currently configured to render through walls + * + * @return True if the renderer is currently configured to render through walls + */ + public static boolean rendersThroughWalls() { + return renderThroughWalls; + } + + private static void setupRender() { + RenderSystem.enableBlend(); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + RenderSystem.enableDepthTest(); + RenderSystem.depthFunc(renderThroughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); + } + + private static void endRender() { + RenderSystem.enableCull(); + RenderSystem.disableBlend(); + } + + static float transformColor(float f) { + return AlphaOverride.compute(f); + } + + /** + * Renders a fading block, that gets more transparent with time + * + * @param outlineColor The color of the outline + * @param fillColor The color of the filling + * @param start Start coordinate of the block + * @param dimensions Dimensions of the block + * @param lifeTimeMs The lifetime of the block, in millis + */ + public static void renderFadingBlock(Color outlineColor, Color fillColor, Vec3d start, Vec3d dimensions, long lifeTimeMs) { + FadingBlock fb = new FadingBlock(outlineColor, fillColor, start, dimensions, System.currentTimeMillis(), + lifeTimeMs); + + fades.removeIf(fadingBlock -> fadingBlock.start.equals(start) && fadingBlock.dimensions.equals(dimensions)); + fades.add(fb); + } + + /** + * Renders all fading blocks. For internal use only. You should have a good reason to call this yourself (don't). + * + * @param stack The MatrixStack + */ + @Internal + public static void renderFadingBlocks(MatrixStack stack) { + fades.removeIf(FadingBlock::isDead); + for (FadingBlock fade : fades) { + if (fade == null) { + continue; + } + long lifetimeLeft = fade.getLifeTimeLeft(); + double progress = lifetimeLeft / (double) fade.lifeTime; + progress = MathHelper.clamp(progress, 0, 1); + double ip = 1 - progress; + Color out = modifyColor(fade.outline, -1, -1, -1, (int) (fade.outline.getAlpha() * progress)); + Color fill = modifyColor(fade.fill, -1, -1, -1, (int) (fade.fill.getAlpha() * progress)); + renderEdged(stack, fill, out, fade.start.add(new Vec3d(0.2, 0.2, 0.2).multiply(ip)), + fade.dimensions.subtract(new Vec3d(.4, .4, .4).multiply(ip))); + } + } + + private static Vec3d transformVec3d(Vec3d in) { + Camera camera = client.gameRenderer.getCamera(); + Vec3d camPos = camera.getPos(); + return in.subtract(camPos); + } + + static float[] getColor(Color c) { + return new float[]{c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, transformColor( + c.getAlpha() / 255f)}; + } + + private static void useBuffer(DrawMode mode, VertexFormat format, Supplier shader, Consumer runner) { + Tessellator t = Tessellator.getInstance(); + BufferBuilder bb = t.getBuffer(); + + bb.begin(mode, format); + + runner.accept(bb); + + setupRender(); + RenderSystem.setShader(shader); + BufferUtils.draw(bb); + endRender(); + } + + /** + * Renders a block outline + * + * @param stack The MatrixStack + * @param color The color of the outline + * @param start Start position of the block + * @param dimensions Dimensions of the block + */ + public static void renderOutline(MatrixStack stack, Color color, Vec3d start, Vec3d dimensions) { + Matrix4f m = stack.peek().getPositionMatrix(); + genericAABBRender( + DrawMode.DEBUG_LINES, + VertexFormats.POSITION_COLOR, + GameRenderer::getPositionColorProgram, + m, + start, + dimensions, + color, + (buffer, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, matrix) -> { + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + } + ); + } + + + /** + * Renders both a filled and outlined block + * + * @param stack The MatrixStack + * @param colorFill The color of the filling + * @param colorOutline The color of the outline + * @param start The start coordinate + * @param dimensions The dimensions + */ + public static void renderEdged(MatrixStack stack, Color colorFill, Color colorOutline, Vec3d start, Vec3d dimensions) { + Matrix4f matrix = stack.peek().getPositionMatrix(); + float[] fill = getColor(colorFill); + float[] outline = getColor(colorOutline); + + Vec3d vec3d = transformVec3d(start); + Vec3d end = vec3d.add(dimensions); + float x1 = (float) vec3d.x; + float y1 = (float) vec3d.y; + float z1 = (float) vec3d.z; + float x2 = (float) end.x; + float y2 = (float) end.y; + float z2 = (float) end.z; + float redFill = fill[0]; + float greenFill = fill[1]; + float blueFill = fill[2]; + float alphaFill = fill[3]; + float redOutline = outline[0]; + float greenOutline = outline[1]; + float blueOutline = outline[2]; + float alphaOutline = outline[3]; + useBuffer(DrawMode.QUADS, VertexFormats.POSITION_COLOR, GameRenderer::getPositionColorProgram, buffer -> { + buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + + buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + + buffer.vertex(matrix, x2, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + + buffer.vertex(matrix, x2, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + + buffer.vertex(matrix, x1, y2, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y2, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + + buffer.vertex(matrix, x1, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z1).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x2, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + buffer.vertex(matrix, x1, y1, z2).color(redFill, greenFill, blueFill, alphaFill).next(); + }); + + useBuffer(DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR, GameRenderer::getPositionColorProgram, buffer -> { + buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + + buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + + buffer.vertex(matrix, x1, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + + buffer.vertex(matrix, x2, y1, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z1).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + + buffer.vertex(matrix, x2, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x2, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + + buffer.vertex(matrix, x1, y1, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + buffer.vertex(matrix, x1, y2, z2).color(redOutline, greenOutline, blueOutline, alphaOutline).next(); + }); + } + + private static void genericAABBRender(DrawMode mode, VertexFormat format, Supplier shader, Matrix4f stack, Vec3d start, Vec3d dimensions, Color color, + RenderAction action) { + float red = color.getRed() / 255f; + float green = color.getGreen() / 255f; + float blue = color.getBlue() / 255f; + float alpha = transformColor(color.getAlpha() / 255f); + Vec3d vec3d = transformVec3d(start); + Vec3d end = vec3d.add(dimensions); + float x1 = (float) vec3d.x; + float y1 = (float) vec3d.y; + float z1 = (float) vec3d.z; + float x2 = (float) end.x; + float y2 = (float) end.y; + float z2 = (float) end.z; + useBuffer(mode, format, shader, + bufferBuilder -> action.run(bufferBuilder, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, stack)); + } + + /** + * Renders a filled block + * + * @param stack The MatrixStack + * @param color The color of the filling + * @param start Start coordinates + * @param dimensions Dimensions + */ + public static void renderFilled(MatrixStack stack, Color color, Vec3d start, Vec3d dimensions) { + Matrix4f s = stack.peek().getPositionMatrix(); + genericAABBRender( + DrawMode.QUADS, + VertexFormats.POSITION_COLOR, + GameRenderer::getPositionColorProgram, + s, + start, + dimensions, + color, + (buffer, x1, y1, z1, x2, y2, z2, red, green, blue, alpha, matrix) -> { + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x2, y2, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x2, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y2, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y2, z2).color(red, green, blue, alpha).next(); + + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z1).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x2, y1, z2).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z2).color(red, green, blue, alpha).next(); + } + ); + } + + /** + * Renders a simple line from {@code start} to {@code end} + * + * @param matrices The MatrixStack + * @param color The color of the line + * @param start The start coordinate + * @param end The end coordinate + */ + public static void renderLine(MatrixStack matrices, Color color, Vec3d start, Vec3d end) { + Matrix4f s = matrices.peek().getPositionMatrix(); + genericAABBRender( + DrawMode.DEBUG_LINES, + VertexFormats.POSITION_COLOR, + GameRenderer::getPositionColorProgram, + s, + start, + end.subtract(start), + color, + (buffer, x, y, z, x1, y1, z1, red, green, blue, alpha, matrix) -> { + buffer.vertex(matrix, x, y, z).color(red, green, blue, alpha).next(); + buffer.vertex(matrix, x1, y1, z1).color(red, green, blue, alpha).next(); + } + ); + } + + /** + * @param original the original color + * @param redOverwrite the new red (or -1 for original) + * @param greenOverwrite the new green (or -1 for original) + * @param blueOverwrite the new blue (or -1 for original) + * @param alphaOverwrite the new alpha (or -1 for original) + * @return the modified color + */ + public static Color modifyColor(Color original, int redOverwrite, int greenOverwrite, int blueOverwrite, int alphaOverwrite) { + return new Color( + redOverwrite == -1 ? original.getRed() : redOverwrite, + greenOverwrite == -1 ? original.getGreen() : greenOverwrite, + blueOverwrite == -1 ? original.getBlue() : blueOverwrite, + alphaOverwrite == -1 ? original.getAlpha() : alphaOverwrite + ); + } + + interface RenderAction { + void run(BufferBuilder buffer, float x, float y, float z, float x1, float y1, float z1, float red, float green, float blue, float alpha, Matrix4f matrix); + } + + record FadingBlock(Color outline, Color fill, Vec3d start, Vec3d dimensions, long created, long lifeTime) { + long getLifeTimeLeft() { + return Math.max(0, created - System.currentTimeMillis() + lifeTime); + } + + boolean isDead() { + return getLifeTimeLeft() == 0; + } + } } diff --git a/src/main/java/me/x150/renderer/render/SVGFile.java b/src/main/java/me/x150/renderer/render/SVGFile.java index 8c28069..3cf799c 100644 --- a/src/main/java/me/x150/renderer/render/SVGFile.java +++ b/src/main/java/me/x150/renderer/render/SVGFile.java @@ -30,81 +30,82 @@ */ @SuppressWarnings("unused") public class SVGFile implements Closeable { - final String svgSource; - final int originalWidth; - final int originalHeight; - int memoizedGuiScale = -1; // default of -1 means that the svg will get redrawn the first time when render() is called, no matter what - Identifier id; + final String svgSource; + final int originalWidth; + final int originalHeight; + int memoizedGuiScale = -1; // default of -1 means that the svg will get redrawn the first time when render() is called, no matter what + Identifier id; - /** - * Creates a new SVG file. The SVG is only parsed when {@link #render(MatrixStack, double, double, float, float)} is called. - * - * @param svgSource The SVG to draw - * @param width Width of the image to render to. This is automatically adjusted, based on gui scale. - * @param height Height of the image to render to. This is automatically adjusted, based on gui scale. - */ - public SVGFile(String svgSource, int width, int height) { - this.svgSource = svgSource; - this.originalWidth = width; - this.originalHeight = height; - } + /** + * Creates a new SVG file. The SVG is only parsed when {@link #render(MatrixStack, double, double, float, float)} is called. + * + * @param svgSource The SVG to draw + * @param width Width of the image to render to. This is automatically adjusted, based on gui scale. + * @param height Height of the image to render to. This is automatically adjusted, based on gui scale. + */ + public SVGFile(String svgSource, int width, int height) { + this.svgSource = svgSource; + this.originalWidth = width; + this.originalHeight = height; + } - private void _redraw(float width, float height) { - if (this.id != null) { - close(); // destroy texture - } - this.id = RendererUtils.randomIdentifier(); - PNGTranscoder transcoder = new PNGTranscoder(); - transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, width); - transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, height); - TranscoderInput transcoderInput = new TranscoderInput(new StringReader(svgSource)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TranscoderOutput transcoderOutput = new TranscoderOutput(out); - try { - transcoder.transcode(transcoderInput, transcoderOutput); - byte[] t = out.toByteArray(); - NativeImageBackedTexture tex = new NativeImageBackedTexture(NativeImage.read(new ByteArrayInputStream(t))); - MinecraftClient.getInstance().execute(() -> MinecraftClient.getInstance().getTextureManager().registerTexture(this.id, tex)); - } catch (Throwable t) { - RendererMain.LOGGER.error("Failed to render SVG", t); - //noinspection SpellCheckingInspection - this.id = new Identifier("missingno"); // yes, this is real. this points to the "missing" texture - } - } + private void _redraw(float width, float height) { + if (this.id != null) { + close(); // destroy texture + } + this.id = RendererUtils.randomIdentifier(); + PNGTranscoder transcoder = new PNGTranscoder(); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, width); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, height); + TranscoderInput transcoderInput = new TranscoderInput(new StringReader(svgSource)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TranscoderOutput transcoderOutput = new TranscoderOutput(out); + try { + transcoder.transcode(transcoderInput, transcoderOutput); + byte[] t = out.toByteArray(); + NativeImageBackedTexture tex = new NativeImageBackedTexture(NativeImage.read(new ByteArrayInputStream(t))); + MinecraftClient.getInstance() + .execute(() -> MinecraftClient.getInstance().getTextureManager().registerTexture(this.id, tex)); + } catch (Throwable t) { + RendererMain.LOGGER.error("Failed to render SVG", t); + //noinspection SpellCheckingInspection + this.id = new Identifier("missingno"); // yes, this is real. this points to the "missing" texture + } + } - /** - * Renders this SVG onto the screen - * - * @param stack MatrixStack - * @param x X coordinate - * @param y Y coordinate - * @param renderWidth Width of the rendered texture. This should be the same as used in the constructor for best results - * @param renderHeight Height of the rendered texture. This should be the same as used in the constructor for best results - */ - public void render(MatrixStack stack, double x, double y, float renderWidth, float renderHeight) { - int guiScale = RendererUtils.getGuiScale(); - if (this.memoizedGuiScale != guiScale || this.id == null) { // need to remake the texture - this.memoizedGuiScale = guiScale; - _redraw(this.originalWidth * this.memoizedGuiScale, this.originalHeight * this.memoizedGuiScale); - } - Renderer2d.renderTexture(stack, this.id, x, y, renderWidth, renderHeight); - } + /** + * Renders this SVG onto the screen + * + * @param stack MatrixStack + * @param x X coordinate + * @param y Y coordinate + * @param renderWidth Width of the rendered texture. This should be the same as used in the constructor for best results + * @param renderHeight Height of the rendered texture. This should be the same as used in the constructor for best results + */ + public void render(MatrixStack stack, double x, double y, float renderWidth, float renderHeight) { + int guiScale = RendererUtils.getGuiScale(); + if (this.memoizedGuiScale != guiScale || this.id == null) { // need to remake the texture + this.memoizedGuiScale = guiScale; + _redraw(this.originalWidth * this.memoizedGuiScale, this.originalHeight * this.memoizedGuiScale); + } + Renderer2d.renderTexture(stack, this.id, x, y, renderWidth, renderHeight); + } - /** - * "Closes" this SVG file, freeing the cached texture, if it exists. - * - * @throws IllegalStateException When the texture is already freed - */ - @SuppressWarnings("SpellCheckingInspection") - @Override - public void close() { - if (this.id == null) { - throw new IllegalStateException("Already closed"); - } - if (this.id.getNamespace().equals("renderer")) { - // this might be minecraft's "missingno" texture, we don't want to free that. - MinecraftClient.getInstance().getTextureManager().destroyTexture(this.id); - } - this.id = null; - } + /** + * "Closes" this SVG file, freeing the cached texture, if it exists. + * + * @throws IllegalStateException When the texture is already freed + */ + @SuppressWarnings("SpellCheckingInspection") + @Override + public void close() { + if (this.id == null) { + throw new IllegalStateException("Already closed"); + } + if (this.id.getNamespace().equals("renderer")) { + // this might be minecraft's "missingno" texture, we don't want to free that. + MinecraftClient.getInstance().getTextureManager().destroyTexture(this.id); + } + this.id = null; + } } diff --git a/src/main/java/me/x150/renderer/shader/Shader.java b/src/main/java/me/x150/renderer/shader/Shader.java deleted file mode 100644 index 7ab1ca9..0000000 --- a/src/main/java/me/x150/renderer/shader/Shader.java +++ /dev/null @@ -1,71 +0,0 @@ -package me.x150.renderer.shader; - -import com.mojang.blaze3d.systems.RenderSystem; -import lombok.Getter; -import lombok.SneakyThrows; -import me.x150.renderer.mixinUtil.ShaderEffectDuck; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.gl.PostEffectPass; -import net.minecraft.client.gl.PostEffectProcessor; -import net.minecraft.util.Identifier; -import org.lwjgl.opengl.GL11; - -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; - -public class Shader { - @Getter - final PostEffectProcessor shader; - int previousWidth, previousHeight; - - @SneakyThrows - private Shader(Identifier identifier, Consumer init) { - MinecraftClient client = MinecraftClient.getInstance(); - this.shader = new PostEffectProcessor(client.getTextureManager(), client.getResourceManager(), client.getFramebuffer(), identifier); - checkUpdateDimensions(); - init.accept(this); - } - - public static Shader createPost(String id, Consumer callback) { - return new Shader(new Identifier("renderer", String.format("shaders/post/%s.json", id)), callback); - } - - void checkUpdateDimensions() { - MinecraftClient client = MinecraftClient.getInstance(); - int currentWidth = client.getWindow().getFramebufferWidth(); - int currentHeight = client.getWindow().getFramebufferHeight(); - if (previousWidth != currentWidth || previousHeight != currentHeight) { - this.shader.setupDimensions(currentWidth, currentHeight); - previousWidth = currentWidth; - previousHeight = currentHeight; - } - } - - public void setUniformF(String name, float... value) { - List passes = ((ShaderEffectDuck) shader).getPasses(); - passes.stream().map(postEffectPass -> postEffectPass.getProgram().getUniformByName(name)).filter(Objects::nonNull).forEach(glUniform -> glUniform.set(value)); - } - - public void setUniformSampler(String name, Framebuffer framebuffer) { - List passes = ((ShaderEffectDuck) shader).getPasses(); - for (PostEffectPass pass : passes) { - pass.getProgram().bindSampler(name, framebuffer::getColorAttachment); - } - } - - public void render(float delta) { - checkUpdateDimensions(); - RenderSystem.disableBlend(); - RenderSystem.disableDepthTest(); - // RenderSystem.enableTexture(); - RenderSystem.resetTextureMatrix(); - shader.render(delta); - MinecraftClient.getInstance().getFramebuffer().beginWrite(true); - RenderSystem.disableBlend(); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // restore blending - RenderSystem.enableDepthTest(); - - } -} diff --git a/src/main/java/me/x150/renderer/shader/ShaderManager.java b/src/main/java/me/x150/renderer/shader/ShaderManager.java index 9515e52..d195e5e 100644 --- a/src/main/java/me/x150/renderer/shader/ShaderManager.java +++ b/src/main/java/me/x150/renderer/shader/ShaderManager.java @@ -1,6 +1,10 @@ package me.x150.renderer.shader; +import ladysnake.satin.api.managed.ManagedShaderEffect; +import ladysnake.satin.api.managed.ShaderEffectManager; +import net.minecraft.util.Identifier; + public class ShaderManager { - public static final Shader OUTLINE_SHADER = Shader.createPost("outline", shader -> { - }); + public static final ManagedShaderEffect OUTLINE_SHADER = ShaderEffectManager.getInstance() + .manage(new Identifier("renderer", String.format("shaders/post/%s.json", "outline"))); } diff --git a/src/main/java/me/x150/renderer/util/AlphaOverride.java b/src/main/java/me/x150/renderer/util/AlphaOverride.java index 2e36791..15efad0 100644 --- a/src/main/java/me/x150/renderer/util/AlphaOverride.java +++ b/src/main/java/me/x150/renderer/util/AlphaOverride.java @@ -6,36 +6,35 @@ * An alpha override utility. Has the ability to modify the color of any element on screen, rendered by the library */ public class AlphaOverride { - private static final Stack alphaMultipliers = new Stack<>(); + private static final Stack alphaMultipliers = new Stack<>(); - /** - * Pushes an alpha multiplier (0-1) to the stack - * - * @param val The alpha multiplier - */ - public static void pushAlphaMul(float val) { - alphaMultipliers.push(val); - } + /** + * Pushes an alpha multiplier (0-1) to the stack + * + * @param val The alpha multiplier + */ + public static void pushAlphaMul(float val) { + alphaMultipliers.push(val); + } - /** - * Pops the previously added alpha multiplier from the stack - */ - public static void popAlphaMul() { - alphaMultipliers.pop(); - } + /** + * Pops the previously added alpha multiplier from the stack + */ + public static void popAlphaMul() { + alphaMultipliers.pop(); + } - /** - * Computes the new alpha from the initialAlpha, based on the current state of the stack - * - * @param initialAlpha The initial alpha (0-1) - * - * @return The new alpha - */ - public static float compute(float initialAlpha) { - float alpha = initialAlpha; - for (Float alphaMultiplier : alphaMultipliers) { - alpha *= alphaMultiplier; - } - return alpha; - } + /** + * Computes the new alpha from the initialAlpha, based on the current state of the stack + * + * @param initialAlpha The initial alpha (0-1) + * @return The new alpha + */ + public static float compute(float initialAlpha) { + float alpha = initialAlpha; + for (Float alphaMultiplier : alphaMultipliers) { + alpha *= alphaMultiplier; + } + return alpha; + } } diff --git a/src/main/java/me/x150/renderer/util/BufferUtils.java b/src/main/java/me/x150/renderer/util/BufferUtils.java index 5e885b7..fa1a64e 100644 --- a/src/main/java/me/x150/renderer/util/BufferUtils.java +++ b/src/main/java/me/x150/renderer/util/BufferUtils.java @@ -7,41 +7,40 @@ import net.minecraft.client.render.BufferRenderer; public class BufferUtils { - /** - * Draws a buffer - * - * @param builder The buffer - */ - public static void draw(BufferBuilder builder) { - BufferRenderer.drawWithGlobalProgram(builder.end()); - } + /** + * Draws a buffer + * + * @param builder The buffer + */ + public static void draw(BufferBuilder builder) { + BufferRenderer.drawWithGlobalProgram(builder.end()); + } - /** - * Creates a VBO for this buffer - * - * @param builder The buffer - * @param expectedUsage The expected usage of this vertex buffer. {@link VertexBuffer.Usage#STATIC} will upload this buffer to VRAM as soon as possible, whereas {@link VertexBuffer.Usage#DYNAMIC} will also keep it in local memory. Setting the correct flag is only a suggestion to the GL driver, nothing will happen if you use the wrong flag. The optimizations for this flag are minuscule. - * - * @return The VBO - */ - public static VertexBuffer createVbo(BuiltBuffer builder, VertexBuffer.Usage expectedUsage) { - VertexBuffer buffer = new VertexBuffer(expectedUsage); - buffer.bind(); - buffer.upload(builder); - VertexBuffer.unbind(); - return buffer; - } + /** + * Creates a VBO for this buffer + * + * @param builder The buffer + * @param expectedUsage The expected usage of this vertex buffer. {@link VertexBuffer.Usage#STATIC} will upload this buffer to VRAM as soon as possible, whereas {@link VertexBuffer.Usage#DYNAMIC} will also keep it in local memory. Setting the correct flag is only a suggestion to the GL driver, nothing will happen if you use the wrong flag. The optimizations for this flag are minuscule. + * @return The VBO + */ + public static VertexBuffer createVbo(BuiltBuffer builder, VertexBuffer.Usage expectedUsage) { + VertexBuffer buffer = new VertexBuffer(expectedUsage); + buffer.bind(); + buffer.upload(builder); + VertexBuffer.unbind(); + return buffer; + } - /** - * Uploads this buffer to a VBO - * - * @param builder The buffer to upload - * @param buffer The VBO to upload to - */ - public static void uploadToVbo(BuiltBuffer builder, VertexBuffer buffer) { - Preconditions.checkArgument(!buffer.isClosed(), "VBO is closed"); - buffer.bind(); - buffer.upload(builder); - VertexBuffer.unbind(); - } + /** + * Uploads this buffer to a VBO + * + * @param builder The buffer to upload + * @param buffer The VBO to upload to + */ + public static void uploadToVbo(BuiltBuffer builder, VertexBuffer buffer) { + Preconditions.checkArgument(!buffer.isClosed(), "VBO is closed"); + buffer.bind(); + buffer.upload(builder); + VertexBuffer.unbind(); + } } diff --git a/src/main/java/me/x150/renderer/util/Colors.java b/src/main/java/me/x150/renderer/util/Colors.java index 60f7186..c6a2070 100644 --- a/src/main/java/me/x150/renderer/util/Colors.java +++ b/src/main/java/me/x150/renderer/util/Colors.java @@ -7,102 +7,97 @@ * Helper for colors, specifically for parsing hex colors into rgb(a) components and converting rgb(a) components into hex colors */ public class Colors { - /** - *

Converts RGBA into one single integer

- *

The output color is 0xAARRGGBB formatted

- * - * @param r The red component - * @param g The green component - * @param b The blue component - * @param a The alpha component - * - * @return The integer describing the color, formatted 0xAARRGGBB - */ - public static int ARGBToInt(@Range(from = 0, to = 255) int r, @Range(from = 0, to = 255) int g, @Range(from = 0, to = 255) int b, @Range(from = 0, to = 255) int a) { - Preconditions.checkArgument(validateColorRange(r), "Expected r to be 0-255, received " + r); - Preconditions.checkArgument(validateColorRange(g), "Expected g to be 0-255, received " + g); - Preconditions.checkArgument(validateColorRange(b), "Expected b to be 0-255, received " + b); - Preconditions.checkArgument(validateColorRange(a), "Expected a to be 0-255, received " + a); - return a << 8 * 3 | r << 8 * 2 | g << 8 | b; - } + /** + *

Converts RGBA into one single integer

+ *

The output color is 0xAARRGGBB formatted

+ * + * @param r The red component + * @param g The green component + * @param b The blue component + * @param a The alpha component + * @return The integer describing the color, formatted 0xAARRGGBB + */ + public static int ARGBToInt(@Range(from = 0, to = 255) int r, @Range(from = 0, to = 255) int g, @Range(from = 0, to = 255) int b, @Range(from = 0, to = 255) int a) { + Preconditions.checkArgument(validateColorRange(r), "Expected r to be 0-255, received " + r); + Preconditions.checkArgument(validateColorRange(g), "Expected g to be 0-255, received " + g); + Preconditions.checkArgument(validateColorRange(b), "Expected b to be 0-255, received " + b); + Preconditions.checkArgument(validateColorRange(a), "Expected a to be 0-255, received " + a); + return a << 8 * 3 | r << 8 * 2 | g << 8 | b; + } - /** - *

Converts RGBA into one single integer

- *

The output color is 0xRRGGBBAA formatted

- * - * @param r The red component - * @param g The green component - * @param b The blue component - * @param a The alpha component - * - * @return The integer describing the color, formatted 0xRRGGBBAA - */ - public static int RGBAToInt(@Range(from = 0, to = 255) int r, @Range(from = 0, to = 255) int g, @Range(from = 0, to = 255) int b, @Range(from = 0, to = 255) int a) { - Preconditions.checkArgument(validateColorRange(r), "Expected r to be 0-255, received " + r); - Preconditions.checkArgument(validateColorRange(g), "Expected g to be 0-255, received " + g); - Preconditions.checkArgument(validateColorRange(b), "Expected b to be 0-255, received " + b); - Preconditions.checkArgument(validateColorRange(a), "Expected a to be 0-255, received " + a); - return r << 8 * 3 | g << 8 * 2 | b << 8 | a; - } + /** + *

Converts RGBA into one single integer

+ *

The output color is 0xRRGGBBAA formatted

+ * + * @param r The red component + * @param g The green component + * @param b The blue component + * @param a The alpha component + * @return The integer describing the color, formatted 0xRRGGBBAA + */ + public static int RGBAToInt(@Range(from = 0, to = 255) int r, @Range(from = 0, to = 255) int g, @Range(from = 0, to = 255) int b, @Range(from = 0, to = 255) int a) { + Preconditions.checkArgument(validateColorRange(r), "Expected r to be 0-255, received " + r); + Preconditions.checkArgument(validateColorRange(g), "Expected g to be 0-255, received " + g); + Preconditions.checkArgument(validateColorRange(b), "Expected b to be 0-255, received " + b); + Preconditions.checkArgument(validateColorRange(a), "Expected a to be 0-255, received " + a); + return r << 8 * 3 | g << 8 * 2 | b << 8 | a; + } - /** - *

Parses a single RGBA formatted integer into RGBA format

- * - * @param in The input color integer - * - * @return A length 4 array containing the R, G, B and A component of the color - */ - public static int[] RGBAIntToRGBA(int in) { - int red = in >> 8 * 3 & 0xFF; - int green = in >> 8 * 2 & 0xFF; - int blue = in >> 8 & 0xFF; - int alpha = in & 0xFF; - return new int[] { red, green, blue, alpha }; - } + /** + *

Parses a single RGBA formatted integer into RGBA format

+ * + * @param in The input color integer + * @return A length 4 array containing the R, G, B and A component of the color + */ + public static int[] RGBAIntToRGBA(int in) { + int red = in >> 8 * 3 & 0xFF; + int green = in >> 8 * 2 & 0xFF; + int blue = in >> 8 & 0xFF; + int alpha = in & 0xFF; + return new int[]{red, green, blue, alpha}; + } - /** - *

Parses a single ARGB formatted integer into RGBA format

- * - * @param in The input color integer - * - * @return A length 4 array containing the R, G, B and A component of the color - */ - public static int[] ARGBIntToRGBA(int in) { - int alpha = in >> 8 * 3 & 0xFF; - int red = in >> 8 * 2 & 0xFF; - int green = in >> 8 & 0xFF; - int blue = in & 0xFF; - return new int[] { red, green, blue, alpha }; - } + /** + *

Parses a single ARGB formatted integer into RGBA format

+ * + * @param in The input color integer + * @return A length 4 array containing the R, G, B and A component of the color + */ + public static int[] ARGBIntToRGBA(int in) { + int alpha = in >> 8 * 3 & 0xFF; + int red = in >> 8 * 2 & 0xFF; + int green = in >> 8 & 0xFF; + int blue = in & 0xFF; + return new int[]{red, green, blue, alpha}; + } - /** - *

Parses a single RGB formatted integer into RGB format

- * - * @param in The input color integer - * - * @return A length 3 array containing the R, G and B component of the color - */ - public static int[] RGBIntToRGB(int in) { - int red = in >> 8 * 2 & 0xFF; - int green = in >> 8 & 0xFF; - int blue = in & 0xFF; - return new int[] { red, green, blue }; - } + /** + *

Parses a single RGB formatted integer into RGB format

+ * + * @param in The input color integer + * @return A length 3 array containing the R, G and B component of the color + */ + public static int[] RGBIntToRGB(int in) { + int red = in >> 8 * 2 & 0xFF; + int green = in >> 8 & 0xFF; + int blue = in & 0xFF; + return new int[]{red, green, blue}; + } - /** - *

Converts an int[4] color array (RGBA) to a float[4] array (RGBA)

- * - * @return The float[4] array - */ - public static float[] intArrayToFloatArray(int[] in) { - Preconditions.checkArgument(in.length == 4, "Expected int[] of size 4, got " + in.length); - for (int i = 0; i < in.length; i++) { - Preconditions.checkArgument(validateColorRange(in[i]), "Expected in[" + i + "] to be 0-255, got " + in[i]); - } - return new float[] { in[0] / 255f, in[1] / 255f, in[2] / 255f, in[3] / 255f }; - } + /** + *

Converts an int[4] color array (RGBA) to a float[4] array (RGBA)

+ * + * @return The float[4] array + */ + public static float[] intArrayToFloatArray(int[] in) { + Preconditions.checkArgument(in.length == 4, "Expected int[] of size 4, got " + in.length); + for (int i = 0; i < in.length; i++) { + Preconditions.checkArgument(validateColorRange(in[i]), "Expected in[" + i + "] to be 0-255, got " + in[i]); + } + return new float[]{in[0] / 255f, in[1] / 255f, in[2] / 255f, in[3] / 255f}; + } - private static boolean validateColorRange(int in) { - return in >= 0 && in <= 255; - } + private static boolean validateColorRange(int in) { + return in >= 0 && in <= 255; + } } diff --git a/src/main/java/me/x150/renderer/util/FastMStack.java b/src/main/java/me/x150/renderer/util/FastMStack.java new file mode 100644 index 0000000..f5b0c1d --- /dev/null +++ b/src/main/java/me/x150/renderer/util/FastMStack.java @@ -0,0 +1,112 @@ +package me.x150.renderer.util; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.SneakyThrows; +import net.minecraft.client.util.math.MatrixStack; +import org.joml.Matrix3f; +import org.joml.Matrix4f; +import org.joml.Quaternionf; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * A reimplementation of {@link MatrixStack}, containing a few optimizations. + */ +public class FastMStack extends MatrixStack { + private static final MethodHandle MATRIXSTACK_ENTRY_CTOR; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(MatrixStack.Entry.class, + MethodHandles.lookup()); + MATRIXSTACK_ENTRY_CTOR = lookup.findConstructor(MatrixStack.Entry.class, + MethodType.methodType(void.class, Matrix4f.class, Matrix3f.class)); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private final ObjectArrayList fEntries = new ObjectArrayList<>(8); + private Entry top; + public FastMStack() { + fEntries.add(top = new Entry(new Matrix4f(), new Matrix3f())); + } + + @Override + public void translate(float x, float y, float z) { + top.positionMatrix.translate(x, y, z); + } + + @Override + public void scale(float x, float y, float z) { + top.positionMatrix.scale(x, y, z); + if (x == y && y == z) { + // normal matrix is normalized, if all elements are uniform, we can just scale it based on the sign of the + // elements. (positive / zero = no effect, negative = flip it) + if (x != 0) { + top.normalMatrix.scale(Math.signum(x)); + } + return; + } + float inverseX = 1.0f / x; + float inverseY = 1.0f / y; + float inverseZ = 1.0f / z; + // cbrt is faster than the pure java approximation these days + float scalar = (float) (1f / Math.cbrt(inverseX * inverseY * inverseZ)); + top.normalMatrix.scale(scalar * inverseX, scalar * inverseY, scalar * inverseZ); + } + + @Override + public void multiply(Quaternionf quaternion) { + top.positionMatrix.rotate(quaternion); + top.normalMatrix.rotate(quaternion); + } + + @Override + public void multiply(Quaternionf quaternion, float originX, float originY, float originZ) { + top.positionMatrix.rotateAround(quaternion, originX, originY, originZ); + top.normalMatrix.rotate(quaternion); + } + + @Override + public void multiplyPositionMatrix(Matrix4f matrix) { + top.positionMatrix.mul(matrix); + } + + @Override + public void push() { + fEntries.add(top = new Entry(new Matrix4f(top.positionMatrix), new Matrix3f(top.normalMatrix))); + } + + @Override + public void pop() { + if (fEntries.size() == 1) { + throw new IllegalStateException("Trying to pop an empty stack"); + } + fEntries.pop(); + top = fEntries.top(); + } + + @SneakyThrows + @Override + public MatrixStack.Entry peek() { + // ugly hack but needed to interop with the original stack api + return (MatrixStack.Entry) MATRIXSTACK_ENTRY_CTOR.invoke(top.positionMatrix, top.normalMatrix); + } + + @Override + public boolean isEmpty() { + return fEntries.size() == 1; + } + + @Override + public void loadIdentity() { + top.positionMatrix.identity(); + top.normalMatrix.identity(); + } + + record Entry(Matrix4f positionMatrix, Matrix3f normalMatrix) { + } +} diff --git a/src/main/java/me/x150/renderer/util/Rectangle.java b/src/main/java/me/x150/renderer/util/Rectangle.java index 12c54b2..5586308 100644 --- a/src/main/java/me/x150/renderer/util/Rectangle.java +++ b/src/main/java/me/x150/renderer/util/Rectangle.java @@ -8,53 +8,52 @@ * Describes a rectangle */ public class Rectangle { - @Getter - private final double x, y, x1, y1; + @Getter + private final double x, y, x1, y1; - /** - * Constructs a new rectangle. The coordinates provided will be normalized. - * - * @param minX Min X coordinate (left) - * @param minY Min Y coordinate (top) - * @param maxX Max X coordinate (right) - * @param maxY Max Y coordinate (bottom) - */ - public Rectangle(double minX, double minY, double maxX, double maxY) { - double nx0 = Math.min(minX, maxX); - double nx1 = Math.max(minX, maxX); - double ny0 = Math.min(minY, maxY); - double ny1 = Math.max(minY, maxY); - this.x = nx0; - this.y = ny0; - this.x1 = nx1; - this.y1 = ny1; - } + /** + * Constructs a new rectangle. The coordinates provided will be normalized. + * + * @param minX Min X coordinate (left) + * @param minY Min Y coordinate (top) + * @param maxX Max X coordinate (right) + * @param maxY Max Y coordinate (bottom) + */ + public Rectangle(double minX, double minY, double maxX, double maxY) { + double nx0 = Math.min(minX, maxX); + double nx1 = Math.max(minX, maxX); + double ny0 = Math.min(minY, maxY); + double ny1 = Math.max(minY, maxY); + this.x = nx0; + this.y = ny0; + this.x1 = nx1; + this.y1 = ny1; + } - /** - * Returns true if a coordinate is inside the rectangle - * - * @param x The X coordinate - * @param y The Y coordinate - * - * @return Whether the coordinates specified are inside the rectangle - */ - public boolean contains(double x, double y) { - return x >= this.x && x <= this.x1 && y >= this.y && y <= this.y1; - } + /** + * Returns true if a coordinate is inside the rectangle + * + * @param x The X coordinate + * @param y The Y coordinate + * @return Whether the coordinates specified are inside the rectangle + */ + public boolean contains(double x, double y) { + return x >= this.x && x <= this.x1 && y >= this.y && y <= this.y1; + } - @Override - public String toString() { - return String.format(Locale.ENGLISH, "%s{x=%f, y=%f, x1=%f, y1=%f}", getClass().getSimpleName(), this.x, this.y, this.x1, this.y1); - } + @Override + public String toString() { + return String.format(Locale.ENGLISH, "%s{x=%f, y=%f, x1=%f, y1=%f}", getClass().getSimpleName(), this.x, this.y, + this.x1, this.y1); + } - /** - * Checks if {@code this} rectangle overlaps with {@code that} rectangle. An overlap is defined as any point of either rectangle being within the other. - * - * @param that Rectangle to check against - * - * @return {@code true} if both rectangles overlap, {@code false} otherwise - */ - public boolean overlaps(Rectangle that) { - return this.x1 >= that.x && this.y1 >= that.y && this.x <= that.x1 && this.y <= that.y1; - } + /** + * Checks if {@code this} rectangle overlaps with {@code that} rectangle. An overlap is defined as any point of either rectangle being within the other. + * + * @param that Rectangle to check against + * @return {@code true} if both rectangles overlap, {@code false} otherwise + */ + public boolean overlaps(Rectangle that) { + return this.x1 >= that.x && this.y1 >= that.y && this.x <= that.x1 && this.y <= that.y1; + } } diff --git a/src/main/java/me/x150/renderer/util/RenderProfiler.java b/src/main/java/me/x150/renderer/util/RenderProfiler.java index 546295d..bfd8739 100644 --- a/src/main/java/me/x150/renderer/util/RenderProfiler.java +++ b/src/main/java/me/x150/renderer/util/RenderProfiler.java @@ -8,42 +8,42 @@ * A render profiler */ public class RenderProfiler { - static final Stack s = new Stack<>(); - static final Map latestTickTimes = new ConcurrentHashMap<>(); + static final Stack s = new Stack<>(); + static final Map latestTickTimes = new ConcurrentHashMap<>(); - /** - * Adds a new element to the profiler - * - * @param sec The name of the element - */ - public static void begin(String sec) { - long start = System.nanoTime(); - s.push(new Entry(sec, start, start)); - } + /** + * Adds a new element to the profiler + * + * @param sec The name of the element + */ + public static void begin(String sec) { + long start = System.nanoTime(); + s.push(new Entry(sec, start, start)); + } - /** - * Stops recording the segment currently on the stack - */ - public static void pop() { - Entry pop = s.pop(); - latestTickTimes.put(pop.name, new Entry(pop.name, pop.start, System.nanoTime())); - } + /** + * Stops recording the segment currently on the stack + */ + public static void pop() { + Entry pop = s.pop(); + latestTickTimes.put(pop.name, new Entry(pop.name, pop.start, System.nanoTime())); + } - /** - * Gets all elements recorded - * - * @return All elements recorded - */ - public static Entry[] getAllTickTimes() { - Entry[] e = new Entry[latestTickTimes.size()]; - String[] ks = latestTickTimes.keySet().toArray(String[]::new); - for (int i = 0; i < ks.length; i++) { - e[i] = latestTickTimes.get(ks[i]); - } - latestTickTimes.clear(); - return e; - } + /** + * Gets all elements recorded + * + * @return All elements recorded + */ + public static Entry[] getAllTickTimes() { + Entry[] e = new Entry[latestTickTimes.size()]; + String[] ks = latestTickTimes.keySet().toArray(String[]::new); + for (int i = 0; i < ks.length; i++) { + e[i] = latestTickTimes.get(ks[i]); + } + latestTickTimes.clear(); + return e; + } - public record Entry(String name, long start, long end) { - } + public record Entry(String name, long start, long end) { + } } diff --git a/src/main/java/me/x150/renderer/util/RendererUtils.java b/src/main/java/me/x150/renderer/util/RendererUtils.java index f61fafc..71243fc 100644 --- a/src/main/java/me/x150/renderer/util/RendererUtils.java +++ b/src/main/java/me/x150/renderer/util/RendererUtils.java @@ -10,6 +10,7 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -18,7 +19,7 @@ import org.lwjgl.opengl.GL11; import javax.imageio.ImageIO; -import java.awt.Color; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; @@ -31,273 +32,282 @@ */ @SuppressWarnings("unused") public class RendererUtils { - public static final Matrix4f lastProjMat = new Matrix4f(); - public static final Matrix4f lastModMat = new Matrix4f(); - public static final Matrix4f lastWorldSpaceMatrix = new Matrix4f(); - private static final MatrixStack empty = new MatrixStack(); - private static final MinecraftClient client = MinecraftClient.getInstance(); - private static final char RND_START = 'a'; - private static final char RND_END = 'z'; - private static final Random RND = new Random(); + @ApiStatus.Internal + public static final Matrix4f lastProjMat = new Matrix4f(); + @ApiStatus.Internal + public static final Matrix4f lastModMat = new Matrix4f(); + @ApiStatus.Internal + public static final Matrix4f lastWorldSpaceMatrix = new Matrix4f(); - /** - *

Sets up rendering and resets everything that should be reset

- */ - public static void setupRender() { - RenderSystem.disableCull(); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); - } + private static final FastMStack empty = new FastMStack(); + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static final char RND_START = 'a'; + private static final char RND_END = 'z'; + private static final Random RND = new Random(); - /** - *

Reverts everything back to normal after rendering

- */ - public static void endRender() { - RenderSystem.disableBlend(); - RenderSystem.enableCull(); - RenderSystem.depthFunc(GL11.GL_LEQUAL); - } + /** + *

Sets up rendering and resets everything that should be reset

+ */ + public static void setupRender() { + RenderSystem.disableCull(); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + } - /** - *

Linear interpolation between two integers

- * - * @param from Range from - * @param to Range to - * @param delta Range delta - * - * @return The interpolated value between from and to - */ - public static int lerp(int from, int to, double delta) { - return (int) Math.floor(from + (to - from) * MathHelper.clamp(delta, 0, 1)); - } + /** + *

Reverts everything back to normal after rendering

+ */ + public static void endRender() { + RenderSystem.disableBlend(); + RenderSystem.enableCull(); + RenderSystem.depthFunc(GL11.GL_LEQUAL); + } - /** - *

Linear interpolation between two doubles

- * - * @param from Range from - * @param to Range to - * @param delta Range delta - * - * @return The interpolated value between from and to - */ - public static double lerp(double from, double to, double delta) { - return from + (to - from) * MathHelper.clamp(delta, 0, 1); - } + /** + *

Linear interpolation between two integers

+ * + * @param from Range from + * @param to Range to + * @param delta Range delta + * @return The interpolated value between from and to + */ + public static int lerp(int from, int to, double delta) { + return (int) Math.floor(from + (to - from) * MathHelper.clamp(delta, 0, 1)); + } - /** - *

Linear interpolation between two colors

- * - * @param a Color range from - * @param b Color range to - * @param c Range delta - * - * @return The interpolated color - */ - @Contract(value = "_, _, _ -> new", pure = true) - public static Color lerp(@NonNull Color a, @NonNull Color b, double c) { - return new Color(lerp(a.getRed(), b.getRed(), c), lerp(a.getGreen(), b.getGreen(), c), lerp(a.getBlue(), b.getBlue(), c), lerp(a.getAlpha(), b.getAlpha(), c)); - } + /** + *

Linear interpolation between two doubles

+ * + * @param from Range from + * @param to Range to + * @param delta Range delta + * @return The interpolated value between from and to + */ + public static double lerp(double from, double to, double delta) { + return from + (to - from) * MathHelper.clamp(delta, 0, 1); + } - /** - *

Modifies a color

- *

Any of the components can be set to -1 to keep them from the original color

- * - * @param original The original color - * @param redOverwrite The new red component - * @param greenOverwrite The new green component - * @param blueOverwrite The new blue component - * @param alphaOverwrite The new alpha component - * - * @return The new color - */ - @Contract(value = "_, _, _, _, _ -> new", pure = true) - public static Color modify(@NonNull Color original, int redOverwrite, int greenOverwrite, int blueOverwrite, int alphaOverwrite) { - return new Color( - redOverwrite == -1 ? original.getRed() : redOverwrite, - greenOverwrite == -1 ? original.getGreen() : greenOverwrite, - blueOverwrite == -1 ? original.getBlue() : blueOverwrite, - alphaOverwrite == -1 ? original.getAlpha() : alphaOverwrite - ); - } + /** + *

Linear interpolation between two colors

+ * + * @param a Color range from + * @param b Color range to + * @param c Range delta + * @return The interpolated color + */ + @Contract(value = "_, _, _ -> new", pure = true) + public static Color lerp(@NonNull Color a, @NonNull Color b, double c) { + return new Color(lerp(a.getRed(), b.getRed(), c), lerp(a.getGreen(), b.getGreen(), c), + lerp(a.getBlue(), b.getBlue(), c), lerp(a.getAlpha(), b.getAlpha(), c)); + } - /** - *

Translates a Vec3d's position with a MatrixStack

- * - * @param stack The MatrixStack to translate with - * @param in The Vec3d to translate - * - * @return The translated Vec3d - */ - @Contract(value = "_, _ -> new", pure = true) - public static Vec3d translateVec3dWithMatrixStack(@NonNull MatrixStack stack, @NonNull Vec3d in) { - Matrix4f matrix = stack.peek().getPositionMatrix(); - Vector4f vec = new Vector4f((float) in.x, (float) in.y, (float) in.z, 1); - vec.mul(matrix); - return new Vec3d(vec.x(), vec.y(), vec.z()); - } + /** + *

Modifies a color

+ *

Any of the components can be set to -1 to keep them from the original color

+ * + * @param original The original color + * @param redOverwrite The new red component + * @param greenOverwrite The new green component + * @param blueOverwrite The new blue component + * @param alphaOverwrite The new alpha component + * @return The new color + */ + @Contract(value = "_, _, _, _, _ -> new", pure = true) + public static Color modify(@NonNull Color original, int redOverwrite, int greenOverwrite, int blueOverwrite, int alphaOverwrite) { + return new Color( + redOverwrite == -1 ? original.getRed() : redOverwrite, + greenOverwrite == -1 ? original.getGreen() : greenOverwrite, + blueOverwrite == -1 ? original.getBlue() : blueOverwrite, + alphaOverwrite == -1 ? original.getAlpha() : alphaOverwrite + ); + } - /** - *

Registers a BufferedImage as Identifier, to be used in future render calls

- *

WARNING: This will wait for the main tick thread to register the texture, keep in mind that the texture will not be available instantly

- *

WARNING 2: This will throw an exception when called when the OpenGL context is not yet made

- * - * @param i The identifier to register the texture under - * @param bi The BufferedImage holding the texture - */ - public static void registerBufferedImageTexture(@NonNull Identifier i, @NonNull BufferedImage bi) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ImageIO.write(bi, "png", out); - byte[] bytes = out.toByteArray(); + /** + *

Translates a Vec3d's position with a MatrixStack

+ * + * @param stack The MatrixStack to translate with + * @param in The Vec3d to translate + * @return The translated Vec3d + */ + @Contract(value = "_, _ -> new", pure = true) + public static Vec3d translateVec3dWithMatrixStack(@NonNull MatrixStack stack, @NonNull Vec3d in) { + Matrix4f matrix = stack.peek().getPositionMatrix(); + Vector4f vec = new Vector4f((float) in.x, (float) in.y, (float) in.z, 1); + vec.mul(matrix); + return new Vec3d(vec.x(), vec.y(), vec.z()); + } - ByteBuffer data = BufferUtils.createByteBuffer(bytes.length).put(bytes); - data.flip(); - NativeImageBackedTexture tex = new NativeImageBackedTexture(NativeImage.read(data)); - MinecraftClient.getInstance().execute(() -> MinecraftClient.getInstance().getTextureManager().registerTexture(i, tex)); - } catch (Exception e) { // should never happen, but just in case - e.printStackTrace(); - } - } + /** + *

Registers a BufferedImage as Identifier, to be used in future render calls

+ *

WARNING: This will wait for the main tick thread to register the texture, keep in mind that the texture will not be available instantly

+ *

WARNING 2: This will throw an exception when called when the OpenGL context is not yet made

+ * + * @param i The identifier to register the texture under + * @param bi The BufferedImage holding the texture + */ + public static void registerBufferedImageTexture(@NonNull Identifier i, @NonNull BufferedImage bi) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(bi, "png", out); + byte[] bytes = out.toByteArray(); - /** - * Gets an empty matrix stack without having to initialize the object - * - * @return An empty matrix stack - */ - public static MatrixStack getEmptyMatrixStack() { - empty.loadIdentity(); // essentially clear the stack - return empty; - } + ByteBuffer data = BufferUtils.createByteBuffer(bytes.length).put(bytes); + data.flip(); + NativeImageBackedTexture tex = new NativeImageBackedTexture(NativeImage.read(data)); + MinecraftClient.getInstance() + .execute(() -> MinecraftClient.getInstance().getTextureManager().registerTexture(i, tex)); + } catch (Exception e) { // should never happen, but just in case + e.printStackTrace(); + } + } - /** - * Gets the position of the crosshair of the player, transformed into world space - * - * @return The position of the crosshair of the player, transformed into world space - */ - @Contract("-> new") - public static Vec3d getCrosshairVector() { - Camera camera = client.gameRenderer.getCamera(); + /** + * Gets an empty matrix stack without having to initialize the object + * + * @return An empty matrix stack + */ + public static MatrixStack getEmptyMatrixStack() { + if (!empty.isEmpty()) { + throw new IllegalStateException( + "Supposed \"empty\" stack is not actually empty; someone does not clean up after themselves."); + } + empty.loadIdentity(); // reset top to identity, in case someone modified it + return empty; + } - float pi = (float) Math.PI; - float yawRad = (float) Math.toRadians(-camera.getYaw()); - float pitchRad = (float) Math.toRadians(-camera.getPitch()); - float f1 = MathHelper.cos(yawRad - pi); - float f2 = MathHelper.sin(yawRad - pi); - float f3 = -MathHelper.cos(pitchRad); - float f4 = MathHelper.sin(pitchRad); + /** + * Gets the position of the crosshair of the player, transformed into world space + * + * @return The position of the crosshair of the player, transformed into world space + */ + @Contract("-> new") + public static Vec3d getCrosshairVector() { + Camera camera = client.gameRenderer.getCamera(); - return new Vec3d(f2 * f3, f4, f1 * f3).add(camera.getPos()); - } + float pi = (float) Math.PI; + float yawRad = (float) Math.toRadians(-camera.getYaw()); + float pitchRad = (float) Math.toRadians(-camera.getPitch()); + float f1 = MathHelper.cos(yawRad - pi); + float f2 = MathHelper.sin(yawRad - pi); + float f3 = -MathHelper.cos(pitchRad); + float f4 = MathHelper.sin(pitchRad); - /** - * Transforms an input position into a (x, y, d) coordinate, transformed to screen space. d specifies the far plane of the position, and can be used to check if the position is on screen. Use {@link #screenSpaceCoordinateIsVisible(Vec3d)}. - * Example: - *
-     * {@code
-     * // Hud render event
-     * Vec3d targetPos = new Vec3d(100, 64, 100); // world space
-     * Vec3d screenSpace = RendererUtils.worldSpaceToScreenSpace(targetPos);
-     * if (RendererUtils.screenSpaceCoordinateIsVisible(screenSpace)) {
-     *     // do something with screenSpace.x and .y
-     * }
-     * }
-     * 
- * - * @param pos The world space coordinates to translate - * - * @return The (x, y, d) coordinates - * @throws NullPointerException If {@code pos} is null - */ - @Contract(value = "_ -> new", pure = true) - public static Vec3d worldSpaceToScreenSpace(@NonNull Vec3d pos) { - Camera camera = client.getEntityRenderDispatcher().camera; - int displayHeight = client.getWindow().getHeight(); - int[] viewport = new int[4]; - GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport); - Vector3f target = new Vector3f(); + return new Vec3d(f2 * f3, f4, f1 * f3).add(camera.getPos()); + } - double deltaX = pos.x - camera.getPos().x; - double deltaY = pos.y - camera.getPos().y; - double deltaZ = pos.z - camera.getPos().z; + /** + * Transforms an input position into a (x, y, d) coordinate, transformed to screen space. d specifies the far plane of the position, and can be used to check if the position is on screen. Use {@link #screenSpaceCoordinateIsVisible(Vec3d)}. + * Example: + *
+	 * {@code
+	 * // Hud render event
+	 * Vec3d targetPos = new Vec3d(100, 64, 100); // world space
+	 * Vec3d screenSpace = RendererUtils.worldSpaceToScreenSpace(targetPos);
+	 * if (RendererUtils.screenSpaceCoordinateIsVisible(screenSpace)) {
+	 *     // do something with screenSpace.x and .y
+	 * }
+	 * }
+	 * 
+ * + * @param pos The world space coordinates to translate + * @return The (x, y, d) coordinates + * @throws NullPointerException If {@code pos} is null + */ + @Contract(value = "_ -> new", pure = true) + public static Vec3d worldSpaceToScreenSpace(@NonNull Vec3d pos) { + Camera camera = client.getEntityRenderDispatcher().camera; + int displayHeight = client.getWindow().getHeight(); + int[] viewport = new int[4]; + GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport); + Vector3f target = new Vector3f(); - Vector4f transformedCoordinates = new Vector4f((float) deltaX, (float) deltaY, (float) deltaZ, 1.f).mul(lastWorldSpaceMatrix); + double deltaX = pos.x - camera.getPos().x; + double deltaY = pos.y - camera.getPos().y; + double deltaZ = pos.z - camera.getPos().z; - Matrix4f matrixProj = new Matrix4f(lastProjMat); - Matrix4f matrixModel = new Matrix4f(lastModMat); + Vector4f transformedCoordinates = new Vector4f((float) deltaX, (float) deltaY, (float) deltaZ, 1.f).mul( + lastWorldSpaceMatrix); - matrixProj.mul(matrixModel).project(transformedCoordinates.x(), transformedCoordinates.y(), transformedCoordinates.z(), viewport, target); + Matrix4f matrixProj = new Matrix4f(lastProjMat); + Matrix4f matrixModel = new Matrix4f(lastModMat); - return new Vec3d(target.x / client.getWindow().getScaleFactor(), (displayHeight - target.y) / client.getWindow().getScaleFactor(), target.z); - } + matrixProj.mul(matrixModel) + .project(transformedCoordinates.x(), transformedCoordinates.y(), transformedCoordinates.z(), viewport, + target); - /** - * Checks if a screen space coordinate (x, y, d) is on screen - * - * @param pos The (x, y, d) coordinates to check - * - * @return True if the coordinates are visible - */ - public static boolean screenSpaceCoordinateIsVisible(Vec3d pos) { - return pos != null && pos.z > -1 && pos.z < 1; - } + return new Vec3d(target.x / client.getWindow().getScaleFactor(), + (displayHeight - target.y) / client.getWindow().getScaleFactor(), target.z); + } - /** - * Converts a (x, y, d) screen space coordinate back into a world space coordinate. Example: - *
-     * {@code
-     * // World render event
-     * Vec3d near = RendererUtils.screenSpaceToWorldSpace(100, 100, 0);
-     * Vec3d far = RendererUtils.screenSpaceToWorldSpace(100, 100, 1);
-     * // Ray-cast from near to far to get block or entity at (100, 100) screen space
-     * }
-     * 
- * - * @param x x - * @param y y - * @param d d - * - * @return The world space coordinate - */ - @Contract(value = "_,_,_ -> new", pure = true) - public static Vec3d screenSpaceToWorldSpace(double x, double y, double d) { - Camera camera = client.getEntityRenderDispatcher().camera; - int displayHeight = client.getWindow().getScaledHeight(); - int displayWidth = client.getWindow().getScaledWidth(); - int[] viewport = new int[4]; - GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport); - Vector3f target = new Vector3f(); + /** + * Checks if a screen space coordinate (x, y, d) is on screen + * + * @param pos The (x, y, d) coordinates to check + * @return True if the coordinates are visible + */ + public static boolean screenSpaceCoordinateIsVisible(Vec3d pos) { + return pos != null && pos.z > -1 && pos.z < 1; + } - Matrix4f matrixProj = new Matrix4f(lastProjMat); - Matrix4f matrixModel = new Matrix4f(lastModMat); + /** + * Converts a (x, y, d) screen space coordinate back into a world space coordinate. Example: + *
+	 * {@code
+	 * // World render event
+	 * Vec3d near = RendererUtils.screenSpaceToWorldSpace(100, 100, 0);
+	 * Vec3d far = RendererUtils.screenSpaceToWorldSpace(100, 100, 1);
+	 * // Ray-cast from near to far to get block or entity at (100, 100) screen space
+	 * }
+	 * 
+ * + * @param x x + * @param y y + * @param d d + * @return The world space coordinate + */ + @Contract(value = "_,_,_ -> new", pure = true) + public static Vec3d screenSpaceToWorldSpace(double x, double y, double d) { + Camera camera = client.getEntityRenderDispatcher().camera; + int displayHeight = client.getWindow().getScaledHeight(); + int displayWidth = client.getWindow().getScaledWidth(); + int[] viewport = new int[4]; + GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport); + Vector3f target = new Vector3f(); - matrixProj.mul(matrixModel) - .mul(lastWorldSpaceMatrix) - .unproject((float) x / displayWidth * viewport[2], (float) (displayHeight - y) / displayHeight * viewport[3], (float) d, viewport, target); + Matrix4f matrixProj = new Matrix4f(lastProjMat); + Matrix4f matrixModel = new Matrix4f(lastModMat); - return new Vec3d(target.x, target.y, target.z).add(camera.getPos()); - } + matrixProj.mul(matrixModel) + .mul(lastWorldSpaceMatrix) + .unproject((float) x / displayWidth * viewport[2], + (float) (displayHeight - y) / displayHeight * viewport[3], (float) d, viewport, target); - /** - * Returns the GUI scale of the current window - * - * @return The GUI scale of the current window - */ - public static int getGuiScale() { - return (int) MinecraftClient.getInstance().getWindow().getScaleFactor(); - } + return new Vec3d(target.x, target.y, target.z).add(camera.getPos()); + } - private static String randomString(int length) { - return IntStream.range(0, length).mapToObj(operand -> String.valueOf((char) RND.nextInt(RND_START, RND_END + 1))).collect(Collectors.joining()); - } + /** + * Returns the GUI scale of the current window + * + * @return The GUI scale of the current window + */ + public static int getGuiScale() { + return (int) MinecraftClient.getInstance().getWindow().getScaleFactor(); + } - /** - * Returns an identifier in the renderer namespace, with a random id - * - * @return The identifier - */ - @Contract(value = "-> new", pure = true) - public static Identifier randomIdentifier() { - return new Identifier("renderer", "temp/" + randomString(32)); - } + private static String randomString(int length) { + return IntStream.range(0, length) + .mapToObj(operand -> String.valueOf((char) RND.nextInt(RND_START, RND_END + 1))) + .collect(Collectors.joining()); + } + + /** + * Returns an identifier in the renderer namespace, with a random id + * + * @return The identifier + */ + @Contract(value = "-> new", pure = true) + public static Identifier randomIdentifier() { + return new Identifier("renderer", "temp/" + randomString(32)); + } } diff --git a/src/main/resources/renderer.mixins.json b/src/main/resources/renderer.mixins.json index d52505e..3d9c9ca 100644 --- a/src/main/resources/renderer.mixins.json +++ b/src/main/resources/renderer.mixins.json @@ -9,9 +9,8 @@ "DebugHudMixin", "GameRendererMixin", "InGameHudMixin", - "JsonEffectShaderProgramMixin", - "PostProcessShaderMixin", - "ShaderEffectMixin" + "PostEffectPassAccessor", + "PostEffectProcessorMixin" ], "injectors": { "defaultRequire": 1