diff --git a/src/client/java/com/straice/smoothdoors/client/anim/SddAnimator.java b/src/client/java/com/straice/smoothdoors/client/anim/SddAnimator.java index 60de2d1..d60bb6d 100644 --- a/src/client/java/com/straice/smoothdoors/client/anim/SddAnimator.java +++ b/src/client/java/com/straice/smoothdoors/client/anim/SddAnimator.java @@ -2,302 +2,287 @@ package com.straice.smoothdoors.client.anim; import com.straice.smoothdoors.config.SddConfig; import com.straice.smoothdoors.config.SddConfigManager; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.DoorBlock; import net.minecraft.block.FenceGateBlock; import net.minecraft.block.TrapdoorBlock; -import net.minecraft.block.enums.BlockHalf; -import net.minecraft.block.enums.DoorHinge; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.OverlayTexture; -import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.render.block.BlockRenderManager; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.state.property.Properties; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; -import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; public final class SddAnimator { - private static final Long2ObjectOpenHashMap ANIMS = new Long2ObjectOpenHashMap<>(); - private SddAnimator() {} private enum Kind { DOOR, TRAPDOOR, FENCE_GATE } - private static final class Anim { - final BlockPos pos; - final long startNanos; - final long durationNanos; - final float fromDeg; - final float toDeg; - - Anim(BlockPos pos, long startNanos, long durationNanos, float fromDeg, float toDeg) { - this.pos = pos; - this.startNanos = startNanos; - this.durationNanos = durationNanos; - this.fromDeg = fromDeg; - this.toDeg = toDeg; - } - } - - public static boolean isAnimating(BlockPos pos) { - return ANIMS.containsKey(pos.asLong()); - } - - /** Called on client when a block state update happens. */ - public static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world == null) return; - - Kind kind = getKind(oldState, newState); - if (kind == null) return; - - SddConfig cfg = SddConfigManager.get(); - if (!isAnimEnabled(cfg, kind)) return; - - // Only animate OPEN changes - Boolean oldOpen = getOpenNullable(oldState); - Boolean newOpen = getOpenNullable(newState); - if (oldOpen == null || newOpen == null) return; - if (oldOpen.equals(newOpen)) return; - - float from = targetAngleDeg(kind, oldState); - float to = targetAngleDeg(kind, newState); + private static final Map ANIMS = new HashMap<>(); + private static long lastNs = -1; + private static float frameDtSeconds() { long now = System.nanoTime(); - long dur = (long) (baseDurationSeconds(kind) / getSpeed(cfg, kind) * 1_000_000_000L); - if (dur < 30_000_000L) dur = 30_000_000L; // 30ms min - - ANIMS.put(pos.asLong(), new Anim(pos.toImmutable(), now, dur, from, to)); - - // Force rerender so the chunk can "forget" the vanilla block draw (we'll hide it) - client.worldRenderer.scheduleBlockRenders(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ()); - } - - /** Used by BlockRenderManagerMixin to hide the vanilla block while animating. */ - public static boolean shouldHideInChunk(BlockPos pos, BlockState state) { - if (!isAnimating(pos)) return false; - - Kind k = getKind(state, state); - if (k == null) return false; - - SddConfig cfg = SddConfigManager.get(); - return isAnimEnabled(cfg, k); - } - - /** Render all active animations (called each frame from WorldRendererMixin). */ - public static void renderAll(Vec3d cameraPos, float tickDeltaIgnored) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world == null) { - ANIMS.clear(); - return; + if (lastNs < 0) { + lastNs = now; + return 0f; } - if (ANIMS.isEmpty()) return; + long d = now - lastNs; + lastNs = now; - VertexConsumerProvider.Immediate consumers = client.getBufferBuilders().getEntityVertexConsumers(); - BlockRenderManager brm = client.getBlockRenderManager(); - - long now = System.nanoTime(); - ArrayList done = new ArrayList<>(); - - for (var e : ANIMS.long2ObjectEntrySet()) { - long key = e.getLongKey(); - Anim anim = e.getValue(); - - BlockState state = client.world.getBlockState(anim.pos); - Kind kind = getKind(state, state); - if (kind == null) { - done.add(key); - continue; - } - - SddConfig cfg = SddConfigManager.get(); - if (!isAnimEnabled(cfg, kind)) { - done.add(key); - client.worldRenderer.scheduleBlockRenders(anim.pos.getX(), anim.pos.getY(), anim.pos.getZ(), - anim.pos.getX(), anim.pos.getY(), anim.pos.getZ()); - continue; - } - - float t = (float) (now - anim.startNanos) / (float) anim.durationNanos; - if (t >= 1.0f) { - done.add(key); - client.worldRenderer.scheduleBlockRenders(anim.pos.getX(), anim.pos.getY(), anim.pos.getZ(), - anim.pos.getX(), anim.pos.getY(), anim.pos.getZ()); - continue; - } - - float eased = smoothstep(clamp01(t)); - float angle = lerp(anim.fromDeg, anim.toDeg, eased); - - // Render the "closed" state model and rotate it ourselves - BlockState renderState = forceClosed(state); - - int light = WorldRenderer.getLightmapCoordinates(client.world, anim.pos); - - MatrixStack ms = new MatrixStack(); - ms.push(); - - ms.translate(anim.pos.getX() - cameraPos.x, anim.pos.getY() - cameraPos.y, anim.pos.getZ() - cameraPos.z); - applyTransform(ms, renderState, kind, angle); - - brm.renderBlockAsEntity(renderState, ms, consumers, light, OverlayTexture.DEFAULT_UV); - - ms.pop(); - } - - consumers.draw(); - - for (Long k : done) ANIMS.remove(k.longValue()); + float dt = d / 1_000_000_000f; + if (dt > 0.1f) dt = 0.1f; + if (dt < 0f) dt = 0f; + return dt; } - // =================== helpers =================== + private static boolean isDoor(BlockState s) { return s.getBlock() instanceof DoorBlock; } + private static boolean isTrapdoor(BlockState s) { return s.getBlock() instanceof TrapdoorBlock; } + private static boolean isFenceGate(BlockState s) { return s.getBlock() instanceof FenceGateBlock; } - private static float clamp01(float v) { return v < 0 ? 0 : (v > 1 ? 1 : v); } - private static float lerp(float a, float b, float t) { return a + (b - a) * t; } - private static float smoothstep(float t) { return t * t * (3f - 2f * t); } + private static Kind kindOf(BlockState s) { + if (isDoor(s)) return Kind.DOOR; + if (isTrapdoor(s)) return Kind.TRAPDOOR; + if (isFenceGate(s)) return Kind.FENCE_GATE; + return null; + } - private static boolean isAnimEnabled(SddConfig cfg, Kind k) { - return switch (k) { + private static boolean getOpen(BlockState s) { + return s.contains(Properties.OPEN) && s.get(Properties.OPEN); + } + + private static BlockState withOpen(BlockState s, boolean open) { + return s.contains(Properties.OPEN) ? s.with(Properties.OPEN, open) : s; + } + + private static float getSpeed(SddConfig cfg, Kind kind) { + return switch (kind) { + case DOOR -> cfg.doorSpeed; + case TRAPDOOR -> cfg.trapdoorSpeed; + case FENCE_GATE -> cfg.fenceGateSpeed; + }; + } + + private static boolean isEnabled(SddConfig cfg, Kind kind) { + return switch (kind) { case DOOR -> cfg.animateDoors; case TRAPDOOR -> cfg.animateTrapdoors; case FENCE_GATE -> cfg.animateFenceGates; }; } - private static float getSpeed(SddConfig cfg, Kind k) { - float v = switch (k) { - case DOOR -> cfg.doorSpeed; - case TRAPDOOR -> cfg.trapdoorSpeed; - case FENCE_GATE -> cfg.fenceGateSpeed; - }; - if (v < 0.2f) v = 0.2f; - if (v > 3.0f) v = 3.0f; - return v; + public static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) { + SddConfig cfg = SddConfigManager.get(); + Kind kind = kindOf(newState); + if (kind == null) return; + if (!isEnabled(cfg, kind)) return; + + Block oldB = oldState.getBlock(); + Block newB = newState.getBlock(); + if (oldB != newB) return; + + boolean oldOpen = getOpen(oldState); + boolean newOpen = getOpen(newState); + if (oldOpen == newOpen) return; + + BlockState baseClosed = withOpen(newState, false); + ANIMS.put(pos.toImmutable(), new Anim(kind, pos.toImmutable(), baseClosed, newOpen)); } - private static float baseDurationSeconds(Kind k) { - return switch (k) { - case DOOR -> 0.18f; - case TRAPDOOR -> 0.16f; - case FENCE_GATE -> 0.18f; - }; + public static boolean shouldHideInChunk(BlockPos pos, BlockState state) { + Anim a = ANIMS.get(pos); + if (a == null) return false; + + SddConfig cfg = SddConfigManager.get(); + if (!isEnabled(cfg, a.kind)) return false; + + return kindOf(state) == a.kind; } - private static Kind getKind(BlockState a, BlockState b) { - if (a.getBlock() instanceof DoorBlock || b.getBlock() instanceof DoorBlock) return Kind.DOOR; - if (a.getBlock() instanceof TrapdoorBlock || b.getBlock() instanceof TrapdoorBlock) return Kind.TRAPDOOR; - if (a.getBlock() instanceof FenceGateBlock || b.getBlock() instanceof FenceGateBlock) return Kind.FENCE_GATE; - return null; - } - - private static Boolean getOpenNullable(BlockState s) { - Block b = s.getBlock(); - if ((b instanceof DoorBlock || b instanceof TrapdoorBlock || b instanceof FenceGateBlock) && s.contains(Properties.OPEN)) { - return s.get(Properties.OPEN); + public static void renderAll(Vec3d camPos) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world == null) { + ANIMS.clear(); + lastNs = -1; + return; } - return null; - } - private static BlockState forceClosed(BlockState s) { - if (s.contains(Properties.OPEN)) return s.with(Properties.OPEN, false); - return s; - } + float dt = frameDtSeconds(); + if (dt <= 0f && ANIMS.isEmpty()) return; - /** Target angle for the given state (0 or +/- 90 degrees). */ - private static float targetAngleDeg(Kind k, BlockState state) { - Boolean open = getOpenNullable(state); - if (open == null || !open) return 0f; + SddConfig cfg = SddConfigManager.get(); + var consumers = client.getBufferBuilders().getEntityVertexConsumers(); + BlockRenderManager brm = client.getBlockRenderManager(); - return switch (k) { - case DOOR -> { - DoorHinge hinge = state.get(Properties.DOOR_HINGE); - yield (hinge == DoorHinge.LEFT) ? -90f : 90f; + Iterator> it = ANIMS.entrySet().iterator(); + while (it.hasNext()) { + Anim a = it.next().getValue(); + + if (!isEnabled(cfg, a.kind)) { + it.remove(); + continue; } - case FENCE_GATE -> -90f; - case TRAPDOOR -> trapdoorOpenAngle(state); + + float speed = getSpeed(cfg, a.kind); + float baseDuration = switch (a.kind) { + case DOOR -> 0.18f; + case TRAPDOOR -> 0.16f; + case FENCE_GATE -> 0.16f; + }; + float dur = baseDuration / Math.max(0.05f, speed); + + a.time += dt; + float t = (dur <= 0f) ? 1f : (a.time / dur); + if (t > 1f) t = 1f; + + float eased = smoothStep(t); + float open01 = a.toOpen ? eased : (1f - eased); + + renderOne(client, brm, consumers, camPos, a, open01); + + if (t >= 1f) it.remove(); + } + + consumers.draw(); + } + + private static float smoothStep(float t) { + return t * t * (3f - 2f * t); + } + + private static void renderOne(MinecraftClient client, + BlockRenderManager brm, + net.minecraft.client.render.VertexConsumerProvider.Immediate consumers, + Vec3d camPos, + Anim a, + float open01) { + + BlockState worldState = client.world.getBlockState(a.pos); + if (kindOf(worldState) != a.kind) return; + + MatrixStack matrices = new MatrixStack(); + + double rx = a.pos.getX() - camPos.x; + double ry = a.pos.getY() - camPos.y; + double rz = a.pos.getZ() - camPos.z; + matrices.translate(rx, ry, rz); + + int light = WorldRenderer.getLightmapCoordinates(client.world, a.pos); + + switch (a.kind) { + case DOOR -> applyDoorTransform(matrices, worldState, open01); + case TRAPDOOR -> applyTrapdoorTransform(matrices, worldState, open01); + case FENCE_GATE -> applyFenceGateTransform(matrices, worldState, open01); + } + + brm.renderBlockAsEntity(a.baseClosed, matrices, consumers, light, OverlayTexture.DEFAULT_UV); + } + + private static void applyDoorTransform(MatrixStack matrices, BlockState worldState, float open01) { + float angle = 90f * open01; + + var facing = worldState.get(Properties.HORIZONTAL_FACING); + var hinge = worldState.get(Properties.DOOR_HINGE); + + float dir = (hinge == net.minecraft.block.enums.DoorHinge.LEFT) ? 1f : -1f; + + float pivotX = 0f; + float pivotZ = 0f; + + // FIX: switch completo para Direction (incluye UP/DOWN) + switch (facing) { + case NORTH -> pivotX = (hinge == net.minecraft.block.enums.DoorHinge.LEFT) ? 0f : 1f; + case SOUTH -> pivotX = (hinge == net.minecraft.block.enums.DoorHinge.LEFT) ? 1f : 0f; + case EAST -> pivotZ = (hinge == net.minecraft.block.enums.DoorHinge.LEFT) ? 0f : 1f; + case WEST -> pivotZ = (hinge == net.minecraft.block.enums.DoorHinge.LEFT) ? 1f : 0f; + case UP, DOWN -> { + // No debería ocurrir en puertas, pero evita warning del compilador + } + } + + matrices.translate(pivotX, 0.0, pivotZ); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(dir * angle)); + matrices.translate(-pivotX, 0.0, -pivotZ); + } + + private static void applyTrapdoorTransform(MatrixStack matrices, BlockState worldState, float open01) { + float angle = 90f * open01; + + var facing = worldState.get(Properties.HORIZONTAL_FACING); + var half = worldState.get(Properties.BLOCK_HALF); // TOP/BOTTOM + + float sign = (half == net.minecraft.block.enums.BlockHalf.BOTTOM) ? -1f : 1f; + float pivotY = (half == net.minecraft.block.enums.BlockHalf.BOTTOM) ? 0f : 1f; + + // Ya estaba completo, lo dejo igual + switch (facing) { + case NORTH -> { + float pivotZ = 0f; + matrices.translate(0, pivotY, pivotZ); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(sign * angle)); + matrices.translate(0, -pivotY, -pivotZ); + } + case SOUTH -> { + float pivotZ = 1f; + matrices.translate(0, pivotY, pivotZ); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-sign * angle)); + matrices.translate(0, -pivotY, -pivotZ); + } + case WEST -> { + float pivotX = 0f; + matrices.translate(pivotX, pivotY, 0); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-sign * angle)); + matrices.translate(-pivotX, -pivotY, 0); + } + case EAST -> { + float pivotX = 1f; + matrices.translate(pivotX, pivotY, 0); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(sign * angle)); + matrices.translate(-pivotX, -pivotY, 0); + } + case UP, DOWN -> { + // No aplicable + } + } + } + + private static void applyFenceGateTransform(MatrixStack matrices, BlockState worldState, float open01) { + float angle = 90f * open01; + var facing = worldState.get(Properties.HORIZONTAL_FACING); + + // FIX: switch completo para Direction (incluye UP/DOWN) + float dir = switch (facing) { + case NORTH, EAST -> 1f; + case SOUTH, WEST -> -1f; + case UP, DOWN -> 1f; // no debería ocurrir }; + + matrices.translate(0.5, 0.0, 0.5); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(dir * angle)); + matrices.translate(-0.5, 0.0, -0.5); } - private static float trapdoorOpenAngle(BlockState state) { - Direction f = state.get(Properties.HORIZONTAL_FACING); - BlockHalf half = state.get(Properties.BLOCK_HALF); + private static final class Anim { + final Kind kind; + final BlockPos pos; + final BlockState baseClosed; + final boolean toOpen; + float time = 0f; - float angle; - switch (f) { - case NORTH -> angle = -90f; - case SOUTH -> angle = 90f; - case WEST -> angle = 90f; - case EAST -> angle = -90f; - default -> angle = 90f; + Anim(Kind kind, BlockPos pos, BlockState baseClosed, boolean toOpen) { + this.kind = kind; + this.pos = pos; + this.baseClosed = baseClosed; + this.toOpen = toOpen; } - - if (half == BlockHalf.TOP) angle = -angle; - return angle; - } - - private static void applyTransform(MatrixStack ms, BlockState state, Kind kind, float angleDeg) { - switch (kind) { - case DOOR -> applyDoorTransform(ms, state, angleDeg); - case TRAPDOOR -> applyTrapdoorTransform(ms, state, angleDeg); - case FENCE_GATE -> applyFenceGateTransform(ms, state, angleDeg); - } - } - - private static void applyDoorTransform(MatrixStack ms, BlockState state, float angleDeg) { - Direction facing = state.get(Properties.HORIZONTAL_FACING); - DoorHinge hinge = state.get(Properties.DOOR_HINGE); - - Direction hingeDir = (hinge == DoorHinge.LEFT) ? facing.rotateYCounterclockwise() : facing.rotateYClockwise(); - - double px = 0.5; - double pz = 0.5; - if (hingeDir == Direction.WEST) px = 0.0; - if (hingeDir == Direction.EAST) px = 1.0; - if (hingeDir == Direction.NORTH) pz = 0.0; - if (hingeDir == Direction.SOUTH) pz = 1.0; - - ms.translate(px, 0.0, pz); - ms.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angleDeg)); - ms.translate(-px, 0.0, -pz); - } - - private static void applyFenceGateTransform(MatrixStack ms, BlockState state, float angleDeg) { - ms.translate(0.5, 0.0, 0.5); - ms.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angleDeg)); - ms.translate(-0.5, 0.0, -0.5); - } - - private static void applyTrapdoorTransform(MatrixStack ms, BlockState state, float angleDeg) { - Direction f = state.get(Properties.HORIZONTAL_FACING); - BlockHalf half = state.get(Properties.BLOCK_HALF); - - double py = (half == BlockHalf.TOP) ? 1.0 : 0.0; - - double px = 0.5; - double pz = 0.5; - - if (f == Direction.NORTH) pz = 0.0; - if (f == Direction.SOUTH) pz = 1.0; - if (f == Direction.WEST) px = 0.0; - if (f == Direction.EAST) px = 1.0; - - ms.translate(px, py, pz); - - if (f == Direction.NORTH || f == Direction.SOUTH) { - ms.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angleDeg)); - } else { - ms.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angleDeg)); - } - - ms.translate(-px, -py, -pz); } } diff --git a/src/client/java/com/straice/smoothdoors/mixin/client/WorldRendererMixin.java b/src/client/java/com/straice/smoothdoors/mixin/client/WorldRendererMixin.java index 7346ed4..51c30c4 100644 --- a/src/client/java/com/straice/smoothdoors/mixin/client/WorldRendererMixin.java +++ b/src/client/java/com/straice/smoothdoors/mixin/client/WorldRendererMixin.java @@ -1,13 +1,13 @@ package com.straice.smoothdoors.mixin.client; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.straice.smoothdoors.client.anim.SddAnimator; import net.minecraft.client.render.Camera; -import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.util.ObjectAllocator; -import net.minecraft.util.math.Vec3d; import org.joml.Matrix4f; +import org.joml.Vector4f; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -16,12 +16,26 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(WorldRenderer.class) public class WorldRendererMixin { - @Inject(method = "render", at = @At("TAIL")) - private void sdd$renderTail(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, - Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, + @Inject( + method = "render(Lnet/minecraft/client/util/ObjectAllocator;" + + "Lnet/minecraft/client/render/RenderTickCounter;" + + "ZLnet/minecraft/client/render/Camera;" + + "Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;" + + "Lcom/mojang/blaze3d/buffers/GpuBufferSlice;" + + "Lorg/joml/Vector4f;Z)V", + at = @At("TAIL") + ) + private void sdd$renderTail(ObjectAllocator allocator, + RenderTickCounter tickCounter, + boolean renderBlockOutline, + Camera camera, + Matrix4f positionMatrix, + Matrix4f projectionMatrix, + GpuBufferSlice slice, + Vector4f fogColor, + boolean bl, CallbackInfo ci) { - Vec3d camPos = camera.getPos(); - float tickDelta = tickCounter.getTickProgress(true); - SddAnimator.renderAll(camPos, tickDelta); + + SddAnimator.renderAll(camera.getPos()); } }