Add portal rendering (wip)

This commit is contained in:
Sollace 2024-01-24 19:44:57 +00:00
parent fce8623f44
commit 8785a3f5aa
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
8 changed files with 336 additions and 14 deletions

View file

@ -17,18 +17,27 @@ public class RenderUtil {
new Vertex(new Vector3f(1, 1, 0), 0, 0), new Vertex(new Vector3f(1, 1, 0), 0, 0),
new Vertex(new Vector3f(1, 0, 0), 0, 1) 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) { 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); buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT);
for (Vertex vertex : UNIT_FACE) { for (Vertex vertex : UNIT_FACE) {
Vector4f position = vertex.position(matrices); 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(); te.draw();
} }
public record Vertex(Vector3f position, float u, float v) { public record Vertex(Vector3f position, float u, float v) {
public Vector4f position(MatrixStack matrices) { public Vector4f position(MatrixStack matrices) {
matrices.peek().getPositionMatrix().transform(TEMP_VECTOR.set(position, 1)); matrices.peek().getPositionMatrix().transform(TEMP_VECTOR.set(position, 1));
return TEMP_VECTOR; return TEMP_VECTOR;

View file

@ -20,7 +20,7 @@ public class SphereModel extends BakedModel {
compileVertices(azimuthRange, zenithIncrement, azimuthIncrement, this::addVertex); compileVertices(azimuthRange, zenithIncrement, azimuthIncrement, this::addVertex);
} }
private static void compileVertices(double azimuthRange, double zenithIncrement, double azimuthIncrement, Consumer<Vector4f> collector) { static void compileVertices(double azimuthRange, double zenithIncrement, double azimuthIncrement, Consumer<Vector4f> collector) {
Vector4f vector = new Vector4f(); Vector4f vector = new Vector4f();
for (double zenith = 0; zenith < DrawableUtil.PI; zenith += zenithIncrement) { for (double zenith = 0; zenith < DrawableUtil.PI; zenith += zenithIncrement) {
for (double azimuth = 0; azimuth < azimuthRange; azimuth += azimuthIncrement) { for (double azimuth = 0; azimuth < azimuthRange; azimuth += azimuthIncrement) {

View file

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

View file

@ -27,6 +27,8 @@ public class PlacedSpellRenderer extends SpellRenderer<PlaceableSpell> {
@Override @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) { 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)) { if (!(caster.asEntity() instanceof CastSpellEntity castSpell)) {
return; return;
} }

View file

@ -1,17 +1,57 @@
package com.minelittlepony.unicopia.client.render.spell; 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.common.util.Color;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.PortalSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.PortalSpell;
import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.RenderLayers;
import com.minelittlepony.unicopia.client.render.model.SphereModel; 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.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider; 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.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.RotationAxis;
import net.minecraft.util.math.Vec3d;
public class PortalSpellRenderer extends SpellRenderer<PortalSpell> { public class PortalSpellRenderer extends SpellRenderer<PortalSpell> {
private static final LoadingCache<UUID, PortalFrameBuffer> FRAME_BUFFERS = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.<UUID, PortalFrameBuffer>removalListener(n -> n.getValue().close())
.build(CacheLoader.from(uuid -> new PortalFrameBuffer()));
@Override @Override
public boolean shouldRenderEffectPass(int pass) { public boolean shouldRenderEffectPass(int pass) {
@ -32,20 +72,228 @@ public class PortalSpellRenderer extends SpellRenderer<PortalSpell> {
float green = Color.g(color); float green = Color.g(color);
float blue = Color.b(color); float blue = Color.b(color);
VertexConsumer buffer = vertices.getBuffer(RenderLayers.getEndGateway()); VertexConsumer buff = vertices.getBuffer(RenderLayers.getEndGateway());
double thickness = 0.1;
matrices.push(); matrices.push();
matrices.translate(0, thickness, 0); matrices.translate(0, 0.02, 0);
SphereModel.DISK.render(matrices, buffer, light, 0, 2.5F, red, green, blue, 1); SphereModel.DISK.render(matrices, buff, light, 0, 2F, red, green, blue, 1);
matrices.pop(); matrices.pop();
if (caster.asEntity().distanceTo(client.cameraEntity) > 50) {
return; // don't bother rendering if too far away
}
spell.getTarget().ifPresent(target -> {
try {
float grown = Math.min(caster.asEntity().age, 20) / 20F;
matrices.push(); matrices.push();
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); matrices.translate(0, 0.01, 0);
matrices.translate(0, thickness, 0); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-spell.getYaw()));
SphereModel.DISK.render(matrices, buffer, light, 0, 2.5F, red, green, blue, 1); matrices.scale(grown, 1, grown);
PortalFrameBuffer buffer = FRAME_BUFFERS.get(target.uuid());
buffer.build(spell, caster, target);
buffer.draw(matrices);
matrices.pop(); 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<Entity> 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<Entity> 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<Object> 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();
} }
} }

View file

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

View file

@ -15,7 +15,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.minelittlepony.unicopia.client.ClientBlockDestructionManager; import com.minelittlepony.unicopia.client.ClientBlockDestructionManager;
import com.minelittlepony.unicopia.client.UnicopiaClient; 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.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.render.BlockBreakingInfo; import net.minecraft.client.render.BlockBreakingInfo;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.Camera;
import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.render.WorldRenderer;
@ -25,7 +28,7 @@ import net.minecraft.resource.SynchronousResourceReloader;
import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.RotationAxis;
@Mixin(value = WorldRenderer.class, priority = 1001) @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(); private final ClientBlockDestructionManager destructions = new ClientBlockDestructionManager();
@ -41,6 +44,10 @@ abstract class MixinWorldRenderer implements SynchronousResourceReloader, AutoCl
return destructions; return destructions;
} }
@Override
@Accessor("chunkInfos")
public abstract ObjectArrayList<?> unicopia_getChunkInfos();
@Override @Override
@Accessor("ticks") @Accessor("ticks")
public abstract int getTicks(); public abstract int getTicks();

View file

@ -73,6 +73,7 @@
"client.MixinItemStack", "client.MixinItemStack",
"client.MixinKeyboardInput", "client.MixinKeyboardInput",
"client.MixinLivingEntityRenderer", "client.MixinLivingEntityRenderer",
"client.MixinMinecraftClient",
"client.MixinModelPart", "client.MixinModelPart",
"client.MixinMouse", "client.MixinMouse",
"client.MixinPlayerEntityRenderer", "client.MixinPlayerEntityRenderer",