@@ -14,112 +14,143 @@ import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.WorldRenderer ;
import net.minecraft.client.util.math.MatrixStack ;
import net.minecraft.util.math.* ;
import net.minecraft.util.math.RotationAxis ;
import net.minecraft.world.BlockRenderView ;
import net.minecraft.world.Wor ld;
import net.minecraft.util.shape.VoxelShape ;
import java.lang.reflect.Fie ld;
public final class SddAnimator {
private static final MinecraftClient MC = MinecraftClient . getInstance ( ) ;
// Duració n base (segundos) a speed= 1.0x
// Duració n base (segundos) a speed = 1.0x
private static final float BASE_DURATION_S = 0 . 35f ;
private static final float MIN_DURATION_S = 0 . 05f ;
private static final Long2ObjectOpenHashMap < Anim > ANIMS = new Long2ObjectOpenHashMap < > ( ) ;
private static boolean hooksInit = false ;
private SddAnimator ( ) { }
/**
* Llamar desde SmoothDoubleDoorsClient.onInitializeClient()
* (evita mixins a WorldRenderer y firmas rotas).
*/
/** Llamar desde SmoothDoubleDoorsClient#onInitializeClient(). */
public static void initClientHooks ( ) {
if ( hooksInit ) return ;
hooksInit = true ;
// Render cada frame
WorldRenderEvents . END . register ( ctx - > {
WorldRenderEvents . AFTER_ENTITIES . register ( ctx - > {
if ( MC . world = = null | | ANIMS . isEmpty ( ) ) return ;
Vec3d camPos = ctx . camera ( ) . getPos ( ) ;
float tickDelta = MC . getRenderTickCounter ( ) . getTickProgress ( true ) ;
renderAll ( camPos , tickDelta ) ;
renderAll ( tickDelta ) ;
} ) ;
}
// === API que usan tus mixins ===
// === API usada por mixins ===
/** Se usa en BlockRenderManagerMixin para NO meter el modelo vanilla en el chunk mientras animamos . */
/** Oculta el bloque vanilla mientras animamos (evita “doble puerta† ) . */
public static boolean shouldHideInChunk ( BlockPos pos , BlockState state ) {
return ANIMS . containsKey ( pos . asLong ( ) ) ;
synchronized ( ANIMS ) {
if ( ANIMS . containsKey ( pos . asLong ( ) ) ) return true ;
if ( state . getBlock ( ) instanceof DoorBlock & & state . contains ( DoorBlock . HALF ) ) {
DoubleBlockHalf half = state . get ( DoorBlock . HALF ) ;
BlockPos other = ( half = = DoubleBlockHalf . LOWER ) ? pos . up ( ) : pos . down ( ) ;
return ANIMS . containsKey ( other . asLong ( ) ) ;
}
return false ;
}
}
/**
* Se llama desde tu WorldMixin (scheduleBlockRerenderIfNeeded).
* Arranca/actualiza animaciones.
*/
public static boolean isAnimatingAt ( BlockPos pos ) {
synchronized ( ANIMS ) {
return ANIMS . containsKey ( pos . asLong ( ) ) ;
}
}
/** Se llama cuando cambia el bloque (abrir/cerrar). */
public static void onBlockUpdate ( BlockPos pos , BlockState oldState , BlockState newState ) {
if ( MC . world = = null ) return ;
Kind kind = kindOf ( oldState , newState ) ;
if ( kind = = null ) {
ANIMS . remove ( pos . asLong ( ) ) ;
synchronized ( ANIMS ) {
ANIMS . remove ( pos . asLong ( ) ) ;
}
return ;
}
if ( ! isKindBlock ( kind , newState ) ) {
removeKindAt ( pos , kind ) ;
return ;
}
// DOOR: arrancamos solo desde la mitad LOWER para no duplicar.
if ( kind = = Kind . DOOR & & newState . contains ( DoorBlock . HALF ) ) {
if ( newState . get ( DoorBlock . HALF ) = = DoubleBlockHalf . UPPER ) return ;
}
boolean oldOpen = isOpen ( oldState ) ;
boolean newOpen = isOpen ( newState ) ;
if ( oldOpen = = newOpen ) return ;
SddConfig cfg = SddConfigManager . get ( ) ;
if ( ! isAnimationEnabled ( cfg , kind ) ) {
// por seguridad, limpia este pos y (si es puerta) también su otra mitad
removeWithPairIfDoor ( pos , newState ) ;
removeKindAt ( pos , kind ) ;
return ;
}
float speed = speedFor ( cfg , kind ) ;
if ( speed < = 0 . 001f ) speed = 1 . 0f ;
long startNs = System . nano Time( ) ;
long startTick = MC . world . get Time( ) ;
if ( kind = = Kind . DOOR ) {
// IMPORTANTÍSIMO:
// 1) animar SIEMPRE LAS 2 MITADES (upper+lower) para que no quede “puerta vanilla” encima
// 2) si cfg.connectDoors está ON, arrancar también la puerta gemela con el MISMO startNs para que vayan a la vez
startDoorAnimationWithPairs ( cfg , pos , oldOpen , newOpen , speed , startNs ) ;
return ;
startDoorAnimBothHalves ( pos , newState , oldOpen , newOpen , speed , startTick ) ;
} else {
BlockState base = forceClosedModel ( newState , kind ) ;
synchronized ( ANIMS ) {
ANIMS . put ( pos . asLong ( ) , new Anim ( pos . toImmutable ( ) , base , kind , oldOpen , newOpen , speed , startTick ) ) ;
}
requestRerender ( pos ) ;
}
// Trapdoor / Fence gate (1 bloque)
BlockState base = forceClosedModel ( newState , kind ) ;
putOrReplaceAnim ( pos , base , kind , oldOpen , newOpen , speed , startNs ) ;
ensureRerender ( pos ) ;
}
// === Render ===
public static void renderAll (Vec3d camPos , float tickDelta ) {
private static void renderAll ( float tickDelta ) {
if ( MC . world = = null | | ANIMS . isEmpty ( ) ) return ;
VertexConsumerProvider . Immediate consumers = MC . getBufferBuild ers ( ) . getEntityVertexConsumer s ( ) ;
Vec3d camPos = MC . gameRenderer . getCam era ( ) . getPo s ( ) ;
MatrixStack matrices = new MatrixStack ( ) ;
VertexConsumerProvider . Immediate consumers = MC . getBufferBuilders ( ) . getEntityVertexConsumers ( ) ;
long nowNs = System . nano Time( ) ;
long worldTick = MC . world . get Time( ) ;
double nowTick = worldTick + ( double ) MathHelper . clamp ( tickDelta , 0 . 0f , 1 . 0f ) ;
for ( Anim a : ANIMS . values ( ) ) {
float t = a . progress01 ( nowNs ) ;
Anim [ ] snapshot ;
synchronized ( ANIMS ) {
snapshot = ANIMS . values ( ) . toArray ( new Anim [ 0 ] ) ;
}
for ( Anim a : snapshot ) {
float t = a . progress01 ( nowTick ) ;
renderOne ( a , t , camPos , matrices , consumers ) ;
}
consumers . draw ( ) ;
cleanupFinished ( nowNs ) ;
cleanupFinished ( nowTick ) ;
}
// === Interno ===
private static void cleanupFinished ( double nowTick ) {
synchronized ( ANIMS ) {
var it = ANIMS . long2ObjectEntrySet ( ) . iterator ( ) ;
while ( it . hasNext ( ) ) {
var entry = it . next ( ) ;
if ( entry . getValue ( ) . isFinished ( nowTick ) ) {
BlockPos pos = entry . getValue ( ) . pos ;
it . remove ( ) ;
requestRerender ( pos ) ;
}
}
}
}
private static void renderOne ( Anim anim , float t , Vec3d camPos , MatrixStack matrices , VertexConsumerProvider consumers ) {
if ( MC . world = = null ) return ;
@@ -129,7 +160,7 @@ public final class SddAnimator {
matrices . push ( ) ;
// Fijado al bloque (NO a la cámara): world -> camera-relative
// Fijo en mundo: bloque - cà  ¡mara
matrices . translate (
pos . getX ( ) - camPos . x ,
pos . getY ( ) - camPos . y ,
@@ -138,155 +169,45 @@ public final class SddAnimator {
float eased = easeInOut ( t ) ;
float angleDeg = lerpAngleDeg ( anim . fromOpen , anim . toOpen , eased , anim . kind , state ) ;
applyTransform ( anim . kind , state , angleDeg , matrices ) ;
int light = WorldRenderer . getLightmapCoordinates ( ( BlockRenderView ) MC . world , pos ) ;
int light = WorldRenderer . getLightmapCoordinates ( ( net . minecraft . world . BlockRenderView) MC . world , pos ) ;
MC . getBlockRenderManager ( ) . renderBlockAsEntity ( state , matrices , consumers , light , OverlayTexture . DEFAULT_UV ) ;
matrices . pop ( ) ;
}
private static void cleanupFinished ( long nowNs ) {
if ( ANIMS . isEmpty ( ) | | MC . world = = null ) return ;
// === DOOR (dos mitades) ===
// Cuando termina una animación, hay que forzar rerender para que el chunk vuelva a incluir el bloque vanilla final.
ANIMS . long2ObjectEntrySet ( ) . removeIf ( e - > {
Anim a = e . getValue ( ) ;
if ( ! a . isFinished ( nowNs ) ) return false ;
private static void startDoorAnimBothHalves ( BlockPos lowerPos , BlockState lowerNewState , boolean oldOpen , boolean newOpen , float speed , long startTick ) {
BlockState baseLower = forceClosedModel ( lowerNewState , Kind . DOOR ) ;
ensureRerender ( a . pos ) ;
return true ;
} ) ;
}
BlockPos upperPos = lowerPos . up ( ) ;
BlockState baseUpper = baseLower . with ( DoorBlock . HALF , DoubleBlockHalf . UPPER ) ;
private static void putOrReplace Anim ( B lock Pos pos , BlockState base , Kind kind ,
boolean from Open, boolean toOpen ,
float speed , long startNs ) {
Anim aLower = new Anim ( lower Pos . toImmutable ( ) , baseLower , Kind . DOOR , oldOpen , newOpen , speed , startTick ) ;
Anim aUpper = new Anim ( upperPos . toImmutable ( ) , baseUpper , Kind . DOOR , old Open, newOpen , speed , startTick ) ;
Anim anim = new Anim ( pos . toImmutable ( ) , base , kind , fromOpen , toOpen , speed , startNs ) ;
ANIMS . put ( p os. asLong ( ) , anim ) ;
}
private static void removeWithPairIfDoor ( BlockPos pos , BlockState state ) {
ANIMS . remove ( pos . asLong ( ) ) ;
if ( ! ( state . getBlock ( ) instanceof DoorBlock ) | | MC . world = = null ) return ;
BlockPos other = otherDoorHalfPos ( pos , state ) ;
if ( other ! = null ) ANIMS . remove ( other . asLong ( ) ) ;
}
private static void startDoorAnimationWithPairs ( SddConfig cfg , BlockPos anyHalfPos ,
boolean fromOpen , boolean toOpen ,
float speed , long startNs ) {
if ( MC . world = = null ) return ;
// Normaliza a LOWER para encontrar gemela de doble puerta de forma estable
BlockPos lowerPos = anyHalfPos ;
BlockState anyStateNow = MC . world . getBlockState ( anyHalfPos ) ;
if ( ! ( anyStateNow . getBlock ( ) instanceof DoorBlock ) ) return ;
if ( anyStateNow . get ( DoorBlock . HALF ) = = DoubleBlockHalf . UPPER ) {
lowerPos = anyHalfPos . down ( ) ;
synchronized ( ANIMS ) {
ANIMS . put ( lowerP os. asLong ( ) , aLower ) ;
ANIMS . put ( upperPos . asLong ( ) , aUpper ) ;
}
requestRerender ( lowerPos ) ;
requestRerender ( upperPos ) ;
}
BlockState lowerS tate = MC . world . getBlockState ( lowerPos ) ;
if ( ! ( lowerState . getBlock ( ) instanceof DoorBlock ) ) {
// fallback: al menos animar la mitad recibida
putOrReplaceAnim ( anyHalfPos , forceClosedModel ( anyStateNow , Kind . DOOR ) , Kind . DOOR , fromOpen , toOpen , speed , startNs ) ;
ensureRerender ( anyHalfPos ) ;
return ;
}
// 1) Esta puerta: lower + upper
startDoorBothHalves ( lowerPos , lowerState , fromOpen , toOpen , speed , startNs ) ;
// 2) Doble puerta (gemela), sincronizada
if ( cfg . connectDoors ) {
BlockPos otherLower = findDoubleDoorOtherLower ( lowerPos , lowerState ) ;
if ( otherLower ! = null ) {
BlockState otherLowerState = MC . world . getBlockState ( otherLower ) ;
// Solo si sigue siendo puerta
if ( otherLowerState . getBlock ( ) instanceof DoorBlock ) {
startDoorBothHalves ( otherLower , otherLowerState , fromOpen , toOpen , speed , startNs ) ;
// Forzamos rerender ya, aunque la gemela cambie un tick después: evita ver la puerta vanilla encima.
ensureRerender ( otherLower ) ;
BlockPos otherUpper = otherLower . up ( ) ;
ensureRerender ( otherUpper ) ;
}
private s tatic void removeKindAt ( BlockPos pos , Kind kind ) {
synchronized ( ANIMS ) {
ANIMS . remove ( pos . asLong ( ) ) ;
if ( kind = = Kind . DOOR ) {
ANIMS . remove ( pos . up ( ) . asLong ( ) ) ;
ANIMS . remove ( pos . down ( ) . asLong ( ) ) ;
}
}
// Forzamos rerender de esta puerta ya
ensureRerender ( lowerPos ) ;
ensureRerender ( lowerPos . up ( ) ) ;
}
private static void startDoorBothHalves ( BlockPos lowerPos , BlockState lowerState ,
boolean fromOpen , boolean toOpen ,
float speed , long startNs ) {
if ( MC . world = = null ) return ;
// LOWER
BlockState baseLower = forceClosedModel ( lowerState , Kind . DOOR ) ;
putOrReplaceAnim ( lowerPos , baseLower , Kind . DOOR , fromOpen , toOpen , speed , startNs ) ;
// UPPER (si existe)
BlockPos upperPos = lowerPos . up ( ) ;
BlockState upperState = MC . world . getBlockState ( upperPos ) ;
if ( upperState . getBlock ( ) instanceof DoorBlock ) {
BlockState baseUpper = forceClosedModel ( upperState , Kind . DOOR ) ;
putOrReplaceAnim ( upperPos , baseUpper , Kind . DOOR , fromOpen , toOpen , speed , startNs ) ;
}
}
/**
* Encuentra la otra puerta (LOWER) de una doble puerta.
* Regla:
* - misma FACING
* - hinge opuesto
* - vecina en el lado “no bisagra” (donde se juntan)
*/
private static BlockPos findDoubleDoorOtherLower ( BlockPos lowerPos , BlockState lowerState ) {
if ( MC . world = = null ) return null ;
if ( ! ( lowerState . getBlock ( ) instanceof DoorBlock ) ) return null ;
if ( lowerState . get ( DoorBlock . HALF ) ! = DoubleBlockHalf . LOWER ) return null ;
Direction facing = lowerState . get ( DoorBlock . FACING ) ;
DoorHinge hinge = lowerState . get ( DoorBlock . HINGE ) ;
// Si mi bisagra está a la IZQ, mi “lado de unión” es a la DCHA (clockwise), y viceversa.
Direction joinSide = ( hinge = = DoorHinge . LEFT ) ? facing . rotateYClockwise ( )
: facing . rotateYCounterclockwise ( ) ;
BlockPos otherLower = lowerPos . offset ( joinSide ) ;
BlockState other = MC . world . getBlockState ( otherLower ) ;
if ( ! ( other . getBlock ( ) instanceof DoorBlock ) ) return null ;
if ( other . get ( DoorBlock . HALF ) ! = DoubleBlockHalf . LOWER ) return null ;
if ( other . get ( DoorBlock . FACING ) ! = facing ) return null ;
DoorHinge otherHinge = other . get ( DoorBlock . HINGE ) ;
if ( otherHinge = = hinge ) return null ; // deben ser opuestas
return otherLower ;
}
private static BlockPos otherDoorHalfPos ( BlockPos pos , BlockState state ) {
if ( ! ( state . getBlock ( ) instanceof DoorBlock ) ) return null ;
return ( state . get ( DoorBlock . HALF ) = = DoubleBlockHalf . UPPER ) ? pos . down ( ) : pos . up ( ) ;
}
private static void ensureRerender ( BlockPos pos ) {
if ( MC . world = = null ) return ;
// Esta existe (la estás mixineando). Esto fuerza al motor a reconstruir el chunk.
World w = MC . world ;
BlockState s = w . getBlockState ( pos ) ;
w . scheduleBlockRerenderIfNeeded ( pos , s , s ) ;
}
// === Ã   ngulos y transforms ===
private static float lerpAngleDeg ( boolean fromOpen , boolean toOpen , float t , Kind kind , BlockState state ) {
float a = angleFor ( kind , state , fromOpen ) ;
@@ -297,30 +218,14 @@ public final class SddAnimator {
private static float angleFor ( Kind kind , BlockState state , boolean open ) {
if ( ! open ) return 0 . 0f ;
switch ( kind ) {
return switch ( kind ) {
case DOOR - > {
Direction facing = state . get ( DoorBlock . FACING ) ;
DoorHinge hinge = state . get ( DoorBlock . HINGE ) ;
// Igual que vanilla (en shapes): al abrir, la “dirección efectiva” rota ±90 según hinge.
Direction openDir = ( hinge = = DoorHinge . LEFT ) ? facing . rotateYClockwise ( )
: facing . rotateYCounterclockwise ( ) ;
float yawClosed = Direction . getHorizontalDegreesOrThrow ( facing ) ;
float yawOpen = Direction . getHorizontalDegreesOrThrow ( openDir ) ;
return MathHelper . wrapDegrees ( yawOpen - yawClosed ) ; // +90 o -90
yield ( hinge = = DoorHinge . RIGHT ) ? - 90 . 0f : 90 . 0f ;
}
case TRAPDOOR - > {
// (lo afinamos luego) 90º
return 90 . 0f ;
}
case FENCE_GATE - > {
// (lo afinamos luego) 90º
return 90 . 0f ;
}
}
return 0 . 0f ;
case TRAPDOOR - > 90 . 0f ;
case FENCE_GATE - > 90 . 0f ;
} ;
}
private static void applyTransform ( Kind kind , BlockState state , float angleDeg , MatrixStack matrices ) {
@@ -331,37 +236,130 @@ public final class SddAnimator {
}
}
/**
* FIX del pivote:
* Estabas rotando alrededor de una “punta† (esquina). Una puerta real rota alrededor de la là   nea de bisagra:
* - Coordenada del eje de bisagra (hingeSide): en el BORDE del modelo (min/max del bounding box)
* - Coordenada perpendicular: en el CENTRO del modelo (centro del bounding box)
*
* Esto quita el efecto de que la animacià  ³n “empiece por encima/encima† y que parezca que se desplaza.
*/
private static void transformDoor ( BlockState state , float angleDeg , MatrixStack matrices ) {
if ( MC . world = = null ) return ;
Direction facing = state . get ( DoorBlock . FACING ) ;
DoorHinge hinge = state . get ( DoorBlock . HINGE ) ;
DoorHinge hinge = state . get ( DoorBlock . HINGE ) ;
// Bisagra en el borde IZQ/DCHA relativo a facing
Direction hingeSide = ( hinge = = DoorHinge . LEFT ) ? facing . rotateYCounterc lockwise ( )
: facing . rotateYClockwise ( ) ;
Direction hingeSide = ( hinge = = DoorHinge . RIGHT )
? facing . rotateYClockwise ( )
: facing . rotateYCounterc lockwise ( ) ;
float pivotX = ( h ing eSide = = Direction . EAST ) ? 1 . 0f : ( hingeSide = = Direction . WEST ? 0 . 0f : 0 . 5f ) ;
float pivotZ = ( hingeSide = = Direction . SOUTH ) ? 1 . 0f : ( hingeSide = = Direction . NORTH ? 0 . 0f : 0 . 5f ) ;
Box bb = state . getOutl ineShape ( MC . world , BlockPos . ORIGIN , ShapeContext . absent ( ) ) . getBoundingBox ( ) ;
float cx = ( float ) ( ( bb . minX + bb . maxX ) * 0 . 5 ) ;
float cz = ( float ) ( ( bb . minZ + bb . maxZ ) * 0 . 5 ) ;
float pivotX = cx ;
float pivotZ = cz ;
switch ( hingeSide ) {
case EAST - > pivotX = ( float ) bb . maxX ;
case WEST - > pivotX = ( float ) bb . minX ;
case SOUTH - > pivotZ = ( float ) bb . maxZ ;
case NORTH - > pivotZ = ( float ) bb . minZ ;
default - > {
pivotX = cx ;
pivotZ = cz ;
}
}
float thickness = ( facing = = Direction . NORTH | | facing = = Direction . SOUTH )
? ( float ) ( bb . maxZ - bb . minZ )
: ( float ) ( bb . maxX - bb . minX ) ;
float halfT = thickness * 0 . 5f ;
float dX = 0 . 0f ;
float dZ = 0 . 0f ;
switch ( facing ) {
case NORTH - > dZ = halfT ;
case SOUTH - > dZ = - halfT ;
case WEST - > dX = halfT ;
case EAST - > dX = - halfT ;
default - > {
dX = 0 . 0f ;
dZ = 0 . 0f ;
}
}
double rad = Math . toRadians ( angleDeg ) ;
float cos = ( float ) Math . cos ( rad ) ;
float sin = ( float ) Math . sin ( rad ) ;
float rotDX = dX * cos - dZ * sin ;
float rotDZ = dX * sin + dZ * cos ;
float shiftX = dX - rotDX ;
float shiftZ = dZ - rotDZ ;
matrices . translate ( shiftX , 0 . 0f , shiftZ ) ;
matrices . translate ( pivotX , 0 . 0f , pivotZ ) ;
matrices . multiply ( RotationAxis . POSITIVE_Y . rotationDegrees ( angleDeg ) ) ;
matrices . translate ( - pivotX , 0 . 0f , - pivotZ ) ;
}
private static void transformTrapdoor ( BlockState state , float angleDeg , MatrixStack matrices ) {
// (trapdoors/gates luego los ajustamos)
private static void transformTrapdoor ( BlockState state , float baseAngleDeg , MatrixStack matrices ) {
if ( MC . world = = null ) return ;
Direction facing = state . get ( TrapdoorBlock . FACING ) ;
BlockHalf half = state . get ( TrapdoorBlock . HALF ) ;
float pivotX = ( facing = = Direction . EAST ) ? 1 . 0f : ( facing = = Direction . WEST ? 0 . 0f : 0 . 5f ) ;
float pivotZ = ( facing = = Direction . SOUTH ) ? 1 . 0f : ( facing = = Direction . NORTH ? 0 . 0f : 0 . 5f ) ;
VoxelShape collShape = state . getCollisionShape ( MC . world , BlockPos . ORIGIN , ShapeContext . absent ( ) ) ;
Box bb = collShape . isEmpty ( )
? state . getOutlineShape ( MC . world , BlockPos . ORIGIN , ShapeContext . absent ( ) ) . getBoundingBox ( )
: collShape . getBoundingBox ( ) ;
matrices . translate ( pivotX , 0 . 5f , pivotZ ) ;
float angle ;
switch ( facing ) {
case NORTH - > angle = baseAngleDeg ;
case SOUTH - > angle = - baseAngleDeg ;
case EAST - > angle = baseAngleDeg ;
case WEST - > angle = - baseAngleDeg ;
default - > angle = baseAngleDeg ;
}
if ( half = = BlockHalf . TOP ) angle = - angle ;
float pivotX = ( float ) ( ( bb . minX + bb . maxX ) * 0 . 5 ) ;
float pivotZ = ( float ) ( ( bb . minZ + bb . maxZ ) * 0 . 5 ) ;
Direction hingeSide = facing . getOpposite ( ) ;
switch ( hingeSide ) {
case NORTH - > pivotZ = ( float ) bb . minZ ;
case SOUTH - > pivotZ = ( float ) bb . maxZ ;
case WEST - > pivotX = ( float ) bb . minX ;
case EAST - > pivotX = ( float ) bb . maxX ;
default - > {
pivotX = ( float ) ( ( bb . minX + bb . maxX ) * 0 . 5 ) ;
pivotZ = ( float ) ( ( bb . minZ + bb . maxZ ) * 0 . 5 ) ;
}
}
float thickness = ( float ) ( bb . maxY - bb . minY ) ;
float halfT = thickness > 0 . 0f ? thickness * 0 . 5f : ( 3 . 0f / 32 . 0f ) ;
float pivotYClosed = ( half = = BlockHalf . TOP ) ? ( float ) bb . maxY - halfT : ( float ) bb . minY + halfT ;
float pivotYOpen = ( half = = BlockHalf . TOP ) ? ( float ) bb . maxY : ( float ) bb . minY ;
float progress = MathHelper . clamp ( Math . abs ( angle ) / 90 . 0f , 0 . 0f , 1 . 0f ) ;
float pivotY = MathHelper . lerp ( progress , pivotYClosed , pivotYOpen ) ;
float pivotXInterp = pivotX ;
float pivotZInterp = pivotZ ;
matrices . translate ( pivotXInterp , pivotY , pivotZInterp ) ;
if ( facing = = Direction . NORTH | | facing = = Direction . SOUTH ) {
matrices . multiply ( RotationAxis . POSITIVE_X . rotationDegrees ( angleDeg ) ) ;
matrices . multiply ( RotationAxis . POSITIVE_X . rotationDegrees ( angle ) ) ;
} else {
matrices . multiply ( RotationAxis . POSITIVE_Z . rotationDegrees ( - angleDeg ) ) ;
matrices . multiply ( RotationAxis . POSITIVE_Z . rotationDegrees ( angle ) ) ;
}
matrices . translate ( - pivotX , - 0 . 5f , - pivotZ ) ;
matrices . translate ( - pivotXInterp , - pivotY , - pivotZInterp ) ;
}
private static void transformFenceGate ( BlockState state , float angleDeg , MatrixStack matrices ) {
@@ -370,6 +368,8 @@ public final class SddAnimator {
matrices . translate ( - 0 . 5f , 0 . 0f , - 0 . 5f ) ;
}
// === Utilidades ===
private static BlockState forceClosedModel ( BlockState s , Kind kind ) {
return switch ( kind ) {
case DOOR - > s . with ( DoorBlock . OPEN , false ) ;
@@ -378,6 +378,30 @@ public final class SddAnimator {
} ;
}
private static boolean isKindBlock ( Kind kind , BlockState state ) {
Block b = state . getBlock ( ) ;
return switch ( kind ) {
case DOOR - > b instanceof DoorBlock ;
case TRAPDOOR - > b instanceof TrapdoorBlock ;
case FENCE_GATE - > b instanceof FenceGateBlock ;
} ;
}
private static void requestRerender ( BlockPos pos ) {
if ( MC . worldRenderer = = null | | MC . world = = null ) return ;
BlockState st = MC . world . getBlockState ( pos ) ;
MC . worldRenderer . updateBlock ( MC . world , pos , st , st , 0 ) ;
MC . worldRenderer . scheduleBlockRenders (
pos . getX ( ) , pos . getY ( ) , pos . getZ ( ) ,
pos . getX ( ) , pos . getY ( ) , pos . getZ ( )
) ;
MC . worldRenderer . scheduleChunkRender (
pos . getX ( ) > > 4 ,
pos . getY ( ) > > 4 ,
pos . getZ ( ) > > 4
) ;
}
private static boolean isOpen ( BlockState s ) {
Block b = s . getBlock ( ) ;
if ( b instanceof DoorBlock ) return s . get ( DoorBlock . OPEN ) ;
@@ -404,7 +428,7 @@ public final class SddAnimator {
return switch ( kind ) {
case DOOR - > cfg . animateDoors ;
case TRAPDOOR - > cfg . animateTrapdoors ;
case FENCE_GATE - > cfg . animateFenceGates ;
case FENCE_GATE - > getBool ( cfg , " animateFenceGates " , false ) ;
} ;
}
@@ -412,14 +436,43 @@ public final class SddAnimator {
return switch ( kind ) {
case DOOR - > cfg . doorSpeed ;
case TRAPDOOR - > cfg . trapdoorSpeed ;
case FENCE_GATE - > cfg . fenceGateSpeed ;
case FENCE_GATE - > getFloat ( cfg , " fenceGateSpeed " , 1 . 0f ) ;
} ;
}
private static float easeInOut ( float t ) {
return t * t * ( 3 . 0f - 2 . 0f * t ) ; // smoothstep
return t * t * ( 3 . 0f - 2 . 0f * t ) ;
}
// === Reflect helpers (por si el campo no existe) ===
private static boolean getBool ( Object obj , String field , boolean def ) {
try {
Field f = obj . getClass ( ) . getDeclaredField ( field ) ;
f . setAccessible ( true ) ;
Object v = f . get ( obj ) ;
return ( v instanceof Boolean ) ? ( Boolean ) v : def ;
} catch ( Throwable ignored ) {
return def ;
}
}
private static float getFloat ( Object obj , String field , float def ) {
try {
Field f = obj . getClass ( ) . getDeclaredField ( field ) ;
f . setAccessible ( true ) ;
Object v = f . get ( obj ) ;
if ( v instanceof Float ) return ( Float ) v ;
if ( v instanceof Double ) return ( ( Double ) v ) . floatValue ( ) ;
if ( v instanceof Number ) return ( ( Number ) v ) . floatValue ( ) ;
return def ;
} catch ( Throwable ignored ) {
return def ;
}
}
// === Tipos ===
public enum Kind { DOOR , TRAPDOOR , FENCE_GATE }
private static final class Anim {
@@ -428,35 +481,34 @@ public final class SddAnimator {
final Kind kind ;
final boolean fromOpen ;
final boolean toOpen ;
final float speed ;
final long startNs ;
final long durationNs ;
Anim ( BlockPos pos , BlockState baseState , Kind kind ,
boolean fromOpen , boolean toOpen , float speed , long startNs ) {
final double startTick ;
final double durationTicks ;
Anim ( BlockPos pos , BlockState baseState , Kind kind , boolean fromOpen , boolean toOpen , float speed , long startTick ) {
this . pos = pos ;
this . baseState = baseState ;
this . kind = kind ;
this . fromOpen = fromOpen ;
this . toOpen = toOpen ;
this . speed = speed ;
this . startNs = startNs ;
float dur = BASE_DURATION_S / Math . max ( 0 . 001f , speed) ;
if ( dur < MIN_DURATION_S ) dur = MIN_DURATION_S ;
this . durationNs = ( long ) ( dur * 1_000_000_000L ) ;
double durS = BASE_DURATION_S / speed ;
if ( durS < 0 . 05 ) durS = 0 . 05 ;
this . startTick = ( double ) startTick ;
this . durationTicks = Math . max ( 1 . 0 , durS * 20 . 0 ) ;
}
float progress01 ( long nowNs ) {
long d t = nowNs - startN s ;
if ( d t < = 0 ) return 0 . 0f ;
if ( d t > = durationNs ) return 1 . 0f ;
return ( float ) dt / ( float ) durationNs ;
float progress01 ( double nowTick ) {
double t = ( nowTick - startTick ) / durationTick s ;
if ( t < = 0 . 0 ) return 0 . 0f ;
if ( t > = 1 . 0 ) return 1 . 0f ;
return ( float ) t ;
}
boolean isFinished ( long nowNs ) {
return nowNs - startNs > = durationN s ;
boolean isFinished ( double nowTick ) {
return ( nowTick - startTick ) > = durationTick s ;
}
}
}