|
|
|
|
@@ -2,339 +2,270 @@ package com.straice.smoothdoors.client.anim;
|
|
|
|
|
|
|
|
|
|
import com.straice.smoothdoors.config.SddConfig;
|
|
|
|
|
import com.straice.smoothdoors.config.SddConfigManager;
|
|
|
|
|
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 it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
|
|
|
import net.minecraft.block.*;
|
|
|
|
|
import net.minecraft.block.enums.BlockHalf;
|
|
|
|
|
import net.minecraft.block.enums.DoorHinge;
|
|
|
|
|
import net.minecraft.block.enums.DoubleBlockHalf;
|
|
|
|
|
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.*;
|
|
|
|
|
import net.minecraft.util.math.RotationAxis;
|
|
|
|
|
import net.minecraft.util.math.Vec3d;
|
|
|
|
|
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import net.minecraft.world.BlockRenderView;
|
|
|
|
|
|
|
|
|
|
public final class SddAnimator {
|
|
|
|
|
|
|
|
|
|
public enum Kind {
|
|
|
|
|
DOOR,
|
|
|
|
|
TRAPDOOR,
|
|
|
|
|
FENCE_GATE
|
|
|
|
|
}
|
|
|
|
|
private static final MinecraftClient MC = MinecraftClient.getInstance();
|
|
|
|
|
|
|
|
|
|
private static final Map<BlockPos, Anim> ANIMS = new ConcurrentHashMap<>();
|
|
|
|
|
private static long lastNs = -1L;
|
|
|
|
|
// Duración base (segundos) a speed=1.0x
|
|
|
|
|
private static final float BASE_DURATION_S = 0.35f;
|
|
|
|
|
|
|
|
|
|
private static final Long2ObjectOpenHashMap<Anim> ANIMS = new Long2ObjectOpenHashMap<>();
|
|
|
|
|
|
|
|
|
|
private SddAnimator() {}
|
|
|
|
|
|
|
|
|
|
private static final class Anim {
|
|
|
|
|
final BlockPos pos;
|
|
|
|
|
final Kind kind;
|
|
|
|
|
final boolean toOpen;
|
|
|
|
|
float time; // seconds
|
|
|
|
|
final BlockState baseClosed; // state renderizado (cerrado) al que aplicamos la rotación
|
|
|
|
|
// === API que te piden los mixins ===
|
|
|
|
|
|
|
|
|
|
Anim(BlockPos pos, Kind kind, boolean toOpen, BlockState baseClosed) {
|
|
|
|
|
this.pos = pos;
|
|
|
|
|
this.kind = kind;
|
|
|
|
|
this.toOpen = toOpen;
|
|
|
|
|
this.baseClosed = baseClosed;
|
|
|
|
|
this.time = 0f;
|
|
|
|
|
}
|
|
|
|
|
/** Se usa en BlockRenderManagerMixin para ocultar el bloque vanilla mientras animamos. */
|
|
|
|
|
public static boolean shouldHideInChunk(BlockPos pos, BlockState state) {
|
|
|
|
|
return ANIMS.containsKey(pos.asLong());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========= API (llamada desde mixins) =========
|
|
|
|
|
|
|
|
|
|
/** Se usa para arrancar/parar animaciones cuando cambia un bloque. */
|
|
|
|
|
public static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) {
|
|
|
|
|
Kind kNew = kindOf(newState);
|
|
|
|
|
if (kNew == null) {
|
|
|
|
|
ANIMS.remove(pos);
|
|
|
|
|
Kind kind = kindOf(oldState, newState);
|
|
|
|
|
if (kind == null) {
|
|
|
|
|
ANIMS.remove(pos.asLong());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!newState.contains(Properties.OPEN)) return;
|
|
|
|
|
|
|
|
|
|
boolean oldOpen = oldState.contains(Properties.OPEN) && oldState.get(Properties.OPEN);
|
|
|
|
|
boolean newOpen = newState.get(Properties.OPEN);
|
|
|
|
|
boolean oldOpen = isOpen(oldState);
|
|
|
|
|
boolean newOpen = isOpen(newState);
|
|
|
|
|
if (oldOpen == newOpen) return;
|
|
|
|
|
|
|
|
|
|
MinecraftClient client = MinecraftClient.getInstance();
|
|
|
|
|
if (client.world == null) return;
|
|
|
|
|
|
|
|
|
|
// Door: animar lower+upper
|
|
|
|
|
if (kNew == Kind.DOOR && newState.getBlock() instanceof DoorBlock && newState.contains(Properties.DOUBLE_BLOCK_HALF)) {
|
|
|
|
|
DoubleBlockHalf half = newState.get(Properties.DOUBLE_BLOCK_HALF);
|
|
|
|
|
BlockPos basePos = (half == DoubleBlockHalf.UPPER) ? pos.down() : pos;
|
|
|
|
|
|
|
|
|
|
BlockState lower = client.world.getBlockState(basePos);
|
|
|
|
|
BlockState upper = client.world.getBlockState(basePos.up());
|
|
|
|
|
|
|
|
|
|
BlockState lowerClosed = (lower.getBlock() instanceof DoorBlock && lower.contains(Properties.OPEN))
|
|
|
|
|
? lower.with(Properties.OPEN, false)
|
|
|
|
|
: newState.with(Properties.OPEN, false).with(Properties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER);
|
|
|
|
|
|
|
|
|
|
BlockState upperClosed = (upper.getBlock() instanceof DoorBlock && upper.contains(Properties.OPEN))
|
|
|
|
|
? upper.with(Properties.OPEN, false)
|
|
|
|
|
: newState.with(Properties.OPEN, false).with(Properties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.UPPER);
|
|
|
|
|
|
|
|
|
|
startAnim(basePos, Kind.DOOR, newOpen, lowerClosed);
|
|
|
|
|
startAnim(basePos.up(), Kind.DOOR, newOpen, upperClosed);
|
|
|
|
|
SddConfig cfg = SddConfigManager.get();
|
|
|
|
|
if (!isAnimationEnabled(cfg, kind)) {
|
|
|
|
|
ANIMS.remove(pos.asLong());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trapdoor / Fence gate
|
|
|
|
|
startAnim(pos, kNew, newOpen, newState.with(Properties.OPEN, false));
|
|
|
|
|
float speed = speedFor(cfg, kind);
|
|
|
|
|
if (speed <= 0.001f) speed = 1.0f;
|
|
|
|
|
|
|
|
|
|
// Renderizamos SIEMPRE el modelo "cerrado" y nosotros aplicamos la rotación.
|
|
|
|
|
BlockState base = forceClosedModel(newState, kind);
|
|
|
|
|
|
|
|
|
|
Anim anim = new Anim(pos.toImmutable(), base, kind, oldOpen, newOpen, speed);
|
|
|
|
|
ANIMS.put(pos.asLong(), anim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Si devuelve true, el mixin que renderiza chunks debe "ocultar" el bloque real para que
|
|
|
|
|
* sólo se vea el animado.
|
|
|
|
|
*/
|
|
|
|
|
public static boolean shouldHideInChunk(BlockPos pos, BlockState state) {
|
|
|
|
|
Anim a = ANIMS.get(pos);
|
|
|
|
|
if (a == null) return false;
|
|
|
|
|
if (kindOf(state) != a.kind) return false;
|
|
|
|
|
/** Render con tickDelta explícito. */
|
|
|
|
|
public static void renderAll(Vec3d camPos, float tickDelta) {
|
|
|
|
|
if (ANIMS.isEmpty() || MC.world == null) return;
|
|
|
|
|
|
|
|
|
|
SddConfig cfg = SddConfigManager.get();
|
|
|
|
|
return isEnabled(cfg, a.kind);
|
|
|
|
|
}
|
|
|
|
|
tick(tickDelta);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Llamar cada frame desde WorldRendererMixin, pasando camera.getPos().
|
|
|
|
|
*/
|
|
|
|
|
public static void renderAll(Vec3d camPos) {
|
|
|
|
|
MinecraftClient client = MinecraftClient.getInstance();
|
|
|
|
|
if (client.world == null) {
|
|
|
|
|
ANIMS.clear();
|
|
|
|
|
lastNs = -1L;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
VertexConsumerProvider.Immediate consumers = MC.getBufferBuilders().getEntityVertexConsumers();
|
|
|
|
|
MatrixStack matrices = new MatrixStack();
|
|
|
|
|
|
|
|
|
|
float dt = frameDtSeconds();
|
|
|
|
|
if (ANIMS.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
SddConfig cfg = SddConfigManager.get();
|
|
|
|
|
BlockRenderManager brm = client.getBlockRenderManager();
|
|
|
|
|
VertexConsumerProvider.Immediate consumers = client.getBufferBuilders().getEntityVertexConsumers();
|
|
|
|
|
|
|
|
|
|
Iterator<Map.Entry<BlockPos, Anim>> it = ANIMS.entrySet().iterator();
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
|
Anim a = it.next().getValue();
|
|
|
|
|
|
|
|
|
|
if (!isEnabled(cfg, a.kind)) {
|
|
|
|
|
it.remove();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
for (Anim a : ANIMS.values()) {
|
|
|
|
|
renderOne(a, camPos, matrices, consumers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
consumers.draw();
|
|
|
|
|
cleanupFinished();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========= Render =========
|
|
|
|
|
/** Overload por compat si algún sitio llama sin tickDelta. */
|
|
|
|
|
public static void renderAll(Vec3d camPos) {
|
|
|
|
|
float tickDelta = 0.0f;
|
|
|
|
|
if (MC != null) {
|
|
|
|
|
// En 1.21.x normalmente es esto:
|
|
|
|
|
tickDelta = MC.getRenderTickCounter().getTickProgress(true);
|
|
|
|
|
}
|
|
|
|
|
renderAll(camPos, tickDelta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void renderOne(MinecraftClient client,
|
|
|
|
|
BlockRenderManager brm,
|
|
|
|
|
VertexConsumerProvider.Immediate consumers,
|
|
|
|
|
Vec3d camPos,
|
|
|
|
|
Anim a,
|
|
|
|
|
float open01) {
|
|
|
|
|
// === Interno ===
|
|
|
|
|
|
|
|
|
|
BlockState worldState = client.world.getBlockState(a.pos);
|
|
|
|
|
if (kindOf(worldState) != a.kind) return;
|
|
|
|
|
private static void tick(float tickDelta) {
|
|
|
|
|
float dt = (1.0f / 20.0f) * MathHelper.clamp(tickDelta, 0.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
MatrixStack matrices = new MatrixStack();
|
|
|
|
|
for (Anim a : ANIMS.values()) {
|
|
|
|
|
float duration = BASE_DURATION_S / a.speed;
|
|
|
|
|
if (duration < 0.05f) duration = 0.05f;
|
|
|
|
|
|
|
|
|
|
// CLAVE: coordenadas relativas a cámara
|
|
|
|
|
float step = dt / duration;
|
|
|
|
|
a.t = MathHelper.clamp(a.t + step, 0.0f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void cleanupFinished() {
|
|
|
|
|
ANIMS.long2ObjectEntrySet().removeIf(e -> e.getValue().t >= 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void renderOne(Anim anim, Vec3d camPos, MatrixStack matrices, VertexConsumerProvider consumers) {
|
|
|
|
|
if (MC.world == null) return;
|
|
|
|
|
|
|
|
|
|
BlockPos pos = anim.pos;
|
|
|
|
|
BlockState state = anim.baseState;
|
|
|
|
|
|
|
|
|
|
matrices.push();
|
|
|
|
|
matrices.translate(
|
|
|
|
|
a.pos.getX() - camPos.x,
|
|
|
|
|
a.pos.getY() - camPos.y,
|
|
|
|
|
a.pos.getZ() - camPos.z
|
|
|
|
|
pos.getX() - camPos.x,
|
|
|
|
|
pos.getY() - camPos.y,
|
|
|
|
|
pos.getZ() - camPos.z
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
int light = WorldRenderer.getLightmapCoordinates(client.world, a.pos);
|
|
|
|
|
float eased = easeInOut(anim.t);
|
|
|
|
|
float angleDeg = lerpAngleDeg(anim.fromOpen, anim.toOpen, eased, anim.kind, state);
|
|
|
|
|
applyTransform(anim.kind, state, angleDeg, matrices);
|
|
|
|
|
|
|
|
|
|
switch (a.kind) {
|
|
|
|
|
case DOOR -> applyDoorTransform(matrices, worldState, open01);
|
|
|
|
|
case TRAPDOOR -> applyTrapdoorTransform(matrices, worldState, open01);
|
|
|
|
|
case FENCE_GATE -> applyFenceGateTransform(matrices, worldState, open01);
|
|
|
|
|
}
|
|
|
|
|
int light = WorldRenderer.getLightmapCoordinates((BlockRenderView) MC.world, pos);
|
|
|
|
|
MC.getBlockRenderManager().renderBlockAsEntity(state, matrices, consumers, light, OverlayTexture.DEFAULT_UV);
|
|
|
|
|
|
|
|
|
|
brm.renderBlockAsEntity(a.baseClosed, matrices, consumers, light, OverlayTexture.DEFAULT_UV);
|
|
|
|
|
matrices.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========= Transforms =========
|
|
|
|
|
|
|
|
|
|
private static void applyDoorTransform(MatrixStack m, BlockState worldState, float open01) {
|
|
|
|
|
if (!(worldState.getBlock() instanceof DoorBlock)) return;
|
|
|
|
|
|
|
|
|
|
Direction facing = worldState.get(Properties.HORIZONTAL_FACING);
|
|
|
|
|
DoorHinge hinge = worldState.get(Properties.DOOR_HINGE);
|
|
|
|
|
|
|
|
|
|
float pivotX = 0.5f;
|
|
|
|
|
float pivotZ = 0.5f;
|
|
|
|
|
|
|
|
|
|
switch (facing) {
|
|
|
|
|
case NORTH -> pivotX = (hinge == DoorHinge.LEFT) ? 0.0f : 1.0f;
|
|
|
|
|
case SOUTH -> pivotX = (hinge == DoorHinge.LEFT) ? 1.0f : 0.0f;
|
|
|
|
|
case EAST -> pivotZ = (hinge == DoorHinge.LEFT) ? 0.0f : 1.0f;
|
|
|
|
|
case WEST -> pivotZ = (hinge == DoorHinge.LEFT) ? 1.0f : 0.0f;
|
|
|
|
|
default -> {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float sign;
|
|
|
|
|
switch (facing) {
|
|
|
|
|
case NORTH -> sign = (hinge == DoorHinge.RIGHT) ? -1f : 1f;
|
|
|
|
|
case SOUTH -> sign = (hinge == DoorHinge.RIGHT) ? 1f : -1f;
|
|
|
|
|
case EAST -> sign = (hinge == DoorHinge.RIGHT) ? 1f : -1f;
|
|
|
|
|
case WEST -> sign = (hinge == DoorHinge.RIGHT) ? -1f : 1f;
|
|
|
|
|
default -> sign = 1f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float yawDeg = open01 * 90.0f * sign;
|
|
|
|
|
|
|
|
|
|
m.translate(pivotX, 0.0f, pivotZ);
|
|
|
|
|
m.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yawDeg));
|
|
|
|
|
m.translate(-pivotX, 0.0f, -pivotZ);
|
|
|
|
|
private static float lerpAngleDeg(boolean fromOpen, boolean toOpen, float t, Kind kind, BlockState state) {
|
|
|
|
|
float a = angleFor(kind, state, fromOpen);
|
|
|
|
|
float b = angleFor(kind, state, toOpen);
|
|
|
|
|
return MathHelper.lerp(t, a, b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void applyTrapdoorTransform(MatrixStack m, BlockState worldState, float open01) {
|
|
|
|
|
if (!(worldState.getBlock() instanceof TrapdoorBlock)) return;
|
|
|
|
|
private static float angleFor(Kind kind, BlockState state, boolean open) {
|
|
|
|
|
if (!open) return 0.0f;
|
|
|
|
|
|
|
|
|
|
Direction facing = worldState.get(Properties.HORIZONTAL_FACING);
|
|
|
|
|
BlockHalf half = worldState.get(Properties.BLOCK_HALF);
|
|
|
|
|
switch (kind) {
|
|
|
|
|
case DOOR -> {
|
|
|
|
|
DoorHinge hinge = state.get(DoorBlock.HINGE);
|
|
|
|
|
return (hinge == DoorHinge.RIGHT) ? -90.0f : 90.0f;
|
|
|
|
|
}
|
|
|
|
|
case TRAPDOOR -> {
|
|
|
|
|
BlockHalf half = state.get(TrapdoorBlock.HALF);
|
|
|
|
|
return (half == BlockHalf.TOP) ? -90.0f : 90.0f;
|
|
|
|
|
}
|
|
|
|
|
case FENCE_GATE -> {
|
|
|
|
|
return 90.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float sign = (half == BlockHalf.TOP) ? -1f : 1f;
|
|
|
|
|
float angle = open01 * 90.0f * sign;
|
|
|
|
|
|
|
|
|
|
float pivotX = 0.5f;
|
|
|
|
|
float pivotY = 0.5f;
|
|
|
|
|
float pivotZ = 0.5f;
|
|
|
|
|
|
|
|
|
|
switch (facing) {
|
|
|
|
|
case NORTH -> {
|
|
|
|
|
pivotZ = 0.0f;
|
|
|
|
|
m.translate(pivotX, pivotY, pivotZ);
|
|
|
|
|
m.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle));
|
|
|
|
|
m.translate(-pivotX, -pivotY, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
case SOUTH -> {
|
|
|
|
|
pivotZ = 1.0f;
|
|
|
|
|
m.translate(pivotX, pivotY, pivotZ);
|
|
|
|
|
m.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(angle));
|
|
|
|
|
m.translate(-pivotX, -pivotY, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
case WEST -> {
|
|
|
|
|
pivotX = 0.0f;
|
|
|
|
|
m.translate(pivotX, pivotY, pivotZ);
|
|
|
|
|
m.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(angle));
|
|
|
|
|
m.translate(-pivotX, -pivotY, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
case EAST -> {
|
|
|
|
|
pivotX = 1.0f;
|
|
|
|
|
m.translate(pivotX, pivotY, pivotZ);
|
|
|
|
|
m.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle));
|
|
|
|
|
m.translate(-pivotX, -pivotY, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
default -> {}
|
|
|
|
|
private static void applyTransform(Kind kind, BlockState state, float angleDeg, MatrixStack matrices) {
|
|
|
|
|
switch (kind) {
|
|
|
|
|
case DOOR -> transformDoor(state, angleDeg, matrices);
|
|
|
|
|
case TRAPDOOR -> transformTrapdoor(state, angleDeg, matrices);
|
|
|
|
|
case FENCE_GATE -> transformFenceGate(state, angleDeg, matrices);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void applyFenceGateTransform(MatrixStack m, BlockState worldState, float open01) {
|
|
|
|
|
if (!(worldState.getBlock() instanceof FenceGateBlock)) return;
|
|
|
|
|
private static void transformDoor(BlockState state, float angleDeg, MatrixStack matrices) {
|
|
|
|
|
Direction facing = state.get(DoorBlock.FACING);
|
|
|
|
|
DoorHinge hinge = state.get(DoorBlock.HINGE);
|
|
|
|
|
|
|
|
|
|
Direction facing = worldState.get(Properties.HORIZONTAL_FACING);
|
|
|
|
|
Direction hingeSide = (hinge == DoorHinge.RIGHT) ? facing.rotateYClockwise() : facing.rotateYCounterclockwise();
|
|
|
|
|
float pivotX = hingeSide.getOffsetX() == 1 ? 1.0f : (hingeSide.getOffsetX() == -1 ? 0.0f : 0.5f);
|
|
|
|
|
float pivotZ = hingeSide.getOffsetZ() == 1 ? 1.0f : (hingeSide.getOffsetZ() == -1 ? 0.0f : 0.5f);
|
|
|
|
|
|
|
|
|
|
float sign;
|
|
|
|
|
switch (facing) {
|
|
|
|
|
case NORTH, WEST -> sign = 1f;
|
|
|
|
|
case SOUTH, EAST -> sign = -1f;
|
|
|
|
|
default -> sign = 1f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float yawDeg = open01 * 90.0f * sign;
|
|
|
|
|
|
|
|
|
|
m.translate(0.5f, 0.0f, 0.5f);
|
|
|
|
|
m.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yawDeg));
|
|
|
|
|
m.translate(-0.5f, 0.0f, -0.5f);
|
|
|
|
|
matrices.translate(pivotX, 0.0f, pivotZ);
|
|
|
|
|
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angleDeg));
|
|
|
|
|
matrices.translate(-pivotX, 0.0f, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========= Helpers =========
|
|
|
|
|
private static void transformTrapdoor(BlockState state, float angleDeg, MatrixStack matrices) {
|
|
|
|
|
Direction facing = state.get(TrapdoorBlock.FACING);
|
|
|
|
|
|
|
|
|
|
private static Kind kindOf(BlockState s) {
|
|
|
|
|
float pivotX = facing.getOffsetX() == 1 ? 1.0f : (facing.getOffsetX() == -1 ? 0.0f : 0.5f);
|
|
|
|
|
float pivotZ = facing.getOffsetZ() == 1 ? 1.0f : (facing.getOffsetZ() == -1 ? 0.0f : 0.5f);
|
|
|
|
|
|
|
|
|
|
matrices.translate(pivotX, 0.5f, pivotZ);
|
|
|
|
|
if (facing == Direction.NORTH || facing == Direction.SOUTH) {
|
|
|
|
|
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angleDeg));
|
|
|
|
|
} else {
|
|
|
|
|
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-angleDeg));
|
|
|
|
|
}
|
|
|
|
|
matrices.translate(-pivotX, -0.5f, -pivotZ);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void transformFenceGate(BlockState state, float angleDeg, MatrixStack matrices) {
|
|
|
|
|
matrices.translate(0.5f, 0.0f, 0.5f);
|
|
|
|
|
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angleDeg));
|
|
|
|
|
matrices.translate(-0.5f, 0.0f, -0.5f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static BlockState forceClosedModel(BlockState s, Kind kind) {
|
|
|
|
|
return switch (kind) {
|
|
|
|
|
case DOOR -> s.with(DoorBlock.OPEN, false);
|
|
|
|
|
case TRAPDOOR -> s.with(TrapdoorBlock.OPEN, false);
|
|
|
|
|
case FENCE_GATE -> s.with(FenceGateBlock.OPEN, false);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean isOpen(BlockState s) {
|
|
|
|
|
Block b = s.getBlock();
|
|
|
|
|
if (b instanceof DoorBlock) return s.get(DoorBlock.OPEN);
|
|
|
|
|
if (b instanceof TrapdoorBlock) return s.get(TrapdoorBlock.OPEN);
|
|
|
|
|
if (b instanceof FenceGateBlock) return s.get(FenceGateBlock.OPEN);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Kind kindOf(BlockState oldState, BlockState newState) {
|
|
|
|
|
Block b = newState.getBlock();
|
|
|
|
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
|
|
|
|
if (b instanceof TrapdoorBlock) return Kind.TRAPDOOR;
|
|
|
|
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
|
|
|
|
|
|
|
|
|
b = oldState.getBlock();
|
|
|
|
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
|
|
|
|
if (b instanceof TrapdoorBlock) return Kind.TRAPDOOR;
|
|
|
|
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void startAnim(BlockPos pos, Kind kind, boolean toOpen, BlockState baseClosed) {
|
|
|
|
|
ANIMS.put(pos.toImmutable(), new Anim(pos.toImmutable(), kind, toOpen, baseClosed));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean isEnabled(SddConfig cfg, Kind kind) {
|
|
|
|
|
private static boolean isAnimationEnabled(SddConfig cfg, Kind kind) {
|
|
|
|
|
return switch (kind) {
|
|
|
|
|
case DOOR -> cfg.animateDoors;
|
|
|
|
|
case TRAPDOOR -> cfg.animateTrapdoors;
|
|
|
|
|
case FENCE_GATE -> cfg.animateFenceGates;
|
|
|
|
|
case FENCE_GATE -> cfg.animateFenceGates; // debe existir en tu config
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float getSpeed(SddConfig cfg, Kind kind) {
|
|
|
|
|
private static float speedFor(SddConfig cfg, Kind kind) {
|
|
|
|
|
return switch (kind) {
|
|
|
|
|
case DOOR -> cfg.doorSpeed;
|
|
|
|
|
case TRAPDOOR -> cfg.trapdoorSpeed;
|
|
|
|
|
case FENCE_GATE -> cfg.fenceGateSpeed;
|
|
|
|
|
case FENCE_GATE -> cfg.fenceGateSpeed; // debe existir en tu config
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float smoothStep(float t) {
|
|
|
|
|
return t * t * (3f - 2f * t);
|
|
|
|
|
private static float easeInOut(float t) {
|
|
|
|
|
return t * t * (3.0f - 2.0f * t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float frameDtSeconds() {
|
|
|
|
|
long now = System.nanoTime();
|
|
|
|
|
if (lastNs < 0L) {
|
|
|
|
|
lastNs = now;
|
|
|
|
|
return 0f;
|
|
|
|
|
}
|
|
|
|
|
long d = now - lastNs;
|
|
|
|
|
lastNs = now;
|
|
|
|
|
public enum Kind { DOOR, TRAPDOOR, FENCE_GATE }
|
|
|
|
|
|
|
|
|
|
double sec = d / 1_000_000_000.0;
|
|
|
|
|
if (sec < 0) sec = 0;
|
|
|
|
|
if (sec > 0.1) sec = 0.1;
|
|
|
|
|
return (float) sec;
|
|
|
|
|
private static final class Anim {
|
|
|
|
|
final BlockPos pos;
|
|
|
|
|
final BlockState baseState;
|
|
|
|
|
final Kind kind;
|
|
|
|
|
final boolean fromOpen;
|
|
|
|
|
final boolean toOpen;
|
|
|
|
|
final float speed;
|
|
|
|
|
|
|
|
|
|
float t; // 0..1
|
|
|
|
|
|
|
|
|
|
Anim(BlockPos pos, BlockState baseState, Kind kind, boolean fromOpen, boolean toOpen, float speed) {
|
|
|
|
|
this.pos = pos;
|
|
|
|
|
this.baseState = baseState;
|
|
|
|
|
this.kind = kind;
|
|
|
|
|
this.fromOpen = fromOpen;
|
|
|
|
|
this.toOpen = toOpen;
|
|
|
|
|
this.speed = speed;
|
|
|
|
|
this.t = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|