This is fine

- Support rendering the fire animation with instances.
- Add scaleX/Y/Z methods to Scale.
- Add Camera to VisualFrameContext.
- Add camera rotation and look vectors to shader uniforms.
This commit is contained in:
Jozufozu 2024-01-27 22:27:31 -08:00
parent 0177849395
commit 9ab8559efe
11 changed files with 271 additions and 22 deletions

View file

@ -3,6 +3,8 @@ package com.jozufozu.flywheel.api.visual;
import org.jetbrains.annotations.ApiStatus;
import org.joml.FrustumIntersection;
import net.minecraft.client.Camera;
@ApiStatus.NonExtendable
public interface VisualFrameContext {
double cameraX();
@ -16,4 +18,6 @@ public interface VisualFrameContext {
float partialTick();
DistanceUpdateLimiter limiter();
Camera camera();
}

View file

@ -11,7 +11,7 @@ import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
public class FrameUniforms implements UniformProvider {
public static final int SIZE = 194;
public static final int SIZE = 228;
private RenderContext context;
@ -29,12 +29,12 @@ public class FrameUniforms implements UniformProvider {
public void write(long ptr) {
Vec3i renderOrigin = VisualizationManager.getOrThrow(context.level())
.getRenderOrigin();
Vec3 camera = context.camera()
.getPosition();
var camera = context.camera();
var camX = (float) (camera.x - renderOrigin.getX());
var camY = (float) (camera.y - renderOrigin.getY());
var camZ = (float) (camera.z - renderOrigin.getZ());
Vec3 cameraPos = camera.getPosition();
var camX = (float) (cameraPos.x - renderOrigin.getX());
var camY = (float) (cameraPos.y - renderOrigin.getY());
var camZ = (float) (cameraPos.z - renderOrigin.getZ());
viewProjection.set(context.viewProjection());
viewProjection.translate(-camX, -camY, -camZ);
@ -45,23 +45,41 @@ public class FrameUniforms implements UniformProvider {
}
MatrixMath.writeUnsafe(viewProjection, ptr + 96);
MemoryUtil.memPutFloat(ptr + 160, camX);
MemoryUtil.memPutFloat(ptr + 164, camY);
MemoryUtil.memPutFloat(ptr + 168, camZ);
MemoryUtil.memPutFloat(ptr + 172, 0f); // empty component of vec4 because we don't trust std140
MemoryUtil.memPutInt(ptr + 176, getConstantAmbientLightFlag(context));
writeVec3(ptr + 160, camX, camY, camZ);
var lookVector = camera.getLookVector();
writeVec3(ptr + 176, lookVector.x, lookVector.y, lookVector.z);
writeVec2(ptr + 192, camera.getXRot(), camera.getYRot());
MemoryUtil.memPutInt(ptr + 208, getConstantAmbientLightFlag(context));
writeTime(ptr + 212);
}
private void writeTime(long ptr) {
int ticks = context.renderer()
.getTicks();
float partialTick = context.partialTick();
float renderTicks = ticks + partialTick;
float renderSeconds = renderTicks / 20f;
MemoryUtil.memPutInt(ptr + 180, ticks);
MemoryUtil.memPutFloat(ptr + 184, partialTick);
MemoryUtil.memPutFloat(ptr + 188, renderTicks);
MemoryUtil.memPutFloat(ptr + 192, renderSeconds);
MemoryUtil.memPutInt(ptr, ticks);
MemoryUtil.memPutFloat(ptr + 4, partialTick);
MemoryUtil.memPutFloat(ptr + 8, renderTicks);
MemoryUtil.memPutFloat(ptr + 12, renderSeconds);
}
private static void writeVec3(long ptr, float camX, float camY, float camZ) {
MemoryUtil.memPutFloat(ptr, camX);
MemoryUtil.memPutFloat(ptr + 4, camY);
MemoryUtil.memPutFloat(ptr + 8, camZ);
MemoryUtil.memPutFloat(ptr + 12, 0f); // empty component of vec4 because we don't trust std140
}
private static void writeVec2(long ptr, float camX, float camY) {
MemoryUtil.memPutFloat(ptr, camX);
MemoryUtil.memPutFloat(ptr + 4, camY);
}
private static int getConstantAmbientLightFlag(RenderContext context) {

View file

@ -5,6 +5,9 @@ import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.visual.DistanceUpdateLimiter;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import net.minecraft.client.Camera;
public record VisualFrameContextImpl(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum,
float partialTick, DistanceUpdateLimiter limiter) implements VisualFrameContext {
float partialTick, DistanceUpdateLimiter limiter,
Camera camera) implements VisualFrameContext {
}

View file

@ -134,7 +134,7 @@ public class VisualizationManagerImpl implements VisualizationManager {
viewProjection.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
FrustumIntersection frustum = new FrustumIntersection(viewProjection);
return new VisualFrameContextImpl(cameraX, cameraY, cameraZ, frustum, ctx.partialTick(), frameLimiter);
return new VisualFrameContextImpl(cameraX, cameraY, cameraZ, frustum, ctx.partialTick(), frameLimiter, ctx.camera());
}
protected DistanceUpdateLimiterImpl createUpdateLimiter() {

View file

@ -11,7 +11,7 @@ public class ShadowInstance extends AbstractInstance {
public float alpha;
public float radius;
protected ShadowInstance(InstanceType<?> type, InstanceHandle handle) {
public ShadowInstance(InstanceType<?> type, InstanceHandle handle) {
super(type, handle);
}
}

View file

@ -73,10 +73,12 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
}
public TransformedInstance setTransform(PoseStack stack) {
this.model.set(stack.last()
.pose());
this.normal.set(stack.last()
.normal());
return setTransform(stack.last());
}
public TransformedInstance setTransform(PoseStack.Pose pose) {
this.model.set(pose.pose());
this.normal.set(pose.normal());
return this;
}

View file

@ -6,4 +6,16 @@ public interface Scale<Self extends Scale<Self>> {
default Self scale(float factor) {
return scale(factor, factor, factor);
}
default Self scaleX(float factor) {
return scale(factor, 1, 1);
}
default Self scaleY(float factor) {
return scale(1, factor, 1);
}
default Self scaleZ(float factor) {
return scale(1, 1, factor);
}
}

View file

@ -34,12 +34,14 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
protected final T entity;
protected final EntityVisibilityTester visibilityTester;
protected final ShadowComponent shadow;
protected final FireComponent fire;
public AbstractEntityVisual(VisualizationContext ctx, T entity) {
super(ctx, entity.level());
this.entity = entity;
visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin(), 1.5f);
shadow = new ShadowComponent(ctx, entity);
fire = new FireComponent(ctx, entity);
}
@Override
@ -93,5 +95,6 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
@Override
protected void _delete() {
shadow.delete();
fire.delete();
}
}

View file

@ -0,0 +1,204 @@
package com.jozufozu.flywheel.lib.visual;
import java.util.Map;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
import com.jozufozu.flywheel.lib.model.ModelCache;
import com.jozufozu.flywheel.lib.model.QuadMesh;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
/**
* A component that uses instances to render the fire animation on an entity.
*/
public class FireComponent {
private final VisualizationContext context;
private final Entity entity;
private final PoseStack stack = new PoseStack();
private final InstanceRecycler<TransformedInstance> fire0;
private final InstanceRecycler<TransformedInstance> fire1;
public FireComponent(VisualizationContext context, Entity entity) {
this.context = context;
this.entity = entity;
fire0 = new InstanceRecycler<>(() -> context.instancerProvider()
.instancer(InstanceTypes.TRANSFORMED, FireModel.CACHE.get(ModelBakery.FIRE_0), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance());
fire1 = new InstanceRecycler<>(() -> context.instancerProvider()
.instancer(InstanceTypes.TRANSFORMED, FireModel.CACHE.get(ModelBakery.FIRE_1), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance());
}
/**
* Update the fire instances. You'd typically call this in your visual's
* {@link com.jozufozu.flywheel.api.visual.DynamicVisual#beginFrame(VisualFrameContext) beginFrame} method.
*
* @param context The frame context.
*/
public void beginFrame(VisualFrameContext context) {
fire0.resetCount();
fire1.resetCount();
if (entity.displayFireAnimation()) {
setupInstances(context);
}
fire0.discardExtra();
fire1.discardExtra();
}
private void setupInstances(VisualFrameContext context) {
double entityX = Mth.lerp(context.partialTick(), entity.xOld, entity.getX());
double entityY = Mth.lerp(context.partialTick(), entity.yOld, entity.getY());
double entityZ = Mth.lerp(context.partialTick(), entity.zOld, entity.getZ());
var renderOrigin = this.context.renderOrigin();
final float scale = entity.getBbWidth() * 1.4F;
final float maxHeight = entity.getBbHeight() / scale;
float width = 1;
float y = 0;
float z = 0;
stack.setIdentity();
stack.translate(entityX - renderOrigin.getX(), entityY - renderOrigin.getY(), entityZ - renderOrigin.getZ());
stack.scale(scale, scale, scale);
stack.mulPose(Axis.YP.rotationDegrees(-context.camera()
.getYRot()));
stack.translate(0.0F, 0.0F, -0.3F + (float) ((int) maxHeight) * 0.02F);
for (int i = 0; y < maxHeight; ++i) {
var instance = (i % 2 == 0 ? this.fire0 : this.fire1).get()
.setTransform(stack)
.scaleX(width)
.translate(0, y, z);
if (i / 2 % 2 == 0) {
// Vanilla flips the uv directly, but it's easier for us to flip the whole model.
instance.scaleX(-1);
}
instance.setBlockLight(LightTexture.block(240));
instance.setChanged();
y += 0.45F;
// Get narrower as we go up.
width *= 0.9F;
// Offset each one so they don't z-fight.
z += 0.03F;
}
}
public void delete() {
fire0.delete();
fire1.delete();
}
private static class FireModel implements Model {
// Parameterize by the material instead of the sprite
// because Material#sprite is a surprisingly heavy operation.
public static final ModelCache<net.minecraft.client.resources.model.Material> CACHE = new ModelCache<>(mat -> new FireModel(mat.sprite()));
public static final Material MATERIAL = SimpleMaterial.builderOf(Materials.CHUNK_CUTOUT_UNSHADED)
.backfaceCulling(false) // Disable backface because we want to be able to flip the model.
.build();
private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0, 0.5f, 0, (float) (Math.sqrt(2) * 0.5));
private final ImmutableMap<Material, Mesh> meshes;
private FireModel(TextureAtlasSprite sprite) {
meshes = ImmutableMap.of(MATERIAL, new FireMesh(sprite));
}
@Override
public Map<Material, Mesh> meshes() {
return meshes;
}
@Override
public Vector4fc boundingSphere() {
return BOUNDING_SPHERE;
}
@Override
public int vertexCount() {
return 4;
}
@Override
public void delete() {
}
private record FireMesh(TextureAtlasSprite sprite) implements QuadMesh {
@Override
public int vertexCount() {
return 4;
}
@Override
public void write(MutableVertexList vertexList) {
float u0 = sprite.getU0();
float v0 = sprite.getV0();
float u1 = sprite.getU1();
float v1 = sprite.getV1();
writeVertex(vertexList, 0, 0.5f, 0, u1, v1);
writeVertex(vertexList, 1, -0.5f, 0, u0, v1);
writeVertex(vertexList, 2, -0.5f, 1.4f, u0, v0);
writeVertex(vertexList, 3, 0.5f, 1.4f, u1, v0);
}
@Override
public Vector4fc boundingSphere() {
return BOUNDING_SPHERE;
}
@Override
public void delete() {
}
// Magic numbers taken from:
// net.minecraft.client.renderer.entity.EntityRenderDispatcher#fireVertex
private static void writeVertex(MutableVertexList vertexList, int i, float x, float y, float u, float v) {
vertexList.x(i, x);
vertexList.y(i, y);
vertexList.z(i, 0);
vertexList.r(i, 1);
vertexList.g(i, 1);
vertexList.b(i, 1);
vertexList.u(i, u);
vertexList.v(i, v);
vertexList.overlay(i, OverlayTexture.NO_OVERLAY);
vertexList.light(i, 240);
vertexList.normalX(i, 0);
vertexList.normalY(i, 1);
vertexList.normalZ(i, 0);
}
}
}
}

View file

@ -111,6 +111,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
@Override
public void beginFrame(VisualFrameContext context) {
shadow.beginFrame(context);
fire.beginFrame(context);
if (!isVisible(context.frustum())) {
return;

View file

@ -13,6 +13,8 @@ layout(std140) uniform _FlwFrameUniforms {
FrustumPlanes flw_frustumPlanes;
mat4 flw_viewProjection;
vec4 flw_cameraPos;
vec4 flw_cameraLook;
vec2 flw_cameraRot;
uint flw_constantAmbientLight;
uint flw_ticks;