diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java b/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java index fc981555..a9cbbd1e 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java @@ -17,18 +17,27 @@ public class RenderUtil { new Vertex(new Vector3f(1, 1, 0), 0, 0), new Vertex(new Vector3f(1, 0, 0), 0, 1) }; + public static final Vertex[] FRAME_BUFFER_VERTICES = new Vertex[] { + new Vertex(new Vector3f(0, 1, 0), 0, 0), + new Vertex(new Vector3f(1, 1, 0), 1, 0), + new Vertex(new Vector3f(1, 0, 0), 1, 1), + new Vertex(new Vector3f(0, 0, 0), 0, 1) + }; public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light) { + renderFace(matrices, te, buffer, r, g, b, a, light, 1, 1); + } + + public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light, float uScale, float vScale) { buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT); for (Vertex vertex : UNIT_FACE) { Vector4f position = vertex.position(matrices); - buffer.vertex(position.x, position.y, position.z).texture(vertex.u(), vertex.v()).color(r, g, b, a).light(light).next(); + buffer.vertex(position.x, position.y, position.z).texture(vertex.u() * uScale, vertex.v() * vScale).color(r, g, b, a).light(light).next(); } te.draw(); } public record Vertex(Vector3f position, float u, float v) { - public Vector4f position(MatrixStack matrices) { matrices.peek().getPositionMatrix().transform(TEMP_VECTOR.set(position, 1)); return TEMP_VECTOR; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/SphereModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/SphereModel.java index 1d94ae8a..f1202b56 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/model/SphereModel.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/SphereModel.java @@ -20,7 +20,7 @@ public class SphereModel extends BakedModel { compileVertices(azimuthRange, zenithIncrement, azimuthIncrement, this::addVertex); } - private static void compileVertices(double azimuthRange, double zenithIncrement, double azimuthIncrement, Consumer collector) { + static void compileVertices(double azimuthRange, double zenithIncrement, double azimuthIncrement, Consumer collector) { Vector4f vector = new Vector4f(); for (double zenith = 0; zenith < DrawableUtil.PI; zenith += zenithIncrement) { for (double azimuth = 0; azimuth < azimuthRange; azimuth += azimuthIncrement) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/TexturedSphereModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/TexturedSphereModel.java new file mode 100644 index 00000000..60c0b74e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/TexturedSphereModel.java @@ -0,0 +1,41 @@ +package com.minelittlepony.unicopia.client.render.model; + +import org.joml.Vector4f; + +import com.minelittlepony.unicopia.client.gui.DrawableUtil; +import com.minelittlepony.unicopia.client.render.RenderUtil; + +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.util.math.MatrixStack; + +public class TexturedSphereModel extends BakedModel { + public static final TexturedSphereModel DISK = new TexturedSphereModel(40, 2, DrawableUtil.PI); + + public TexturedSphereModel(double rings, double sectors, double azimuthRange) { + double zenithIncrement = DrawableUtil.PI / rings; + double azimuthIncrement = DrawableUtil.TAU / sectors; + SphereModel.compileVertices(azimuthRange, zenithIncrement, azimuthIncrement, this::addVertex); + } + + @Override + protected void addVertex(Vector4f vertex) { + addVertex(vertex.x, vertex.y, vertex.z, vertex.x, vertex.z); + } + + public final void render(MatrixStack matrices, VertexConsumer buffer, float scale, float r, float g, float b, float a, float uScale, float vScale) { + scale = Math.abs(scale); + if (scale < 0.001F) { + return; + } + + matrices.push(); + matrices.scale(scale, scale, scale); + uScale *= 0.5F; + vScale *= 0.5F; + for (RenderUtil.Vertex vertex : vertices) { + Vector4f pos = vertex.position(matrices); + buffer.vertex(pos.x, pos.y, pos.z).texture((vertex.u() + 1) * uScale, (vertex.v() + 1) * vScale).color(r, g, b, a).next(); + } + matrices.pop(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java index c833c5fd..698375c9 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -27,6 +27,8 @@ public class PlacedSpellRenderer extends SpellRenderer { @Override public void render(MatrixStack matrices, VertexConsumerProvider vertices, PlaceableSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + + if (!(caster.asEntity() instanceof CastSpellEntity castSpell)) { return; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java index 4199c38d..aef30589 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java @@ -1,17 +1,57 @@ package com.minelittlepony.unicopia.client.render.spell; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3f; +import org.joml.Matrix4f; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.minelittlepony.common.util.Color; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.PortalSpell; import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.model.SphereModel; +import com.minelittlepony.unicopia.client.render.model.TexturedSphereModel; +import com.minelittlepony.unicopia.entity.EntityReference; +import com.minelittlepony.unicopia.entity.mob.UEntities; +import com.minelittlepony.unicopia.mixin.client.MixinMinecraftClient; +import com.mojang.blaze3d.platform.GlConst; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.systems.VertexSorter; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.SimpleFramebuffer; +import net.minecraft.client.option.Perspective; +import net.minecraft.client.render.BackgroundRenderer; +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.VertexConsumerProvider; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.util.Window; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.screen.PlayerScreenHandler; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; +import net.minecraft.util.math.Vec3d; public class PortalSpellRenderer extends SpellRenderer { + private static final LoadingCache FRAME_BUFFERS = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.SECONDS) + .removalListener(n -> n.getValue().close()) + .build(CacheLoader.from(uuid -> new PortalFrameBuffer())); @Override public boolean shouldRenderEffectPass(int pass) { @@ -32,20 +72,228 @@ public class PortalSpellRenderer extends SpellRenderer { float green = Color.g(color); float blue = Color.b(color); - VertexConsumer buffer = vertices.getBuffer(RenderLayers.getEndGateway()); - - double thickness = 0.1; + VertexConsumer buff = vertices.getBuffer(RenderLayers.getEndGateway()); matrices.push(); - matrices.translate(0, thickness, 0); - SphereModel.DISK.render(matrices, buffer, light, 0, 2.5F, red, green, blue, 1); + matrices.translate(0, 0.02, 0); + SphereModel.DISK.render(matrices, buff, light, 0, 2F, red, green, blue, 1); matrices.pop(); - matrices.push(); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); - matrices.translate(0, thickness, 0); - SphereModel.DISK.render(matrices, buffer, light, 0, 2.5F, red, green, blue, 1); + if (caster.asEntity().distanceTo(client.cameraEntity) > 50) { + return; // don't bother rendering if too far away + } - matrices.pop(); + spell.getTarget().ifPresent(target -> { + try { + float grown = Math.min(caster.asEntity().age, 20) / 20F; + matrices.push(); + matrices.translate(0, 0.01, 0); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-spell.getYaw())); + matrices.scale(grown, 1, grown); + PortalFrameBuffer buffer = FRAME_BUFFERS.get(target.uuid()); + buffer.build(spell, caster, target); + buffer.draw(matrices); + matrices.pop(); + } catch (ExecutionException e) { } + }); + } + + static class PortalFrameBuffer implements AutoCloseable { + @Nullable + private SimpleFramebuffer framebuffer; + private boolean closed; + + private final MinecraftClient client = MinecraftClient.getInstance(); + + private boolean pendingDraw; + + private static int recursionCount; + + public void draw(MatrixStack matrices) { + if (closed || framebuffer == null) { + return; + } + float uScale = (float)framebuffer.viewportWidth / (float)framebuffer.textureWidth; + float vScale = (float)framebuffer.viewportHeight / (float)framebuffer.textureHeight; + + Tessellator tessellator = RenderSystem.renderThreadTesselator(); + BufferBuilder buffer = tessellator.getBuffer(); + + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + RenderSystem.setShaderTexture(0, framebuffer.getColorAttachment()); + RenderSystem.setShader(GameRenderer::getPositionTexColorProgram); + + Matrix4f textureMatrix = matrices.peek().getPositionMatrix(); + + RenderSystem.setTextureMatrix(textureMatrix); + + buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR); + TexturedSphereModel.DISK.render(matrices, buffer, 2F, 1, 1, 1, 1, uScale, vScale); + tessellator.draw(); + client.getTextureManager().bindTexture(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE); + RenderSystem.enableCull(); + RenderSystem.resetTextureMatrix(); + } + + public void build(PortalSpell spell, Caster caster, EntityReference.EntityValues target) { + + if (System.currentTimeMillis() % 100 < 50) { + return; + } + + if (pendingDraw && recursionCount > 0) { + innerBuild(spell, caster, target); + return; + } + + if (pendingDraw) { + return; + } + pendingDraw = true; + if (recursionCount > 0) { + innerBuild(spell, caster, target); + } else { + ((MixinMinecraftClient)client).getRenderTaskQueue().add(() -> innerBuild(spell, caster, target)); + } + } + + private void innerBuild(PortalSpell spell, Caster caster, EntityReference.EntityValues target) { + synchronized (client) { + pendingDraw = false; + + if (recursionCount > 2) { + return; + } + recursionCount++; + + try { + if (closed || client.interactionManager == null) { + close(); + return; + } + + var fov = client.options.getFov(); + int originalFov = fov.getValue(); + fov.setValue(110); + + Vec3d offset = new Vec3d(0, -1.2, -0.2); + float yaw = spell.getYawDifference();// spell.getYawDifference(); + + offset = offset.rotateY(yaw * MathHelper.RADIANS_PER_DEGREE); + + Entity cameraEntity = UEntities.CAST_SPELL.create(caster.asWorld()); + cameraEntity.setPosition(target.pos().add(offset)); + cameraEntity.setPitch(spell.getTargetPitch()); + cameraEntity.setYaw(yaw); + + drawWorld(cameraEntity, 400, 400); + + fov.setValue(originalFov); + } finally { + recursionCount--; + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void drawWorld(Entity cameraEntity, int width, int height) { + Entity oldCameraEntity = client.cameraEntity; + client.cameraEntity = cameraEntity; + + Window window = client.getWindow(); + + Perspective perspective = client.options.getPerspective(); + client.options.setPerspective(Perspective.FIRST_PERSON); + + int i = window.getFramebufferWidth(); + int j = window.getFramebufferHeight(); + + width = i; + height = j; + + client.getFramebuffer().endWrite(); + + if (framebuffer == null) { + framebuffer = new SimpleFramebuffer(width, height, true, MinecraftClient.IS_SYSTEM_MAC); + } + + window.setFramebufferWidth(width); + window.setFramebufferHeight(height); + + MatrixStack view = RenderSystem.getModelViewStack(); + view.push(); + view.loadIdentity(); + RenderSystem.applyModelViewMatrix(); + Matrix4f proj = RenderSystem.getProjectionMatrix(); + Matrix3f invView = RenderSystem.getInverseViewRotationMatrix(); + + int fbo = client.getFramebuffer().fbo; + client.getFramebuffer().fbo = framebuffer.fbo; + + framebuffer.setClearColor(0, 0, 0, 0); + framebuffer.clear(MinecraftClient.IS_SYSTEM_MAC); + + RenderSystem.clear(GlConst.GL_DEPTH_BUFFER_BIT | GlConst.GL_COLOR_BUFFER_BIT, MinecraftClient.IS_SYSTEM_MAC); + framebuffer.beginWrite(true); + BackgroundRenderer.clearFog(); + + Camera camera = client.gameRenderer.getCamera(); + + ObjectArrayList chunkInfos = ((WorldRendererDuck)client.worldRenderer).unicopia_getChunkInfos(); + List backup = new ArrayList<>(chunkInfos); + + client.gameRenderer.setRenderHand(false); + MatrixStack matrices = new MatrixStack(); + + matrices.scale((float)width / height, 1, 1); + + client.gameRenderer.renderWorld(1, 0, matrices); + if (recursionCount <= 1) { + client.gameRenderer.setRenderHand(true); + } + framebuffer.endWrite(); + + chunkInfos.clear(); + chunkInfos.addAll((List)backup); + + view.pop(); + RenderSystem.applyModelViewMatrix(); + + client.getFramebuffer().fbo = fbo; + window.setFramebufferWidth(i); + window.setFramebufferHeight(j); + + client.options.setPerspective(perspective); + client.cameraEntity = oldCameraEntity; + + RenderSystem.setProjectionMatrix(proj, VertexSorter.BY_Z); + RenderSystem.setInverseViewRotationMatrix(invView); + + if (recursionCount <= 1) { + camera.update(client.world, + client.getCameraEntity() == null ? client.player : client.getCameraEntity(), + perspective.isFirstPerson(), + perspective.isFrontView(), + 1 + ); + } + + client.getFramebuffer().beginWrite(true); + } + + @Override + public void close() { + closed = true; + if (framebuffer != null) { + SimpleFramebuffer fb = framebuffer; + framebuffer = null; + fb.delete(); + } + } + } + + public interface WorldRendererDuck { + ObjectArrayList unicopia_getChunkInfos(); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinMinecraftClient.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinMinecraftClient.java new file mode 100644 index 00000000..f210faea --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinMinecraftClient.java @@ -0,0 +1,14 @@ +package com.minelittlepony.unicopia.mixin.client; + +import java.util.Queue; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.MinecraftClient; + +@Mixin(MinecraftClient.class) +public interface MixinMinecraftClient { + @Accessor("renderTaskQueue") + Queue getRenderTaskQueue(); +} diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java index edcc852d..bf5dc7b5 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinWorldRenderer.java @@ -15,7 +15,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.minelittlepony.unicopia.client.ClientBlockDestructionManager; import com.minelittlepony.unicopia.client.UnicopiaClient; +import com.minelittlepony.unicopia.client.render.spell.PortalSpellRenderer; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.client.render.BlockBreakingInfo; import net.minecraft.client.render.Camera; import net.minecraft.client.render.WorldRenderer; @@ -25,7 +28,7 @@ import net.minecraft.resource.SynchronousResourceReloader; import net.minecraft.util.math.RotationAxis; @Mixin(value = WorldRenderer.class, priority = 1001) -abstract class MixinWorldRenderer implements SynchronousResourceReloader, AutoCloseable, ClientBlockDestructionManager.Source { +abstract class MixinWorldRenderer implements SynchronousResourceReloader, AutoCloseable, ClientBlockDestructionManager.Source, PortalSpellRenderer.WorldRendererDuck { private final ClientBlockDestructionManager destructions = new ClientBlockDestructionManager(); @@ -41,6 +44,10 @@ abstract class MixinWorldRenderer implements SynchronousResourceReloader, AutoCl return destructions; } + @Override + @Accessor("chunkInfos") + public abstract ObjectArrayList unicopia_getChunkInfos(); + @Override @Accessor("ticks") public abstract int getTicks(); diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index d8654fc3..dc5da585 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -73,6 +73,7 @@ "client.MixinItemStack", "client.MixinKeyboardInput", "client.MixinLivingEntityRenderer", + "client.MixinMinecraftClient", "client.MixinModelPart", "client.MixinMouse", "client.MixinPlayerEntityRenderer",