diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java b/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java index 23976d1cf..1b0aab569 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/VisualFrameContext.java @@ -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(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java index 755883fbf..514ceecb8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java @@ -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) { diff --git a/src/main/java/com/jozufozu/flywheel/impl/visual/VisualFrameContextImpl.java b/src/main/java/com/jozufozu/flywheel/impl/visual/VisualFrameContextImpl.java index 2fa833c12..c1a0951f9 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visual/VisualFrameContextImpl.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visual/VisualFrameContextImpl.java @@ -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 { } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java index 65bb12404..e5332277e 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -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() { diff --git a/src/main/java/com/jozufozu/flywheel/lib/instance/ShadowInstance.java b/src/main/java/com/jozufozu/flywheel/lib/instance/ShadowInstance.java index 6b1cb9338..5f642d14e 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/instance/ShadowInstance.java +++ b/src/main/java/com/jozufozu/flywheel/lib/instance/ShadowInstance.java @@ -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); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/instance/TransformedInstance.java b/src/main/java/com/jozufozu/flywheel/lib/instance/TransformedInstance.java index 66d8edd7b..6a23ba936 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/instance/TransformedInstance.java +++ b/src/main/java/com/jozufozu/flywheel/lib/instance/TransformedInstance.java @@ -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; } diff --git a/src/main/java/com/jozufozu/flywheel/lib/transform/Scale.java b/src/main/java/com/jozufozu/flywheel/lib/transform/Scale.java index a05223970..970b199ff 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/transform/Scale.java +++ b/src/main/java/com/jozufozu/flywheel/lib/transform/Scale.java @@ -6,4 +6,16 @@ public interface Scale> { 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); + } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java index 2870d4d37..e48d27bdb 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractEntityVisual.java @@ -34,12 +34,14 @@ public abstract class AbstractEntityVisual 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 extends AbstractVis @Override protected void _delete() { shadow.delete(); + fire.delete(); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/FireComponent.java b/src/main/java/com/jozufozu/flywheel/lib/visual/FireComponent.java new file mode 100644 index 000000000..df7d58d7f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/FireComponent.java @@ -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 fire0; + private final InstanceRecycler 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 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 meshes; + + private FireModel(TextureAtlasSprite sprite) { + meshes = ImmutableMap.of(MATERIAL, new FireMesh(sprite)); + } + + @Override + public Map 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); + + } + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java index 0136a44f1..61dde9ea6 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartVisual.java @@ -111,6 +111,7 @@ public class MinecartVisual extends AbstractEntityVi @Override public void beginFrame(VisualFrameContext context) { shadow.beginFrame(context); + fire.beginFrame(context); if (!isVisible(context.frustum())) { return; diff --git a/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index dc4a73532..a5cc4dce0 100644 --- a/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -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;