Phase 2.7 : animation registry + tick handler + placeholder assets
TiedUpAnimationRegistry.CONTEXT_STAND_IDLE instancié au FMLCommonSetupEvent via DirectStaticAnimation + armature BIPED. Placeholder JSON 2-keyframes identity (joueur statique) à assets/tiedup/animmodels/animations/ jusqu'à ce qu'un export Blender authored remplace. biped.json de même (hiérarchie identity) placé dans assets/tiedup/armatures/ — parse via JsonAssetLoader mais pas encore chargé au runtime (l'armature reste procédurale côté Java). RigAnimationTickHandler tick chaque player visible côté client (phase END) : - patch.getAnimator().tick() → avance les layers EF - trigger playAnimation(CONTEXT_STAND_IDLE, 0.2f) quand motion=IDLE et anim courante ≠ CONTEXT_STAND_IDLE (idempotent) - try/catch per-entity avec dedup des erreurs par UUID (pattern TiedUpRenderEngine.loggedRenderErrors) - skip si level null / paused PlayerPatch.initAnimator bind désormais IDLE → CONTEXT_STAND_IDLE quand le registry est prêt (fallback EMPTY_ANIMATION si patch construit avant setup). Voir docs/plans/rig/ASSETS_NEEDED.md pour la spec des assets authored définitifs (anim idle swing respiration 3 keyframes + offsets biped anatomiques).
This commit is contained in:
@@ -140,6 +140,11 @@ public class TiedUpMod {
|
||||
|
||||
// RIG Phase 2 — dispatcher EntityType → EntityPatch (PLAYER Phase 2, NPCs Phase 5)
|
||||
event.enqueueWork(com.tiedup.remake.rig.patch.EntityPatchProvider::registerEntityPatches);
|
||||
|
||||
// RIG Phase 2.7 — registre des StaticAnimation (CONTEXT_STAND_IDLE).
|
||||
// Placeholder JSON procédural jusqu'à ce que les assets Blender arrivent
|
||||
// (cf. docs/plans/rig/ASSETS_NEEDED.md).
|
||||
event.enqueueWork(com.tiedup.remake.rig.TiedUpAnimationRegistry::initStaticAnimations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
156
src/main/java/com/tiedup/remake/rig/TiedUpAnimationRegistry.java
Normal file
156
src/main/java/com/tiedup/remake/rig/TiedUpAnimationRegistry.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
|
||||
/**
|
||||
* Phase 2.7 — registry central des {@link StaticAnimation} TiedUp. Expose les
|
||||
* accessors statiques (ex. {@link #CONTEXT_STAND_IDLE}) utilisés par les
|
||||
* patches + tick handler pour jouer les animations idle / walk / etc.
|
||||
*
|
||||
* <h2>Placeholder assets</h2>
|
||||
* <p>Les JSON associés sont des <b>placeholders procéduraux</b> (2 keyframes
|
||||
* identity) à remplacer par des assets Blender-authored. Voir
|
||||
* {@code docs/plans/rig/ASSETS_NEEDED.md} section 2 pour la spec de l'anim
|
||||
* idle définitive (swing respiration subtle 3 keyframes, 2s boucle).</p>
|
||||
*
|
||||
* <h2>Lifecycle</h2>
|
||||
* <ul>
|
||||
* <li>{@link #initStaticAnimations()} appelé au {@code FMLCommonSetupEvent}
|
||||
* (via {@code event.enqueueWork(...)}). Crée les instances
|
||||
* {@link DirectStaticAnimation} — pas de chargement JSON ici, juste les
|
||||
* métadonnées (registry name, armature, repeat flag).</li>
|
||||
* <li>Le chargement effectif du JSON ({@code JsonAssetLoader}) est lazy :
|
||||
* à la première lecture de {@link StaticAnimation#getAnimationClip()},
|
||||
* donc typiquement à la première frame où l'animation est jouée.</li>
|
||||
* <li>Si l'asset JSON est absent / corrompu, {@code StaticAnimation.loadAnimation}
|
||||
* relance une {@code AssetLoadingException}. Le tick handler
|
||||
* ({@link com.tiedup.remake.rig.tick.RigAnimationTickHandler}) attrape ces
|
||||
* throwables pour éviter un crash complet.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Dist</h2>
|
||||
* <p>Les animations tournent côté client (l'{@code Animator} est créé via
|
||||
* {@code ClientAnimator::getAnimator} côté physical client), donc le registry
|
||||
* est indépendant du side pour le bootstrap mais toute la lecture JSON passe
|
||||
* par {@code Minecraft.getInstance().getResourceManager()} côté client. Les
|
||||
* champs sont accessibles côté serveur (validation / logs), seul
|
||||
* {@link StaticAnimation#loadAnimation()} est client-heavy (et protégé par
|
||||
* la dispatch server/client du {@code AnimationManager.getAnimationResourceManager()}).</p>
|
||||
*/
|
||||
public final class TiedUpAnimationRegistry {
|
||||
|
||||
private TiedUpAnimationRegistry() {}
|
||||
|
||||
/** Registry name de l'anim idle par défaut (résolue en
|
||||
* {@code assets/tiedup/animmodels/animations/context_stand_idle.json}). */
|
||||
public static final ResourceLocation CONTEXT_STAND_IDLE_ID =
|
||||
ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "context_stand_idle");
|
||||
|
||||
/**
|
||||
* Anim idle par défaut — joue quand aucune motion active. Placeholder 2
|
||||
* keyframes identity (joueur sans mouvement visible) jusqu'à ce qu'un
|
||||
* asset authored Blender arrive.
|
||||
*
|
||||
* <p><b>Attention init order</b> : ce field est {@code null} tant que
|
||||
* {@link #initStaticAnimations()} n'a pas tourné. Les sites qui
|
||||
* référencent ce field doivent être gardés par un null-check, ou être
|
||||
* appelés post-setup (tick handler, patch init, etc.).</p>
|
||||
*
|
||||
* <p>Utilise {@link DirectStaticAnimation} (vs un hand-written
|
||||
* {@code StaticAnimation}) pour hériter du pattern accessor=self +
|
||||
* registryName() utilisés dans {@link TiedUpRigRegistry#EMPTY_ANIMATION}.</p>
|
||||
*/
|
||||
public static DirectStaticAnimation CONTEXT_STAND_IDLE;
|
||||
|
||||
/**
|
||||
* Construit les {@link StaticAnimation} TiedUp. À appeler exactement une
|
||||
* fois par game, en {@code FMLCommonSetupEvent.enqueueWork(...)}.
|
||||
*
|
||||
* <p>Pas de chargement JSON ici — juste l'instanciation des accessors.
|
||||
* La première lecture de {@code getAnimationClip()} déclenchera le load
|
||||
* via {@link com.tiedup.remake.rig.asset.JsonAssetLoader}.</p>
|
||||
*
|
||||
* <p>Idempotent (re-appel sans effet visible) mais pas thread-safe. Ne
|
||||
* devrait jamais être appelé hors du mod bus.</p>
|
||||
*/
|
||||
public static void initStaticAnimations() {
|
||||
if (CONTEXT_STAND_IDLE != null) {
|
||||
// Déjà init (hot-reload setup, test double-init). Log debug seulement.
|
||||
TiedUpRigConstants.LOGGER.debug(
|
||||
"TiedUpAnimationRegistry.initStaticAnimations: déjà initialisé, skip."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// transitionTime=GENERAL (6 ticks) + isRepeat=true + registryName +
|
||||
// armature=BIPED. L'ordre match le ctor DirectStaticAnimation(float, boolean, ResourceLocation, AssetAccessor).
|
||||
CONTEXT_STAND_IDLE = new DirectStaticAnimation(
|
||||
TiedUpRigConstants.GENERAL_ANIMATION_TRANSITION_TIME,
|
||||
/* isRepeat */ true,
|
||||
CONTEXT_STAND_IDLE_ID,
|
||||
TiedUpArmatures.BIPED
|
||||
);
|
||||
|
||||
// Layer BASE + priority LOWEST — idle default, écrasable par toute
|
||||
// autre anim. Sans ces props la default du StaticAnimation (LOWEST /
|
||||
// BASE_LAYER) s'applique déjà — on les set explicitement pour la doc.
|
||||
setLowestBaseLayer(CONTEXT_STAND_IDLE);
|
||||
|
||||
TiedUpRigConstants.LOGGER.info(
|
||||
"TiedUpAnimationRegistry: CONTEXT_STAND_IDLE registered ({})",
|
||||
CONTEXT_STAND_IDLE_ID
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
// Fallback : log + laisse CONTEXT_STAND_IDLE null. Le tick handler
|
||||
// verra null et skippera silencieusement. Évite de tout casser si
|
||||
// un asset placeholder est malformé en dev.
|
||||
TiedUpRigConstants.LOGGER.error(
|
||||
"TiedUpAnimationRegistry: init échoué pour CONTEXT_STAND_IDLE — "
|
||||
+ "animation idle désactivée. Voir docs/plans/rig/ASSETS_NEEDED.md §2.",
|
||||
t
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper — set LayerType=BASE_LAYER + Priority=LOWEST sur une anim.
|
||||
* Évite de forcer les callers à importer {@link ClientAnimationProperties}
|
||||
* + {@link Layer}.
|
||||
*
|
||||
* <p>{@code @OnlyIn(CLIENT)} indirect : les properties
|
||||
* {@code LAYER_TYPE}/{@code PRIORITY} sont client-only mais leur écriture
|
||||
* via {@code addProperty} ne déclenche pas de class-load de
|
||||
* {@code net.minecraft.client.*}. Le tag d'{@code @OnlyIn} serait
|
||||
* incorrect ici (le method serait appelé depuis commonSetup). La safety
|
||||
* réelle est assurée par le fait que les properties sont juste stockées
|
||||
* dans la map et lues plus tard sur client uniquement (via
|
||||
* {@code getLayerType()}/{@code getPriority()} tagués
|
||||
* {@code @OnlyIn(CLIENT)}).</p>
|
||||
*/
|
||||
private static void setLowestBaseLayer(StaticAnimation anim) {
|
||||
anim.addProperty(ClientAnimationProperties.LAYER_TYPE, Layer.LayerType.BASE_LAYER);
|
||||
anim.addProperty(ClientAnimationProperties.PRIORITY, Layer.Priority.LOWEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper — vrai ssi la static anim de référence a fini l'init (tick
|
||||
* handler l'utilise en early-return quand Phase 2.7 assets sont absents
|
||||
* en dev test).
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static boolean isReady() {
|
||||
return CONTEXT_STAND_IDLE != null;
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ package com.tiedup.remake.rig.patch;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import com.tiedup.remake.rig.TiedUpAnimationRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpArmatures;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.anim.LivingMotions;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.armature.HumanoidArmature;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
@@ -152,7 +154,16 @@ public abstract class PlayerPatch<T extends Player> extends LivingEntityPatch<T>
|
||||
@Override
|
||||
protected void initAnimator(Animator animator) {
|
||||
super.initAnimator(animator);
|
||||
animator.addLivingAnimation(LivingMotions.IDLE, TiedUpRigRegistry.EMPTY_ANIMATION);
|
||||
// Phase 2.7 : si le registry a init, on bind sur CONTEXT_STAND_IDLE
|
||||
// (placeholder procédural 2-keyframes identity — voir
|
||||
// TiedUpAnimationRegistry + ASSETS_NEEDED.md §2). Sinon fallback sur
|
||||
// EMPTY_ANIMATION — peut arriver si le patch est construit avant que
|
||||
// FMLCommonSetupEvent n'ait tourné (rare mais pas impossible en dev).
|
||||
StaticAnimation idle = TiedUpAnimationRegistry.CONTEXT_STAND_IDLE;
|
||||
animator.addLivingAnimation(
|
||||
LivingMotions.IDLE,
|
||||
idle != null ? idle.getAccessor() : TiedUpRigRegistry.EMPTY_ANIMATION
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.tick;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.rig.TiedUpAnimationRegistry;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.anim.LivingMotions;
|
||||
import com.tiedup.remake.rig.anim.client.ClientAnimator;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import com.tiedup.remake.rig.patch.TiedUpCapabilities;
|
||||
|
||||
/**
|
||||
* Phase 2.7 — tick handler RIG côté client. Pour chaque player visible :
|
||||
*
|
||||
* <ol>
|
||||
* <li>Récupère le {@link LivingEntityPatch} via
|
||||
* {@link TiedUpCapabilities#getEntityPatch(net.minecraft.world.entity.Entity, Class)}</li>
|
||||
* <li>Ticke l'{@link Animator} (avance les layers EF, gère les transitions,
|
||||
* etc.)</li>
|
||||
* <li>Si aucune animation "réelle" n'est active (= on joue encore
|
||||
* l'{@code EMPTY_ANIMATION} du fallback initial), déclenche une
|
||||
* transition vers {@link TiedUpAnimationRegistry#CONTEXT_STAND_IDLE}
|
||||
* (0.2s) — preuve vivante que le pipeline tourne Phase 2.7.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Choix de design</h2>
|
||||
* <ul>
|
||||
* <li><b>Phase END</b> : on tick après la logique vanilla de la frame pour
|
||||
* que l'animator voit le dernier {@code yBodyRot}, position, vélocité
|
||||
* écrits par MC. Pattern EF standard.</li>
|
||||
* <li><b>Itération {@code level.players()}</b> : uniquement les joueurs
|
||||
* dans la dimension courante — les autres ont leurs patches suspendus
|
||||
* de toutes façons. Évite d'itérer les entities tiers non-patchées
|
||||
* (Phase 5+ quand NPCs auront RIG).</li>
|
||||
* <li><b>Error handling per-entity</b> : try/catch autour de chaque patch
|
||||
* tick. Un patch qui throw ne cascade pas sur les autres. L'erreur
|
||||
* est logguée <b>une seule fois par UUID</b> pour éviter de spammer
|
||||
* la console.</li>
|
||||
* <li><b>Registry pas ready → noop</b> : si
|
||||
* {@link TiedUpAnimationRegistry#CONTEXT_STAND_IDLE} est null (setup
|
||||
* pas encore exécuté, ou fallback d'échec), on ne crash pas — on
|
||||
* skippe juste le trigger idle. L'animator tourne quand même
|
||||
* (EMPTY_ANIMATION en boucle).</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Futur Phase 3+</h2>
|
||||
* <p>Ce handler est minimal — Phase 3 ajoutera :</p>
|
||||
* <ul>
|
||||
* <li>Détection motion (WALK, SIT, KNEEL, etc.) via {@code updateMotion}
|
||||
* et binding dynamic des anims correspondantes</li>
|
||||
* <li>Resolve context bondage (arms_bound / legs_bound / etc.) via
|
||||
* {@code RigContextResolver}</li>
|
||||
* <li>Tick des NPCs TiedUp quand leurs patches seront enregistrés</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Cf. {@code docs/plans/rig/MIGRATION.md} Phase 3 — Animation consumers.</p>
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public final class RigAnimationTickHandler {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
/** Set des UUIDs ayant déjà loggé une erreur tick — évite spam console. */
|
||||
private static final Set<UUID> LOGGED_ERRORS = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/** Cap de taille — reset si dépassé (évite leak en session infinie). */
|
||||
private static final int MAX_LOGGED_UUIDS = 1024;
|
||||
|
||||
private RigAnimationTickHandler() {}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
// Loading screen, splash, disconnect, F3+T reload in progress — level null.
|
||||
// Early-return silencieux (évite les logs parasites).
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pause menu / options screen — on skip le tick anim pour matcher
|
||||
// le comportement vanilla MC (tick world paused).
|
||||
if (mc.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cap de taille du set loggé — reset préventif.
|
||||
if (LOGGED_ERRORS.size() > MAX_LOGGED_UUIDS) {
|
||||
LOGGED_ERRORS.clear();
|
||||
}
|
||||
|
||||
// Copie défensive : le set level.players() peut muter pendant notre
|
||||
// itération si un joueur join/leave (MP). Le CME-safe serait itérer
|
||||
// sur une copie. On tolère le CME via try/catch, le coût copy n'en
|
||||
// vaut pas la peine pour une liste typiquement petite (~10 joueurs
|
||||
// visibles max).
|
||||
for (Player player : new HashSet<>(mc.level.players())) {
|
||||
tickPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticke le patch RIG d'un joueur. Noop si :
|
||||
* <ul>
|
||||
* <li>Pas de capability attachée (ex : le type d'entity n'est pas dans
|
||||
* {@code EntityPatchProvider.CAPABILITIES})</li>
|
||||
* <li>L'{@code Animator} est null (entitypatch pas encore onConstructed)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Toute exception thrown par le patch / animator est caught et logguée
|
||||
* une seule fois par UUID — le player reste rendu par le fallback vanilla
|
||||
* du pipeline render engine (cf. {@code TiedUpRenderEngine.onRenderLiving}).</p>
|
||||
*/
|
||||
private static void tickPlayer(Player player) {
|
||||
LivingEntityPatch<?> patch =
|
||||
TiedUpCapabilities.getEntityPatch(player, LivingEntityPatch.class);
|
||||
|
||||
if (patch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Animator animator = patch.getAnimator();
|
||||
if (animator == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = player.getUUID();
|
||||
|
||||
try {
|
||||
// 1. Tick de l'animator — avance les layers + gère les transitions
|
||||
// EF natifs. C'est ce que le MixinLivingEntity.tick() ferait en
|
||||
// Phase 3+ pattern push EF (cf. MIGRATION.md §2.2.1). Ici on
|
||||
// tick côté client uniquement pour Phase 2.7.
|
||||
animator.tick();
|
||||
|
||||
// 2. Trigger transition vers CONTEXT_STAND_IDLE si rien de mieux
|
||||
// n'est actif. Idempotent : ne re-déclenche pas si l'anim
|
||||
// courante est déjà CONTEXT_STAND_IDLE.
|
||||
maybePlayIdle(patch, animator);
|
||||
|
||||
} catch (Throwable t) {
|
||||
if (LOGGED_ERRORS.add(uuid)) {
|
||||
LOGGER.error(
|
||||
"RigAnimationTickHandler: tick animator failed for player {} ({}) — "
|
||||
+ "subsequent errors suppressed for this UUID.",
|
||||
player.getGameProfile().getName(), uuid, t
|
||||
);
|
||||
}
|
||||
// Pas de rethrow — le joueur continue d'être rendu en vanilla (ou
|
||||
// stale pose). Le pipeline render dispose de son propre fallback.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche la transition vers {@code CONTEXT_STAND_IDLE} si :
|
||||
* <ul>
|
||||
* <li>Le registry est ready (placeholder asset loadé)</li>
|
||||
* <li>Le patch est dans {@code LivingMotions.IDLE} (pas en action
|
||||
* particulière — walk, sit, etc. Phase 3+)</li>
|
||||
* <li>L'animation actuellement jouée sur le base layer n'est pas déjà
|
||||
* CONTEXT_STAND_IDLE (évite le re-trigger à chaque tick)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Transition 0.2s (4 ticks @ 20tps) pour un fondu doux avec
|
||||
* l'EMPTY_ANIMATION de départ. Ça reste court — si Phase 3+ veut un fondu
|
||||
* plus long on passera la valeur en param.</p>
|
||||
*/
|
||||
private static void maybePlayIdle(LivingEntityPatch<?> patch, Animator animator) {
|
||||
if (!TiedUpAnimationRegistry.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (patch.getCurrentLivingMotion() != LivingMotions.IDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(animator instanceof ClientAnimator clientAnimator)) {
|
||||
// Server-side Animator — rien à rendre. Phase 3+ si on veut sync
|
||||
// motion serveur, c'est là qu'on le fera.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check si la CONTEXT_STAND_IDLE est déjà l'anim courante du base layer.
|
||||
// getPlayerFor(null) retourne base layer player (non-null en EF).
|
||||
AssetAccessor<? extends DynamicAnimation> currentAnim =
|
||||
clientAnimator.baseLayer.animationPlayer.getAnimation();
|
||||
|
||||
StaticAnimation target = TiedUpAnimationRegistry.CONTEXT_STAND_IDLE;
|
||||
|
||||
if (currentAnim != null && currentAnim.get() != null) {
|
||||
// Compare directement les instances — les StaticAnimation sont
|
||||
// singletons (registry constructor pattern). equals() fallback
|
||||
// sur id et les accessors sont null pour EMPTY, donc ID-based
|
||||
// comparison pas fiable ici. Instance check suffit.
|
||||
if (currentAnim.get() == target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
clientAnimator.playAnimation(target.getAccessor(), 0.2F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset des erreurs loggées — utile aux tests unitaires et au F3+T
|
||||
* reload (aligné avec {@code TiedUpRenderEngine.onAddLayers}).
|
||||
*/
|
||||
public static void resetLoggedErrors() {
|
||||
LOGGED_ERRORS.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"_comment": "PLACEHOLDER Phase 2.7 — animation idle 2-keyframes identity (joueur statique, pas de balancement). Boucle 2 secondes. DOIT parser avec JsonAssetLoader.loadClipForAnimation(). À remplacer par un export Blender authored (respiration/balancement subtle) quand dispo. Voir docs/plans/rig/ASSETS_NEEDED.md section 2.",
|
||||
"format": "ATTRIBUTES",
|
||||
"armature": "tiedup:biped",
|
||||
"animation": [
|
||||
{
|
||||
"name": "Root",
|
||||
"time": [0.0, 2.0],
|
||||
"transform": [
|
||||
{ "loc": [0.0, 0.0, 0.0], "rot": [1.0, 0.0, 0.0, 0.0], "sca": [1.0, 1.0, 1.0] },
|
||||
{ "loc": [0.0, 0.0, 0.0], "rot": [1.0, 0.0, 0.0, 0.0], "sca": [1.0, 1.0, 1.0] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
225
src/main/resources/assets/tiedup/armatures/biped.json
Normal file
225
src/main/resources/assets/tiedup/armatures/biped.json
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"_comment": "PLACEHOLDER Phase 2.7 — identity transforms (joints empilés à l'origine). Doit parser avec JsonAssetLoader.loadArmature() mais n'est PAS encore utilisé au runtime (TiedUpArmatures.BIPED est construit proceduralement en Java). À remplacer par un export Blender addon Antikythera-Studios quand dispo. Voir docs/plans/rig/ASSETS_NEEDED.md section 1.",
|
||||
"armature": {
|
||||
"armature_format": "ATTRIBUTES",
|
||||
"joints": [
|
||||
"Root",
|
||||
"Thigh_R",
|
||||
"Leg_R",
|
||||
"Knee_R",
|
||||
"Thigh_L",
|
||||
"Leg_L",
|
||||
"Knee_L",
|
||||
"Torso",
|
||||
"Chest",
|
||||
"Head",
|
||||
"Shoulder_R",
|
||||
"Arm_R",
|
||||
"Hand_R",
|
||||
"Tool_R",
|
||||
"Elbow_R",
|
||||
"Shoulder_L",
|
||||
"Arm_L",
|
||||
"Hand_L",
|
||||
"Tool_L",
|
||||
"Elbow_L"
|
||||
],
|
||||
"hierarchy": [
|
||||
{
|
||||
"name": "Root",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Thigh_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Leg_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Knee_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thigh_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Leg_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Knee_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Torso",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Chest",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Head",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "Shoulder_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Arm_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Elbow_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Hand_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Tool_R",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Shoulder_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Arm_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Elbow_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Hand_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "Tool_L",
|
||||
"transform": {
|
||||
"loc": [0.0, 0.0, 0.0],
|
||||
"rot": [1.0, 0.0, 0.0, 0.0],
|
||||
"sca": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user