Compare commits
35 Commits
develop
...
feature/ri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fa291563c | ||
|
|
05cc07a97d | ||
|
|
d3a3b400aa | ||
|
|
687b810a0e | ||
|
|
06ec7c7c5f | ||
|
|
8530671a49 | ||
|
|
03c28e3332 | ||
|
|
4152f9fc71 | ||
|
|
647894508d | ||
|
|
f4aae9adb7 | ||
|
|
f80dc68c0b | ||
|
|
5a39fb0c1c | ||
|
|
b494b60d60 | ||
|
|
08808dbcc1 | ||
|
|
73264db3c6 | ||
|
|
987efde86b | ||
|
|
d129983eb7 | ||
|
|
8dff4c0e03 | ||
|
|
39f6177595 | ||
|
|
79fc470aa0 | ||
|
|
ccec6bd87e | ||
|
|
faad0ced0f | ||
|
|
3aec681436 | ||
|
|
4a587b7478 | ||
|
|
4d90a87b48 | ||
|
|
29c4fddb90 | ||
|
|
94fcece05a | ||
|
|
4a615368df | ||
|
|
1cef57a472 | ||
|
|
bdbd429bdf | ||
|
|
f0d8408384 | ||
|
|
324e7fb984 | ||
|
|
cbf61906e0 | ||
|
|
b141e137e7 | ||
|
|
b0b719b3dd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -40,6 +40,7 @@ package-lock.json
|
||||
|
||||
# Build logs
|
||||
build_output.log
|
||||
logs/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
|
||||
102
LICENSE
102
LICENSE
@@ -1,66 +1,21 @@
|
||||
# TiedUp! Remake - License
|
||||
|
||||
**Effective Date:** January 2025
|
||||
**Applies to:** All versions of TiedUp! Remake (past, present, and future)
|
||||
**Effective Date:** April 2026 (license change from GPL-3.0 + Commons-Clause to GPL-3.0 pure)
|
||||
**Applies to:** All versions from 0.6.0-ALPHA onwards. Prior versions (0.1.0 through 0.5.x) were distributed under GPL-3.0 WITH Commons-Clause.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This software is licensed under **GPL-3.0 with Commons Clause** and additional restrictions.
|
||||
This software is licensed under **GPL-3.0-or-later** (GNU General Public License, version 3 or any later version).
|
||||
|
||||
**You CAN:**
|
||||
- Use the mod for free
|
||||
- Modify the source code
|
||||
- Distribute the mod (with source code)
|
||||
- Create derivative works (must be open source under the same license)
|
||||
|
||||
**You CANNOT:**
|
||||
- Sell this software
|
||||
- Put this software behind a paywall, subscription, or any form of monetization
|
||||
- Distribute without providing source code
|
||||
- Use a more restrictive license for derivative works
|
||||
The Commons-Clause restriction and additional monetization restrictions present in prior versions are **removed** effective this release, to enable incorporating third-party GPLv3 code (notably the Epic Fight animation/skeleton/mesh subsystem — see `docs/plans/rig/`).
|
||||
|
||||
---
|
||||
|
||||
## Full License Terms
|
||||
## GNU General Public License v3.0-or-later
|
||||
|
||||
### Part 1: Commons Clause Restriction
|
||||
|
||||
"Commons Clause" License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the License, as defined
|
||||
below, subject to the following condition.
|
||||
|
||||
Without limiting other conditions in the License, the grant of rights under the
|
||||
License will not include, and the License does not grant to you, the right to
|
||||
Sell the Software.
|
||||
|
||||
For purposes of the foregoing, "Sell" means practicing any or all of the rights
|
||||
granted to you under the License to provide to third parties, for a fee or other
|
||||
consideration (including without limitation fees for hosting or consulting/
|
||||
support services related to the Software), a product or service whose value
|
||||
derives, entirely or substantially, from the functionality of the Software.
|
||||
|
||||
**Additional Monetization Restrictions:**
|
||||
|
||||
The following are explicitly prohibited:
|
||||
1. Selling the Software or any derivative work
|
||||
2. Requiring payment, subscription, or donation to access or download the Software
|
||||
3. Placing the Software behind a paywall of any kind (Patreon, Ko-fi, etc.)
|
||||
4. Bundling the Software with paid products or services
|
||||
5. Using the Software as an incentive for paid memberships or subscriptions
|
||||
6. Early access monetization (charging for early access to updates)
|
||||
|
||||
**Permitted:**
|
||||
- Accepting voluntary donations (as long as the Software remains freely accessible)
|
||||
- Using the Software on monetized content platforms (YouTube, Twitch, etc.)
|
||||
|
||||
---
|
||||
|
||||
### Part 2: GNU General Public License v3.0
|
||||
|
||||
Copyright (C) 2024-2025 TiedUp! Remake Contributors
|
||||
Copyright (C) 2024-2026 TiedUp! Remake Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -80,14 +35,24 @@ https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
---
|
||||
|
||||
### Part 3: Asset Exclusions
|
||||
## Derived Work — Epic Fight
|
||||
|
||||
The following assets are NOT covered by this license and remain property of
|
||||
their respective owners:
|
||||
Portions of this project (everything under `com.tiedup.remake.v3.*`, starting with 0.6.0-ALPHA) are derived from **Epic Fight** by the Epic Fight Team, licensed under GPLv3. See:
|
||||
|
||||
- Upstream repository: https://github.com/Epic-Fight/epicfight
|
||||
- Upstream license: GPLv3 (identical to this project)
|
||||
|
||||
Each derived file in `com.tiedup.remake.rig.*` carries a header attribution to Epic Fight. No Epic Fight assets (textures, 3D models, animations) are reused — only Java source code for the animation/skeleton/mesh infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## Asset Exclusions
|
||||
|
||||
The following assets are NOT covered by this license and remain property of their respective owners:
|
||||
|
||||
1. **Original kdnp mod Assets** (textures, models, sounds from the 1.12.2 version)
|
||||
- Original creators: Yuti & Marl Velius
|
||||
- These assets are used under fair use for preservation/educational purposes
|
||||
- Used under fair use for preservation/educational purposes
|
||||
- Contact original authors for commercial use
|
||||
|
||||
2. **Minecraft Assets**
|
||||
@@ -95,28 +60,15 @@ their respective owners:
|
||||
- Subject to Minecraft EULA: https://www.minecraft.net/en-us/eula
|
||||
|
||||
3. **Third-Party Libraries**
|
||||
- PlayerAnimator: Subject to its own license (dev.kosmx.player-anim)
|
||||
- Forge: Subject to Forge license (MinecraftForge)
|
||||
- Other dependencies: Subject to their respective licenses
|
||||
- Forge: subject to Forge license (MinecraftForge)
|
||||
- Other dependencies: subject to their respective licenses
|
||||
- (Prior to 0.6.0, PlayerAnimator and bendy-lib were used; removed in the RIG system.)
|
||||
|
||||
**Code written for this remake** (files in `src/main/java/com/tiedup/remake/`)
|
||||
is fully covered by this GPL-3.0 + Commons Clause license.
|
||||
Code written for this project (files in `src/main/java/com/tiedup/remake/`) is fully covered by GPL-3.0-or-later.
|
||||
|
||||
---
|
||||
|
||||
### Part 4: Derivative Works
|
||||
|
||||
Any derivative work based on this Software MUST:
|
||||
|
||||
1. Be distributed under this same license (GPL-3.0 + Commons Clause)
|
||||
2. Provide complete source code
|
||||
3. Maintain all copyright notices
|
||||
4. Not be sold or monetized in any way
|
||||
5. Credit the original TiedUp! Remake project
|
||||
|
||||
---
|
||||
|
||||
### Part 5: Disclaimer
|
||||
## Disclaimer
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
@@ -131,9 +83,9 @@ SOFTWARE.
|
||||
## SPDX Identifier
|
||||
|
||||
```
|
||||
SPDX-License-Identifier: GPL-3.0-only WITH Commons-Clause-1.0
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
```
|
||||
|
||||
## Contact
|
||||
|
||||
For licensing questions or permission requests, open an issue on the project repository.
|
||||
For licensing questions, open an issue on the project repository.
|
||||
|
||||
@@ -66,7 +66,7 @@ minecraft {
|
||||
// However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
|
||||
// This default location is a best practice to automatically put the file in the right place in the final jar.
|
||||
// See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.
|
||||
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
|
||||
// Default run configurations.
|
||||
// These can be tweaked, removed, or duplicated as needed.
|
||||
@@ -105,6 +105,7 @@ minecraft {
|
||||
// Mixin config arg
|
||||
args '-mixin.config=tiedup.mixins.json'
|
||||
args '-mixin.config=tiedup-compat.mixins.json'
|
||||
args '-mixin.config=tiedup-rig.mixins.json'
|
||||
}
|
||||
|
||||
server {
|
||||
@@ -118,6 +119,7 @@ minecraft {
|
||||
// Mixin config arg
|
||||
args '-mixin.config=tiedup.mixins.json'
|
||||
args '-mixin.config=tiedup-compat.mixins.json'
|
||||
args '-mixin.config=tiedup-rig.mixins.json'
|
||||
}
|
||||
|
||||
// Additional client instances for multiplayer testing
|
||||
|
||||
@@ -49,7 +49,7 @@ mod_id=tiedup
|
||||
# The human-readable display name for the mod.
|
||||
mod_name=TiedUp
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=GPL-3.0 WITH Commons-Clause (No Sale/Paywall)
|
||||
mod_license=GPL-3.0-or-later
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=0.5.6-ALPHA
|
||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||
|
||||
137
scripts/rig-extract-phase0.sh
Executable file
137
scripts/rig-extract-phase0.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
# rig-extract-phase0.sh
|
||||
# Extrait le core Epic Fight nécessaire pour le RIG system TiedUp.
|
||||
# Voir docs/plans/rig/EXTRACTION.md §9 pour détails.
|
||||
#
|
||||
# NE PAS utiliser "set -e" — certains cp peuvent échouer (fichiers non
|
||||
# critiques, 2>/dev/null || true) et on veut continuer.
|
||||
#
|
||||
# Lancer depuis la racine du projet.
|
||||
|
||||
set -u
|
||||
|
||||
SRC="docs/ModSources/epicfight-1.20.1/src/main/java/yesman/epicfight"
|
||||
DST="src/main/java/com/tiedup/remake/rig"
|
||||
|
||||
if [ ! -d "$SRC" ]; then
|
||||
echo "ERROR: Epic Fight source not found at $SRC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== rig-extract-phase0.sh ==="
|
||||
echo "Source : $SRC"
|
||||
echo "Cible : $DST"
|
||||
echo ""
|
||||
|
||||
echo "=== 0. Structure des packages ==="
|
||||
mkdir -p "$DST"/{math,armature,armature/types,anim,anim/types,anim/property,anim/client,anim/client/property,mesh,mesh/transformer,cloth,asset,event,patch,render,render/compute,registry,bridge,tick,mixin,util,util/datastruct,exception}
|
||||
|
||||
echo "=== 1. Math utils ==="
|
||||
cp -v "$SRC"/api/utils/math/*.java "$DST/math/"
|
||||
|
||||
echo ""
|
||||
echo "=== 2. Armature (Armature + Joint + JointTransform) ==="
|
||||
cp -v "$SRC"/api/model/Armature.java "$DST/armature/"
|
||||
cp -v "$SRC"/api/animation/Joint.java "$DST/armature/"
|
||||
cp -v "$SRC"/api/animation/JointTransform.java "$DST/armature/"
|
||||
|
||||
echo ""
|
||||
echo "=== 3. Animation core + LivingMotion + ServerAnimator ==="
|
||||
for f in Animator AnimationPlayer AnimationClip AnimationManager AnimationVariables \
|
||||
SynchedAnimationVariableKey SynchedAnimationVariableKeys Keyframe Pose TransformSheet \
|
||||
LivingMotion LivingMotions ServerAnimator; do
|
||||
cp -v "$SRC/api/animation/$f.java" "$DST/anim/" 2>/dev/null || echo " (skip : $f.java non trouvé)"
|
||||
done
|
||||
cp -v "$SRC"/api/animation/property/*.java "$DST/anim/property/" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== 4. Animation types (filtrés + stubs combat pour JsonAssetLoader) ==="
|
||||
for f in DynamicAnimation StaticAnimation LinkAnimation \
|
||||
ConcurrentLinkAnimation LayerOffAnimation EntityState \
|
||||
ActionAnimation AttackAnimation MainFrameAnimation; do
|
||||
# ActionAnimation/AttackAnimation/MainFrameAnimation seront simplifiés
|
||||
# manuellement en stubs (retirer le combat, garder signatures).
|
||||
cp -v "$SRC/api/animation/types/$f.java" "$DST/anim/types/" 2>/dev/null || echo " (skip : $f.java non trouvé)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== 5. Animation client ==="
|
||||
cp -v "$SRC"/api/client/animation/*.java "$DST/anim/client/" 2>/dev/null
|
||||
cp -v "$SRC"/api/client/animation/property/*.java "$DST/anim/client/property/" 2>/dev/null
|
||||
# TrailInfo hors scope
|
||||
rm -f "$DST/anim/client/property/TrailInfo.java"
|
||||
|
||||
echo ""
|
||||
echo "=== 6. Mesh ==="
|
||||
cp -v "$SRC"/api/client/model/*.java "$DST/mesh/" 2>/dev/null
|
||||
# Retirer ItemSkinsReloadListener (cosmetics combat)
|
||||
rm -f "$DST/mesh/ItemSkinsReloadListener.java"
|
||||
|
||||
echo ""
|
||||
echo "=== 7. Cloth (absorbé Phase 0 — StaticMesh en dépend) ==="
|
||||
cp -v "$SRC"/api/client/physics/AbstractSimulator.java "$DST/cloth/" 2>/dev/null
|
||||
cp -v "$SRC"/api/client/physics/cloth/*.java "$DST/cloth/" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== 8. Asset loader ==="
|
||||
cp -v "$SRC"/api/asset/*.java "$DST/asset/" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== 9. Forge events ==="
|
||||
for f in PatchedRenderersEvent PrepareModelEvent RegisterResourceLayersEvent; do
|
||||
cp -v "$SRC/api/client/forgeevent/$f.java" "$DST/event/" 2>/dev/null || echo " (skip : $f.java non trouvé)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== 10. RenderTypes ==="
|
||||
cp -v "$SRC"/client/renderer/EpicFightRenderTypes.java "$DST/render/TiedUpRenderTypes.java" 2>/dev/null
|
||||
echo " NOTE: RenderEngine.Events à extraire manuellement dans render/TiedUpRenderEngine.java"
|
||||
echo " NOTE: TiedUpRigConstants.java à créer manuellement (factory ANIMATOR_PROVIDER + isPhysicalClient)"
|
||||
|
||||
echo ""
|
||||
echo "=== 11. ComputeShader stubs (à créer manuellement, no-op) ==="
|
||||
echo " NOTE: créer render/compute/ComputeShaderSetup.java et ComputeShaderProvider.java (stubs vides)"
|
||||
|
||||
echo ""
|
||||
echo "=== 12. Transitives oubliées (découvertes par review) ==="
|
||||
cp -v "$SRC"/api/utils/ParseUtil.java "$DST/util/" 2>/dev/null
|
||||
cp -v "$SRC"/api/utils/datastruct/*.java "$DST/util/datastruct/" 2>/dev/null
|
||||
cp -v "$SRC"/api/exception/*.java "$DST/exception/" 2>/dev/null
|
||||
cp -v "$SRC"/model/armature/HumanoidArmature.java "$DST/armature/" 2>/dev/null
|
||||
cp -v "$SRC"/model/armature/types/HumanLikeArmature.java "$DST/armature/types/" 2>/dev/null
|
||||
cp -v "$SRC"/client/mesh/HumanoidMesh.java "$DST/mesh/" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== 13. ClientPlayerPatch + LocalPlayerPatch (typo upstream 'capabilites') ==="
|
||||
cp -v "$SRC"/client/world/capabilites/entitypatch/player/AbstractClientPlayerPatch.java "$DST/patch/ClientPlayerPatch.java" 2>/dev/null
|
||||
cp -v "$SRC"/client/world/capabilites/entitypatch/player/LocalPlayerPatch.java "$DST/patch/" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=== 14. Rewrite imports ==="
|
||||
bash scripts/rig-rewrite-imports.sh "$DST"
|
||||
|
||||
echo ""
|
||||
echo "=== 15. License headers ==="
|
||||
bash scripts/rig-headers.sh "$DST"
|
||||
|
||||
echo ""
|
||||
echo "--- Phase 0 extraction done ---"
|
||||
echo ""
|
||||
echo "Fichiers copiés :"
|
||||
find "$DST" -type f -name "*.java" | wc -l
|
||||
echo "LOC totales :"
|
||||
find "$DST" -type f -name "*.java" -exec cat {} + | wc -l
|
||||
|
||||
echo ""
|
||||
echo "Next steps (manuel, cf. EXTRACTION.md §9) :"
|
||||
echo " 1. Fixer compile errors (strip combat from types, stub refs EpicFightMod/ClientConfig/ClientEngine/SkillManager)"
|
||||
echo " 2. Strip valeurs combat de LivingMotion/LivingMotions enum, ajouter valeurs TiedUp"
|
||||
echo " 3. Extraire RenderEngine.Events → render/TiedUpRenderEngine.java"
|
||||
echo " 4. Créer ComputeShader stubs (render/compute/ComputeShaderSetup.java + ComputeShaderProvider.java no-op)"
|
||||
echo " 5. Adapter TiedUpCapabilities.java depuis EpicFightCapabilities.java (retirer combat caps)"
|
||||
echo " 6. Écrire TiedUpCapabilityEvents.java from scratch (~50L : RegisterCapabilitiesEvent + AttachCapabilitiesEvent)"
|
||||
echo " 7. Fork mixins (MixinEntity, MixinLivingEntity, MixinLivingEntityRenderer — @Invoker only pour renderer)"
|
||||
echo " 8. Simplifier LivingEntityPatch.java (1213L → ~400L, strip combat)"
|
||||
echo " 9. Créer TiedUpRigConstants.java (factory ANIMATOR_PROVIDER)"
|
||||
echo ""
|
||||
echo "Budget total post-script : 2 à 3 semaines pour vert-compile."
|
||||
38
scripts/rig-headers.sh
Executable file
38
scripts/rig-headers.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# rig-headers.sh
|
||||
# Ajoute un header attribution Epic Fight + GPLv3 à chaque fichier .java
|
||||
# forké dans v3/rig qui n'en a pas déjà un.
|
||||
|
||||
set -u
|
||||
|
||||
TARGET="${1:-src/main/java/com/tiedup/remake/rig}"
|
||||
|
||||
if [ ! -d "$TARGET" ]; then
|
||||
echo "ERROR: target dir not found: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HEADER='/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
'
|
||||
|
||||
count=0
|
||||
skipped=0
|
||||
find "$TARGET" -type f -name "*.java" | while read f; do
|
||||
if head -5 "$f" | grep -q "Derived from Epic Fight"; then
|
||||
skipped=$((skipped + 1))
|
||||
else
|
||||
tmp=$(mktemp)
|
||||
printf '%s' "$HEADER" > "$tmp"
|
||||
cat "$f" >> "$tmp"
|
||||
mv "$tmp" "$f"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Headers injected in files (check count via grep)."
|
||||
echo "Run: grep -l 'Derived from Epic Fight' $TARGET -r | wc -l"
|
||||
75
scripts/rig-rewrite-imports.sh
Executable file
75
scripts/rig-rewrite-imports.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# rig-rewrite-imports.sh
|
||||
# Renomme les imports yesman.epicfight.* vers com.tiedup.remake.rig.*
|
||||
# dans tous les fichiers Java fraîchement copiés dans v3/rig.
|
||||
#
|
||||
# IMPORTANT : ordre du plus spécifique au plus général.
|
||||
# Si on fait api.animation avant api.client.animation, la première rule
|
||||
# mange la seconde. Chaque règle utilise un pattern qui matche exactement
|
||||
# le chemin complet jusqu'au séparateur suivant.
|
||||
#
|
||||
# Portabilité : sed -i non-portable BSD (macOS) — utiliser "sed -i.bak"
|
||||
# si besoin support Mac.
|
||||
|
||||
set -u
|
||||
|
||||
TARGET="${1:-src/main/java/com/tiedup/remake/rig}"
|
||||
|
||||
if [ ! -d "$TARGET" ]; then
|
||||
echo "ERROR: target dir not found: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Rewriting imports in $TARGET..."
|
||||
|
||||
find "$TARGET" -type f -name "*.java" -exec sed -i \
|
||||
-e 's|yesman\.epicfight\.api\.utils\.math|com.tiedup.remake.rig.math|g' \
|
||||
-e 's|yesman\.epicfight\.api\.utils\.datastruct|com.tiedup.remake.rig.util.datastruct|g' \
|
||||
-e 's|yesman\.epicfight\.api\.utils\.ParseUtil|com.tiedup.remake.rig.util.ParseUtil|g' \
|
||||
-e 's|yesman\.epicfight\.api\.utils|com.tiedup.remake.rig.util|g' \
|
||||
-e 's|yesman\.epicfight\.api\.exception|com.tiedup.remake.rig.exception|g' \
|
||||
-e 's|yesman\.epicfight\.api\.forgeevent|com.tiedup.remake.rig.event|g' \
|
||||
-e 's|yesman\.epicfight\.api\.model\.Armature|com.tiedup.remake.rig.armature.Armature|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.Joint|com.tiedup.remake.rig.armature.Joint|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.JointTransform|com.tiedup.remake.rig.armature.JointTransform|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.types\.datapack|com.tiedup.remake.rig.anim.types.datapack|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.types\.grappling|com.tiedup.remake.rig.anim.types.grappling|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.types\.procedural|com.tiedup.remake.rig.anim.types.procedural|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.types|com.tiedup.remake.rig.anim.types|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation\.property|com.tiedup.remake.rig.anim.property|g' \
|
||||
-e 's|yesman\.epicfight\.api\.animation|com.tiedup.remake.rig.anim|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.animation\.property|com.tiedup.remake.rig.anim.client.property|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.animation|com.tiedup.remake.rig.anim.client|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.model\.transformer|com.tiedup.remake.rig.mesh.transformer|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.model|com.tiedup.remake.rig.mesh|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.physics\.cloth|com.tiedup.remake.rig.cloth|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.physics|com.tiedup.remake.rig.cloth|g' \
|
||||
-e 's|yesman\.epicfight\.api\.client\.forgeevent|com.tiedup.remake.rig.event|g' \
|
||||
-e 's|yesman\.epicfight\.api\.asset|com.tiedup.remake.rig.asset|g' \
|
||||
-e 's|yesman\.epicfight\.model\.armature\.types|com.tiedup.remake.rig.armature.types|g' \
|
||||
-e 's|yesman\.epicfight\.model\.armature|com.tiedup.remake.rig.armature|g' \
|
||||
-e 's|yesman\.epicfight\.world\.capabilities\.provider|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.world\.capabilities\.entitypatch\.player|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.world\.capabilities\.entitypatch|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.world\.capabilities\.EpicFightCapabilities|com.tiedup.remake.rig.patch.TiedUpCapabilities|g' \
|
||||
-e 's|yesman\.epicfight\.world\.capabilities|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.client\.world\.capabilites\.entitypatch\.player|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.client\.world\.capabilites\.entitypatch|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.client\.world\.capabilites|com.tiedup.remake.rig.patch|g' \
|
||||
-e 's|yesman\.epicfight\.client\.renderer\.patched\.entity|com.tiedup.remake.rig.render|g' \
|
||||
-e 's|yesman\.epicfight\.client\.renderer\.patched|com.tiedup.remake.rig.render|g' \
|
||||
-e 's|yesman\.epicfight\.client\.renderer\.EpicFightRenderTypes|com.tiedup.remake.rig.render.TiedUpRenderTypes|g' \
|
||||
-e 's|yesman\.epicfight\.client\.mesh|com.tiedup.remake.rig.mesh|g' \
|
||||
{} +
|
||||
|
||||
echo ""
|
||||
echo "Verifying no yesman.epicfight references remain..."
|
||||
remaining=$(grep -r "yesman\.epicfight" "$TARGET" 2>/dev/null | wc -l)
|
||||
if [ "$remaining" -eq 0 ]; then
|
||||
echo "OK - all imports rewritten."
|
||||
else
|
||||
echo "WARN - $remaining residual refs found:"
|
||||
grep -rn "yesman\.epicfight" "$TARGET" | head -20
|
||||
echo "..."
|
||||
echo "(affichage limité aux 20 premiers)"
|
||||
fi
|
||||
@@ -1,13 +1,11 @@
|
||||
package com.tiedup.remake.client.animation;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.tiedup.remake.v2.furniture.ISeatProvider;
|
||||
import dev.kosmx.playerAnim.api.layered.IAnimation;
|
||||
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
|
||||
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
|
||||
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
|
||||
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
|
||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
|
||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory;
|
||||
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
|
||||
import java.util.Map;
|
||||
@@ -15,7 +13,6 @@ import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -39,92 +36,42 @@ public class BondageAnimationManager {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
/** Cache of ModifierLayers for NPC entities (players use PlayerAnimationAccess) */
|
||||
/** Cache of item-layer ModifierLayers for NPC entities. */
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>> npcLayers =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** Cache of context ModifierLayers for NPC entities */
|
||||
/** Cache of context-layer ModifierLayers for NPC entities. */
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>> npcContextLayers =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** Cache of furniture ModifierLayers for NPC entities */
|
||||
private static final Map<
|
||||
UUID,
|
||||
ModifierLayer<IAnimation>
|
||||
> npcFurnitureLayers = new ConcurrentHashMap<>();
|
||||
|
||||
/** Factory ID for PlayerAnimator item layer (players only) */
|
||||
private static final ResourceLocation FACTORY_ID =
|
||||
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage");
|
||||
|
||||
/** Factory ID for PlayerAnimator context layer (players only) */
|
||||
private static final ResourceLocation CONTEXT_FACTORY_ID =
|
||||
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage_context");
|
||||
|
||||
/** Factory ID for PlayerAnimator furniture layer (players only) */
|
||||
private static final ResourceLocation FURNITURE_FACTORY_ID =
|
||||
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage_furniture");
|
||||
|
||||
/** Priority for context animation layer (lower = overridable by item layer) */
|
||||
private static final int CONTEXT_LAYER_PRIORITY = 40;
|
||||
/** Priority for item animation layer (higher = overrides context layer) */
|
||||
private static final int ITEM_LAYER_PRIORITY = 42;
|
||||
/**
|
||||
* Priority for furniture animation layer (highest = overrides item layer on blocked bones).
|
||||
* Non-blocked bones are disabled so items can still animate them via the item layer.
|
||||
*/
|
||||
private static final int FURNITURE_LAYER_PRIORITY = 43;
|
||||
|
||||
/** Number of ticks to wait before removing a stale furniture animation. */
|
||||
private static final int FURNITURE_GRACE_TICKS = 3;
|
||||
|
||||
/**
|
||||
* Tracks ticks since a player with an active furniture animation stopped riding
|
||||
* an ISeatProvider. After {@link #FURNITURE_GRACE_TICKS}, the animation is removed
|
||||
* to prevent stuck poses from entity death or network issues.
|
||||
*
|
||||
* <p>Uses ConcurrentHashMap for safe access from both client tick and render thread.</p>
|
||||
*/
|
||||
private static final Map<UUID, Integer> furnitureGraceTicks =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Initialize the animation system.
|
||||
* Must be called during client setup to register the player animation factory.
|
||||
*
|
||||
* <p><b>Pipeline NPC-only</b> — depuis Phase 2.7, les joueurs sont tickés par
|
||||
* {@code RigAnimationTickHandler} via le renderer RIG patched. Aucune
|
||||
* {@link PlayerAnimationFactory} n'est enregistrée pour le joueur et tous
|
||||
* les chemins joueur dans cette classe sont de short-circuits logués.</p>
|
||||
*
|
||||
* <p>Cette classe reste active <b>uniquement pour les NPCs</b>
|
||||
* (entités implémentant {@link IAnimatedPlayer} qui ne sont pas un
|
||||
* {@link Player}) : {@link #getOrCreateLayer} leur crée un {@link ModifierLayer}
|
||||
* via accès direct au stack d'animation
|
||||
* ({@code animated.getAnimationStack().addAnimLayer(...)}) — ce path ne dépend
|
||||
* d'aucune factory. Consumer principal : {@code NpcAnimationTickHandler}.</p>
|
||||
*
|
||||
* <p>Conservé comme méthode publique pour ne pas casser les call sites
|
||||
* externes. Rework V3 (player anim natives RIG) : voir V3-REW-01 dans
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md}.</p>
|
||||
*/
|
||||
public static void init() {
|
||||
LOGGER.info("BondageAnimationManager initializing...");
|
||||
|
||||
// Context layer: lower priority = evaluated first, overridable by item layer.
|
||||
// In AnimationStack, layers are sorted ascending by priority and evaluated in order.
|
||||
// Higher priority layers override lower ones.
|
||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
||||
CONTEXT_FACTORY_ID,
|
||||
CONTEXT_LAYER_PRIORITY,
|
||||
player -> new ModifierLayer<>()
|
||||
);
|
||||
|
||||
// Item layer: higher priority = evaluated last, overrides context layer
|
||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
||||
FACTORY_ID,
|
||||
ITEM_LAYER_PRIORITY,
|
||||
player -> new ModifierLayer<>()
|
||||
);
|
||||
|
||||
// Furniture layer: highest priority = overrides item layer on blocked bones.
|
||||
// Non-blocked bones are disabled via FurnitureAnimationContext so items
|
||||
// can still animate free regions (gag, blindfold, etc.).
|
||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
||||
FURNITURE_FACTORY_ID,
|
||||
FURNITURE_LAYER_PRIORITY,
|
||||
player -> new ModifierLayer<>()
|
||||
);
|
||||
|
||||
LOGGER.info(
|
||||
"BondageAnimationManager: Factories registered — context (pri {}), item (pri {}), furniture (pri {})",
|
||||
CONTEXT_LAYER_PRIORITY,
|
||||
ITEM_LAYER_PRIORITY,
|
||||
FURNITURE_LAYER_PRIORITY
|
||||
"BondageAnimationManager: NPC-only pipeline (Phase 2.8 RIG cleanup). " +
|
||||
"Players handled by RigAnimationTickHandler; all player call sites no-op."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -151,6 +98,11 @@ public class BondageAnimationManager {
|
||||
* <p>If the animation layer is not available (e.g., remote player not fully
|
||||
* initialized), the animation will be queued for retry via PendingAnimationManager.
|
||||
*
|
||||
* <p><b>Phase 2.8</b> — les appels sur un {@link Player} sont no-op : le pipeline
|
||||
* joueur est désormais RIG-native (voir {@link #init} Javadoc). Un WARN est logué
|
||||
* une fois par UUID pour signaler les call sites stale qui devraient être purgés
|
||||
* lors du rework V3.</p>
|
||||
*
|
||||
* @param entity The entity to animate
|
||||
* @param animId Full ResourceLocation of the animation
|
||||
* @return true if animation started successfully, false if layer not available
|
||||
@@ -163,6 +115,12 @@ public class BondageAnimationManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 2.8 : player path is dead. Log once per UUID and no-op.
|
||||
if (entity instanceof Player player) {
|
||||
logPlayerCallOnce(player, "playAnimation(" + animId + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyframeAnimation anim = PlayerAnimationRegistry.getAnimation(animId);
|
||||
if (anim == null) {
|
||||
// Try fallback: remove _sneak_ suffix if present
|
||||
@@ -199,7 +157,7 @@ public class BondageAnimationManager {
|
||||
}
|
||||
layer.setAnimation(new KeyframeAnimationPlayer(anim));
|
||||
|
||||
// Remove from pending queue if it was waiting
|
||||
// Remove from pending queue if it was waiting (legacy, may still hold NPC entries)
|
||||
PendingAnimationManager.remove(entity.getUUID());
|
||||
|
||||
LOGGER.debug(
|
||||
@@ -208,24 +166,12 @@ public class BondageAnimationManager {
|
||||
entity.getUUID()
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
// Layer not available - queue for retry if it's a player
|
||||
if (entity instanceof AbstractClientPlayer) {
|
||||
PendingAnimationManager.queueForRetry(
|
||||
entity.getUUID(),
|
||||
animId.getPath()
|
||||
);
|
||||
LOGGER.debug(
|
||||
"Animation layer not ready for {}, queued for retry",
|
||||
entity.getName().getString()
|
||||
);
|
||||
} else {
|
||||
LOGGER.warn(
|
||||
"Animation layer is NULL for NPC: {} (type: {})",
|
||||
entity.getName().getString(),
|
||||
entity.getClass().getSimpleName()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -246,6 +192,12 @@ public class BondageAnimationManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 2.8 : player path is dead.
|
||||
if (entity instanceof Player player) {
|
||||
logPlayerCallOnce(player, "playDirect");
|
||||
return false;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getOrCreateLayer(entity);
|
||||
if (layer != null) {
|
||||
IAnimation current = layer.getAnimation();
|
||||
@@ -273,6 +225,11 @@ public class BondageAnimationManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 2.8 : player path is dead — no layer to clear.
|
||||
if (entity instanceof Player) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getLayer(entity);
|
||||
if (layer != null) {
|
||||
layer.setAnimation(null);
|
||||
@@ -284,56 +241,36 @@ public class BondageAnimationManager {
|
||||
|
||||
/**
|
||||
* Get the ModifierLayer for an entity (without creating).
|
||||
*
|
||||
* <p>Phase 2.8 : returns {@code null} directly for any {@link Player} — the
|
||||
* player animation pipeline is RIG-native, this manager only tracks NPCs.</p>
|
||||
*/
|
||||
private static ModifierLayer<IAnimation> getLayer(LivingEntity entity) {
|
||||
// Players: try PlayerAnimationAccess first, then cache
|
||||
if (entity instanceof AbstractClientPlayer player) {
|
||||
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
|
||||
if (factoryLayer != null) {
|
||||
return factoryLayer;
|
||||
if (entity instanceof Player) {
|
||||
return null;
|
||||
}
|
||||
// Check cache (for remote players using fallback)
|
||||
return npcLayers.get(entity.getUUID());
|
||||
}
|
||||
|
||||
// NPCs: use cache
|
||||
return npcLayers.get(entity.getUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the ModifierLayer for an entity.
|
||||
*
|
||||
* <p>Phase 2.8 : returns {@code null} directly for any {@link Player} — the
|
||||
* player fallback via {@code IAnimatedPlayer.getAnimationStack()} has been
|
||||
* retired because it was partially alive (FP vanilla render consumed it,
|
||||
* TP RIG override bypassed it), producing a confusing behavior split. All
|
||||
* player anim needs are now handled by {@code RigAnimationTickHandler}.</p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ModifierLayer<IAnimation> getOrCreateLayer(
|
||||
LivingEntity entity
|
||||
) {
|
||||
// Phase 2.8 : strip player path entirely (no partially-alive fallback).
|
||||
if (entity instanceof Player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UUID uuid = entity.getUUID();
|
||||
|
||||
// Players: try factory-based access first, fallback to direct stack access
|
||||
if (entity instanceof AbstractClientPlayer player) {
|
||||
// Try the registered factory first (works for local player)
|
||||
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
|
||||
if (factoryLayer != null) {
|
||||
return factoryLayer;
|
||||
}
|
||||
|
||||
// Fallback for remote players: use direct stack access like NPCs
|
||||
// This handles cases where the factory data isn't available
|
||||
if (player instanceof IAnimatedPlayer animated) {
|
||||
return npcLayers.computeIfAbsent(uuid, k -> {
|
||||
ModifierLayer<IAnimation> newLayer = new ModifierLayer<>();
|
||||
animated
|
||||
.getAnimationStack()
|
||||
.addAnimLayer(ITEM_LAYER_PRIORITY, newLayer);
|
||||
LOGGER.info(
|
||||
"Created animation layer for remote player via stack: {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
return newLayer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NPCs implementing IAnimatedPlayer: create/cache layer
|
||||
if (entity instanceof IAnimatedPlayer animated) {
|
||||
return npcLayers.computeIfAbsent(uuid, k -> {
|
||||
@@ -353,87 +290,49 @@ public class BondageAnimationManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Per-player dedup set so we log the factory-access failure at most once per UUID. */
|
||||
private static final java.util.Set<UUID> layerFailureLogged =
|
||||
/** Per-player-UUID dedup so stale call sites log at most once per session. */
|
||||
private static final java.util.Set<UUID> playerCallLogged =
|
||||
java.util.concurrent.ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* Get the animation layer for a player from PlayerAnimationAccess.
|
||||
*
|
||||
* <p>Throws during the factory-race window for remote players (the factory
|
||||
* hasn't yet initialized their associated data). This is the expected path
|
||||
* for the {@link PendingAnimationManager} retry loop, so we log at DEBUG
|
||||
* and at most once per UUID — a per-tick log would flood during busy
|
||||
* multiplayer.</p>
|
||||
* Log once per player UUID that a stale call site is invoking this manager.
|
||||
* Used by the player no-op short-circuits ({@link #playAnimation},
|
||||
* {@link #playDirect}) to surface call sites that should be migrated to the
|
||||
* RIG pipeline (tracked in V3_REWORK_BACKLOG).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ModifierLayer<IAnimation> getPlayerLayer(
|
||||
AbstractClientPlayer player
|
||||
) {
|
||||
try {
|
||||
return (ModifierLayer<
|
||||
IAnimation
|
||||
>) PlayerAnimationAccess.getPlayerAssociatedData(player).get(
|
||||
FACTORY_ID
|
||||
private static void logPlayerCallOnce(Player player, String op) {
|
||||
if (playerCallLogged.add(player.getUUID())) {
|
||||
LOGGER.warn(
|
||||
"BondageAnimationManager.{} called on player {} — no-op " +
|
||||
"(RIG owns player anims since Phase 2.7). " +
|
||||
"Migrate call site to RigAnimationTickHandler (V3 rework).",
|
||||
op,
|
||||
player.getName().getString()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
if (layerFailureLogged.add(player.getUUID())) {
|
||||
LOGGER.debug(
|
||||
"Animation layer not yet available for player {} (will retry): {}",
|
||||
player.getName().getString(),
|
||||
e.toString()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get the animation layer for a player.
|
||||
* Returns null if the layer is not yet initialized.
|
||||
*
|
||||
* <p>Public method for PendingAnimationManager to access.
|
||||
* Checks both the factory-based layer and the NPC cache fallback.
|
||||
* <p>Phase 2.8 : always returns {@code null}. The player pipeline is
|
||||
* RIG-native; the {@link PendingAnimationManager} retry loop is no
|
||||
* longer fed (player calls to {@link #playAnimation} short-circuit
|
||||
* before queueing), so this getter is maintained only to preserve the
|
||||
* public signature for external call sites.</p>
|
||||
*
|
||||
* @param player The player
|
||||
* @return The animation layer, or null if not available
|
||||
* @param player The player (unused)
|
||||
* @return always null in Phase 2.8+
|
||||
*/
|
||||
@javax.annotation.Nullable
|
||||
public static ModifierLayer<IAnimation> getPlayerLayerSafe(
|
||||
AbstractClientPlayer player
|
||||
) {
|
||||
// Try factory first
|
||||
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
|
||||
if (factoryLayer != null) {
|
||||
return factoryLayer;
|
||||
}
|
||||
|
||||
// Check NPC cache (for remote players using fallback path)
|
||||
return npcLayers.get(player.getUUID());
|
||||
return null;
|
||||
}
|
||||
|
||||
// CONTEXT LAYER (lower priority, for sit/kneel/sneak)
|
||||
|
||||
/**
|
||||
* Get the context animation layer for a player from PlayerAnimationAccess.
|
||||
* Returns null if the layer is not yet initialized.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@javax.annotation.Nullable
|
||||
private static ModifierLayer<IAnimation> getPlayerContextLayer(
|
||||
AbstractClientPlayer player
|
||||
) {
|
||||
try {
|
||||
return (ModifierLayer<
|
||||
IAnimation
|
||||
>) PlayerAnimationAccess.getPlayerAssociatedData(player).get(
|
||||
CONTEXT_FACTORY_ID
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the context animation layer for an NPC entity.
|
||||
* Uses CONTEXT_LAYER_PRIORITY, below the item layer at ITEM_LAYER_PRIORITY.
|
||||
@@ -471,13 +370,14 @@ public class BondageAnimationManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer;
|
||||
if (entity instanceof AbstractClientPlayer player) {
|
||||
layer = getPlayerContextLayer(player);
|
||||
} else {
|
||||
layer = getOrCreateNpcContextLayer(entity);
|
||||
// Phase 2.8 : player context layer is dead (sit/kneel/sneak visuals
|
||||
// will be re-expressed as RIG StaticAnimations — cf. V3-REW-14).
|
||||
if (entity instanceof Player player) {
|
||||
logPlayerCallOnce(player, "playContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getOrCreateNpcContextLayer(entity);
|
||||
if (layer != null) {
|
||||
layer.setAnimation(new KeyframeAnimationPlayer(anim));
|
||||
return true;
|
||||
@@ -495,13 +395,12 @@ public class BondageAnimationManager {
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer;
|
||||
if (entity instanceof AbstractClientPlayer player) {
|
||||
layer = getPlayerContextLayer(player);
|
||||
} else {
|
||||
layer = npcContextLayers.get(entity.getUUID());
|
||||
// Phase 2.8 : player path is dead — no layer to clear.
|
||||
if (entity instanceof Player) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = npcContextLayers.get(entity.getUUID());
|
||||
if (layer != null) {
|
||||
layer.setAnimation(null);
|
||||
}
|
||||
@@ -533,194 +432,46 @@ public class BondageAnimationManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getOrCreateFurnitureLayer(player);
|
||||
if (layer != null) {
|
||||
layer.setAnimation(new KeyframeAnimationPlayer(animation));
|
||||
// Reset grace ticks since we just started/refreshed the animation
|
||||
furnitureGraceTicks.remove(player.getUUID());
|
||||
LOGGER.debug(
|
||||
"Playing furniture animation on player: {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.warn(
|
||||
"Furniture layer not available for player: {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
// Phase 2.8 : player furniture seat pose is dead (will be ported to
|
||||
// RIG StaticAnimations — cf. V3_REWORK_BACKLOG furniture seat entry).
|
||||
logPlayerCallOnce(player, "playFurniture");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the furniture layer animation for a player.
|
||||
*
|
||||
* <p>Phase 2.8 : no-op — the player furniture layer is dead. Kept for
|
||||
* signature compatibility with {@code EntityFurniture} cleanup call site.</p>
|
||||
*
|
||||
* @param player the player whose furniture animation should stop
|
||||
*/
|
||||
public static void stopFurniture(Player player) {
|
||||
if (player == null || !player.level().isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getFurnitureLayer(player);
|
||||
if (layer != null) {
|
||||
layer.setAnimation(null);
|
||||
}
|
||||
furnitureGraceTicks.remove(player.getUUID());
|
||||
LOGGER.debug(
|
||||
"Stopped furniture animation on player: {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
// Phase 2.8 : dead path. Retained signature for backward-compat.
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a player currently has an active furniture animation.
|
||||
*
|
||||
* <p>Phase 2.8 : always returns {@code false} — player furniture layer is dead.</p>
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if the furniture layer has an active animation
|
||||
* @return always false in Phase 2.8+
|
||||
*/
|
||||
public static boolean hasFurnitureAnimation(Player player) {
|
||||
if (player == null || !player.level().isClientSide()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ModifierLayer<IAnimation> layer = getFurnitureLayer(player);
|
||||
return layer != null && layer.getAnimation() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the furniture ModifierLayer for a player (READ-ONLY).
|
||||
* Uses PlayerAnimationAccess for local/factory-registered players,
|
||||
* falls back to NPC cache for remote players. Returns null if no layer
|
||||
* has been created yet — callers that need to guarantee a layer should use
|
||||
* {@link #getOrCreateFurnitureLayer}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@javax.annotation.Nullable
|
||||
private static ModifierLayer<IAnimation> getFurnitureLayer(Player player) {
|
||||
if (player instanceof AbstractClientPlayer clientPlayer) {
|
||||
try {
|
||||
ModifierLayer<IAnimation> layer = (ModifierLayer<
|
||||
IAnimation
|
||||
>) PlayerAnimationAccess.getPlayerAssociatedData(
|
||||
clientPlayer
|
||||
).get(FURNITURE_FACTORY_ID);
|
||||
if (layer != null) {
|
||||
return layer;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fall through to NPC cache
|
||||
}
|
||||
|
||||
// Fallback for remote players: check NPC furniture cache
|
||||
return npcFurnitureLayers.get(player.getUUID());
|
||||
}
|
||||
|
||||
// Non-player entities: use NPC cache
|
||||
return npcFurnitureLayers.get(player.getUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the furniture ModifierLayer for a player. Mirrors
|
||||
* {@link #getOrCreateLayer} but for the FURNITURE layer priority.
|
||||
* Safety tick for furniture animations.
|
||||
*
|
||||
* <p>For the local player (factory-registered), returns the factory layer.
|
||||
* For remote players, creates a new layer on first call and caches it in
|
||||
* {@link #npcFurnitureLayers} — remote players don't own a factory layer,
|
||||
* so without a fallback they can't receive any furniture seat pose.</p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@javax.annotation.Nullable
|
||||
private static ModifierLayer<IAnimation> getOrCreateFurnitureLayer(
|
||||
Player player
|
||||
) {
|
||||
if (player instanceof AbstractClientPlayer clientPlayer) {
|
||||
try {
|
||||
ModifierLayer<IAnimation> layer = (ModifierLayer<
|
||||
IAnimation
|
||||
>) PlayerAnimationAccess.getPlayerAssociatedData(
|
||||
clientPlayer
|
||||
).get(FURNITURE_FACTORY_ID);
|
||||
if (layer != null) {
|
||||
return layer;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fall through to fallback-create below.
|
||||
}
|
||||
|
||||
// Remote players: fallback-create via the animation stack.
|
||||
if (clientPlayer instanceof IAnimatedPlayer animated) {
|
||||
return npcFurnitureLayers.computeIfAbsent(
|
||||
clientPlayer.getUUID(),
|
||||
k -> {
|
||||
ModifierLayer<IAnimation> newLayer =
|
||||
new ModifierLayer<>();
|
||||
animated
|
||||
.getAnimationStack()
|
||||
.addAnimLayer(FURNITURE_LAYER_PRIORITY, newLayer);
|
||||
LOGGER.debug(
|
||||
"Created furniture animation layer for remote player via stack: {}",
|
||||
clientPlayer.getName().getString()
|
||||
);
|
||||
return newLayer;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return npcFurnitureLayers.get(clientPlayer.getUUID());
|
||||
}
|
||||
|
||||
// Non-player entities: use NPC cache (read-only; NPC furniture animation
|
||||
// is not currently produced by this codebase).
|
||||
return npcFurnitureLayers.get(player.getUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Safety tick for furniture animations. Call once per client tick per player.
|
||||
* <p>Phase 2.8 : no-op — the player furniture layer is dead, nothing to
|
||||
* guard. Kept as an empty stub in case older call sites remain.</p>
|
||||
*
|
||||
* <p>If a player has an active furniture animation but is NOT riding an
|
||||
* {@link ISeatProvider}, increment a grace counter. After
|
||||
* {@link #FURNITURE_GRACE_TICKS} consecutive ticks without a seat, the
|
||||
* animation is removed to prevent stuck poses from entity death, network
|
||||
* desync, or teleportation.</p>
|
||||
*
|
||||
* <p>If the player IS riding an ISeatProvider, the counter is reset.</p>
|
||||
*
|
||||
* @param player the player to check
|
||||
* @param player the player to check (unused)
|
||||
*/
|
||||
public static void tickFurnitureSafety(Player player) {
|
||||
if (player == null || !player.level().isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasFurnitureAnimation(player)) {
|
||||
// No furniture animation active, nothing to guard
|
||||
furnitureGraceTicks.remove(player.getUUID());
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = player.getUUID();
|
||||
|
||||
// Check if the player is riding an ISeatProvider
|
||||
Entity vehicle = player.getVehicle();
|
||||
boolean ridingSeat = vehicle instanceof ISeatProvider;
|
||||
|
||||
if (ridingSeat) {
|
||||
// Player is properly seated, reset grace counter
|
||||
furnitureGraceTicks.remove(uuid);
|
||||
} else {
|
||||
// Player has furniture anim but no seat -- increment grace
|
||||
int ticks = furnitureGraceTicks.merge(uuid, 1, Integer::sum);
|
||||
if (ticks >= FURNITURE_GRACE_TICKS) {
|
||||
LOGGER.info(
|
||||
"Removing stale furniture animation for player {} " +
|
||||
"(not riding ISeatProvider for {} ticks)",
|
||||
player.getName().getString(),
|
||||
ticks
|
||||
);
|
||||
stopFurniture(player);
|
||||
}
|
||||
}
|
||||
// Phase 2.8 : dead path. Retained signature for backward-compat.
|
||||
}
|
||||
|
||||
// FALLBACK ANIMATION HANDLING
|
||||
@@ -789,8 +540,9 @@ public class BondageAnimationManager {
|
||||
* @param entityId UUID of the removed entity
|
||||
*/
|
||||
/** All NPC layer caches, for bulk cleanup operations. */
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES =
|
||||
new Map[] { npcLayers, npcContextLayers, npcFurnitureLayers };
|
||||
new Map[] { npcLayers, npcContextLayers };
|
||||
|
||||
public static void cleanup(UUID entityId) {
|
||||
for (Map<UUID, ModifierLayer<IAnimation>> cache : ALL_NPC_CACHES) {
|
||||
@@ -799,8 +551,7 @@ public class BondageAnimationManager {
|
||||
layer.setAnimation(null);
|
||||
}
|
||||
}
|
||||
furnitureGraceTicks.remove(entityId);
|
||||
layerFailureLogged.remove(entityId);
|
||||
playerCallLogged.remove(entityId);
|
||||
LOGGER.debug("Cleaned up animation layers for entity: {}", entityId);
|
||||
}
|
||||
|
||||
@@ -813,8 +564,7 @@ public class BondageAnimationManager {
|
||||
cache.values().forEach(layer -> layer.setAnimation(null));
|
||||
cache.clear();
|
||||
}
|
||||
furnitureGraceTicks.clear();
|
||||
layerFailureLogged.clear();
|
||||
playerCallLogged.clear();
|
||||
LOGGER.info("Cleared all NPC animation layers");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,15 @@ public class DogPoseRenderHandler {
|
||||
|
||||
/**
|
||||
* Get the rotation delta applied to a player's render for DOG pose.
|
||||
* Used by MixinPlayerModel to compensate head rotation.
|
||||
*
|
||||
* @deprecated since Phase 2.8 — this getter fed {@code MixinPlayerModel}
|
||||
* (removed Phase 2.8 RIG cleanup) so head rotation could be compensated
|
||||
* against the body's -90° pitch. No remaining reader. To be deleted
|
||||
* when V3-REW-07 re-expresses dog pose head compensation as a RIG
|
||||
* {@code StaticAnimation pose_dog.json}. See
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
|
||||
*/
|
||||
@Deprecated(since = "2.8")
|
||||
public static float getAppliedRotationDelta(UUID playerUuid) {
|
||||
float[] state = dogPoseState.get(playerUuid);
|
||||
return state != null ? state[IDX_DELTA] : 0f;
|
||||
@@ -61,7 +68,14 @@ public class DogPoseRenderHandler {
|
||||
|
||||
/**
|
||||
* Check if a player is currently moving in DOG pose.
|
||||
*
|
||||
* @deprecated since Phase 2.8 — same cause as {@link #getAppliedRotationDelta}
|
||||
* (fed {@code MixinPlayerModel}, now removed). To be deleted alongside
|
||||
* V3-REW-07 when dog pose head compensation is re-expressed as a RIG
|
||||
* {@code StaticAnimation pose_dog.json}. See
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
|
||||
*/
|
||||
@Deprecated(since = "2.8")
|
||||
public static boolean isDogPoseMoving(UUID playerUuid) {
|
||||
float[] state = dogPoseState.get(playerUuid);
|
||||
return state != null && state[IDX_MOVING] > 0.5f;
|
||||
|
||||
@@ -3,29 +3,17 @@ package com.tiedup.remake.client.animation.tick;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.tiedup.remake.client.animation.AnimationStateRegistry;
|
||||
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
||||
import com.tiedup.remake.client.animation.PendingAnimationManager;
|
||||
import com.tiedup.remake.client.animation.context.AnimationContext;
|
||||
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
|
||||
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
|
||||
import com.tiedup.remake.client.events.CellHighlightHandler;
|
||||
import com.tiedup.remake.client.events.LeashProxyClientHandler;
|
||||
import com.tiedup.remake.client.gltf.GltfAnimationApplier;
|
||||
import com.tiedup.remake.client.state.ClothesClientCache;
|
||||
import com.tiedup.remake.client.state.MovementStyleClientState;
|
||||
import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
@@ -35,16 +23,29 @@ import net.minecraftforge.fml.common.Mod;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Event handler for player animation tick updates.
|
||||
* Event handler for animation tick updates.
|
||||
*
|
||||
* <p>Simplified handler that:
|
||||
* <p><b>Phase 2.8 RIG cleanup</b> : le ticking <i>player</i> V2 (boucle
|
||||
* {@code mc.level.players()} + {@code updatePlayerAnimation}) est entièrement
|
||||
* désactivé. Les joueurs sont désormais pilotés par
|
||||
* {@link com.tiedup.remake.rig.tick.RigAnimationTickHandler} via le pipeline
|
||||
* RIG (capability {@code LivingEntityPatch} + {@code Animator} natif EF).
|
||||
* Les features V2 qui dépendaient du tick player sont trackées dans
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md} (V3-REW-01/02/03/07).</p>
|
||||
*
|
||||
* <p>Restent actifs ici :
|
||||
* <ul>
|
||||
* <li>Tracks tied/struggling/sneaking state for players</li>
|
||||
* <li>Plays animations via BondageAnimationManager when state changes</li>
|
||||
* <li>Handles cleanup on logout/world unload</li>
|
||||
* <li>Nettoyage périodique de {@code ClothesClientCache} (cache remote
|
||||
* players, hygiène mémoire indépendante du pipeline de rendu)</li>
|
||||
* <li>Cleanup logout / world unload (caches V2 encore utilisés par les
|
||||
* NPCs ticked par {@link NpcAnimationTickHandler})</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Registered on the FORGE event bus (not MOD bus).
|
||||
* <p>Le ticking NPC est assuré par {@link NpcAnimationTickHandler}. Ce
|
||||
* handler ne tick plus les NPCs directement — il ne gère que les hooks
|
||||
* lifecycle globaux (logout + world unload).</p>
|
||||
*
|
||||
* <p>Registered on the FORGE event bus (not MOD bus).</p>
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = "tiedup",
|
||||
@@ -83,8 +84,20 @@ public class AnimationTickHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Client tick event - called every tick on the client.
|
||||
* Updates animations for all players when their bondage state changes.
|
||||
* Client tick event — called every tick on the client.
|
||||
*
|
||||
* <p>Phase 2.8 : la boucle {@code mc.level.players()} qui appelait
|
||||
* {@code updatePlayerAnimation}, {@code tickFurnitureSafety} et le
|
||||
* cold-cache retry furniture a été entièrement supprimée. Les joueurs
|
||||
* sont désormais ticked par {@link com.tiedup.remake.rig.tick.RigAnimationTickHandler}
|
||||
* via le pipeline RIG (capability {@code LivingEntityPatch} +
|
||||
* {@code Animator}). Les régressions visuelles (V2 bondage layer cassé,
|
||||
* furniture seat pose sur joueur cassée, pet bed pose cassée) sont
|
||||
* listées dans {@code docs/plans/rig/V3_REWORK_BACKLOG.md}.</p>
|
||||
*
|
||||
* <p>Seul le nettoyage périodique de {@link ClothesClientCache} reste
|
||||
* — c'est de l'hygiène mémoire sur un cache indexé UUID joueur,
|
||||
* indépendant du pipeline de rendu.</p>
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
@@ -97,193 +110,17 @@ public class AnimationTickHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process pending animations first (retry failed animations for remote players)
|
||||
PendingAnimationManager.processPending(mc.level);
|
||||
|
||||
// Periodic cleanup of stale cache entries (every 60 seconds = 1200 ticks)
|
||||
// Periodic cleanup of stale clothes cache entries (every 60 seconds = 1200 ticks).
|
||||
// Indépendant du rendu V2/RIG — c'est juste un cache UUID→ClothesData qui
|
||||
// doit libérer la mémoire des joueurs déconnectés depuis >5min.
|
||||
if (++cleanupTickCounter >= 1200) {
|
||||
cleanupTickCounter = 0;
|
||||
ClothesClientCache.cleanupStale();
|
||||
}
|
||||
|
||||
// Then update all player animations
|
||||
for (Player player : mc.level.players()) {
|
||||
if (player instanceof AbstractClientPlayer clientPlayer) {
|
||||
updatePlayerAnimation(clientPlayer);
|
||||
}
|
||||
// Safety: remove stale furniture animations for players no longer on seats
|
||||
BondageAnimationManager.tickFurnitureSafety(player);
|
||||
// Cold-cache retry: if the player is seated on furniture but has no
|
||||
// active pose (GLB was not yet loaded at mount time, or the GLB cache
|
||||
// entry was a transient failure), retry until the cache warms.
|
||||
// FurnitureGltfCache memoizes failures via Optional.empty(), so
|
||||
// retries after a genuine parse failure return instantly with no
|
||||
// reparse. Bounded at MAX_FURNITURE_RETRIES so a legacy V1-only
|
||||
// GLB (no Player_* armature → seatSkeleton==null → no animation
|
||||
// ever possible) doesn't spam retries at 20 Hz forever.
|
||||
// Single read of getVehicle() — avoids a re-read where the
|
||||
// vehicle could change between instanceof and cast.
|
||||
com.tiedup.remake.v2.furniture.EntityFurniture furniture =
|
||||
player.getVehicle() instanceof
|
||||
com.tiedup.remake.v2.furniture.EntityFurniture f ? f : null;
|
||||
boolean hasAnim = BondageAnimationManager.hasFurnitureAnimation(
|
||||
player
|
||||
);
|
||||
UUID playerUuid = player.getUUID();
|
||||
if (furniture != null && !hasAnim) {
|
||||
int retries = furnitureRetryCounters.getOrDefault(
|
||||
playerUuid,
|
||||
0
|
||||
);
|
||||
if (retries < MAX_FURNITURE_RETRIES) {
|
||||
furnitureRetryCounters.put(playerUuid, retries + 1);
|
||||
com.tiedup.remake.v2.furniture.client.FurnitureClientAnimator
|
||||
.start(furniture, player);
|
||||
if (retries + 1 == MAX_FURNITURE_RETRIES) {
|
||||
LOGGER.debug(
|
||||
"[FurnitureAnim] Giving up on furniture animation retry for {} after {} attempts — GLB likely has no Player_* armature.",
|
||||
player.getName().getString(),
|
||||
MAX_FURNITURE_RETRIES
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Dismounted or successfully applied — drop the counter so a
|
||||
// later re-mount starts fresh.
|
||||
furnitureRetryCounters.remove(playerUuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update animation for a single player.
|
||||
*/
|
||||
private static void updatePlayerAnimation(AbstractClientPlayer player) {
|
||||
// Safety check: skip for removed/dead players
|
||||
if (player.isRemoved() || !player.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
UUID uuid = player.getUUID();
|
||||
|
||||
// Check if player has ANY V2 bondage item equipped (not just ARMS).
|
||||
// isTiedUp() only checks ARMS, but items on LEGS, HEAD, etc. also need animation.
|
||||
boolean isTied =
|
||||
state != null &&
|
||||
(state.isTiedUp() || V2EquipmentHelper.hasAnyEquipment(player));
|
||||
boolean wasTied =
|
||||
AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false);
|
||||
|
||||
// Pet bed animations take priority over bondage animations
|
||||
if (PetBedClientState.get(uuid) != 0) {
|
||||
// Lock body rotation to bed facing (prevents camera from rotating the model)
|
||||
float lockedRot = PetBedClientState.getFacing(uuid);
|
||||
player.yBodyRot = lockedRot;
|
||||
player.yBodyRotO = lockedRot;
|
||||
|
||||
// Clamp head rotation to ±50° from body (like vehicle)
|
||||
float headRot = player.getYHeadRot();
|
||||
float clamped =
|
||||
lockedRot +
|
||||
net.minecraft.util.Mth.clamp(
|
||||
net.minecraft.util.Mth.wrapDegrees(headRot - lockedRot),
|
||||
-50f,
|
||||
50f
|
||||
);
|
||||
player.setYHeadRot(clamped);
|
||||
player.yHeadRotO = clamped;
|
||||
|
||||
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
|
||||
return;
|
||||
}
|
||||
|
||||
// Human chair: clamp 1st-person camera only (body lock handled by MixinLivingEntityBodyRot)
|
||||
// NO return — animation HUMAN_CHAIR must continue playing below
|
||||
if (isTied && state != null) {
|
||||
ItemStack chairBind = state.getEquipment(BodyRegionV2.ARMS);
|
||||
if (HumanChairHelper.isActive(chairBind)) {
|
||||
// 1st person only: clamp yRot so player can't look behind
|
||||
// 3rd person: yRot untouched → camera orbits freely 360°
|
||||
if (
|
||||
player == Minecraft.getInstance().player &&
|
||||
Minecraft.getInstance().options.getCameraType() ==
|
||||
net.minecraft.client.CameraType.FIRST_PERSON
|
||||
) {
|
||||
float lockedRot = HumanChairHelper.getFacing(chairBind);
|
||||
float camClamped =
|
||||
lockedRot +
|
||||
net.minecraft.util.Mth.clamp(
|
||||
net.minecraft.util.Mth.wrapDegrees(
|
||||
player.getYRot() - lockedRot
|
||||
),
|
||||
-90f,
|
||||
90f
|
||||
);
|
||||
player.setYRot(camClamped);
|
||||
player.yRotO =
|
||||
lockedRot +
|
||||
net.minecraft.util.Mth.clamp(
|
||||
net.minecraft.util.Mth.wrapDegrees(
|
||||
player.yRotO - lockedRot
|
||||
),
|
||||
-90f,
|
||||
90f
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTied) {
|
||||
// Resolve V2 equipped items
|
||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
|
||||
player
|
||||
);
|
||||
Map<BodyRegionV2, ItemStack> equipped =
|
||||
equipment != null ? equipment.getAllEquipped() : Map.of();
|
||||
|
||||
// Resolve ALL V2 items with GLB models and per-item bone ownership
|
||||
java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items =
|
||||
RegionBoneMapper.resolveAllV2Items(equipped);
|
||||
|
||||
if (!v2Items.isEmpty()) {
|
||||
// V2 path: multi-item composite animation
|
||||
java.util.Set<String> allOwnedParts =
|
||||
RegionBoneMapper.computeAllOwnedParts(v2Items);
|
||||
MovementStyle activeStyle = MovementStyleClientState.get(
|
||||
player.getUUID()
|
||||
);
|
||||
AnimationContext context = AnimationContextResolver.resolve(
|
||||
player,
|
||||
state,
|
||||
activeStyle
|
||||
);
|
||||
GltfAnimationApplier.applyMultiItemV2Animation(
|
||||
player,
|
||||
v2Items,
|
||||
context,
|
||||
allOwnedParts
|
||||
);
|
||||
} else if (GltfAnimationApplier.hasActiveState(player)) {
|
||||
// Clear any residual V2 composite animation when the player
|
||||
// is still isTiedUp() but has no GLB-bearing items — e.g.
|
||||
// a non-GLB item keeps the tied state, or a GLB item was
|
||||
// removed while another V2 item remains on a non-animated
|
||||
// region. Leaving the composite in place locks the arms in
|
||||
// the pose of an item the player no longer wears.
|
||||
GltfAnimationApplier.clearV2Animation(player);
|
||||
}
|
||||
} else if (wasTied) {
|
||||
// Was tied, now free - stop all animations
|
||||
if (GltfAnimationApplier.hasActiveState(player)) {
|
||||
GltfAnimationApplier.clearV2Animation(player);
|
||||
} else {
|
||||
BondageAnimationManager.stopAnimation(player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
|
||||
// Le tick per-player V2 (updatePlayerAnimation, tickFurnitureSafety,
|
||||
// cold-cache furniture retry) est délégué à RigAnimationTickHandler
|
||||
// Phase 2.7+. Rien à faire ici.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,22 +15,14 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
* <li>Head yaw: convert to zRot (roll) since yRot axis is sideways</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Architecture: Players vs NPCs</h2>
|
||||
* <pre>
|
||||
* ┌─────────────────────────────────────────────────────────────────┐
|
||||
* │ PLAYERS │
|
||||
* ├─────────────────────────────────────────────────────────────────┤
|
||||
* │ 1. PlayerArmHideEventHandler.onRenderPlayerPre() │
|
||||
* │ - Offset vertical (-6 model units) │
|
||||
* │ - Rotation Y lissée (dogPoseState tracking) │
|
||||
* │ │
|
||||
* │ 2. Animation (PlayerAnimator) │
|
||||
* │ - body.pitch = -90° → appliqué au PoseStack automatiquement │
|
||||
* │ │
|
||||
* │ 3. MixinPlayerModel.setupAnim() @TAIL │
|
||||
* │ - Uses DogPoseHelper.applyHeadCompensationClamped() │
|
||||
* └─────────────────────────────────────────────────────────────────┘
|
||||
* <h2>Architecture — NPCs only (Phase 2.8 RIG cleanup)</h2>
|
||||
* <p>Le path PLAYER (ex-{@code MixinPlayerModel.setupAnim @TAIL}) a été retiré
|
||||
* Phase 2.8 : le renderer RIG patched ne passe plus par {@code PlayerModel.setupAnim},
|
||||
* donc le mixin devenait dead code. La compensation head dog pose sera ré-exprimée
|
||||
* nativement en StaticAnimation {@code pose_dog.json} (cf. V3-REW-07 dans
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md}).</p>
|
||||
*
|
||||
* <pre>
|
||||
* ┌─────────────────────────────────────────────────────────────────┐
|
||||
* │ NPCs │
|
||||
* ├─────────────────────────────────────────────────────────────────┤
|
||||
@@ -48,25 +40,13 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
* └─────────────────────────────────────────────────────────────────┘
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Key Differences</h2>
|
||||
* <table>
|
||||
* <tr><th>Aspect</th><th>Players</th><th>NPCs</th></tr>
|
||||
* <tr><td>Rotation X application</td><td>Auto by PlayerAnimator</td><td>Manual in setupRotations()</td></tr>
|
||||
* <tr><td>Rotation Y smoothing</td><td>PlayerArmHideEventHandler</td><td>EntityDamsel.tick() via RotationSmoother</td></tr>
|
||||
* <tr><td>Head compensation</td><td>MixinPlayerModel</td><td>DamselModel.setupAnim()</td></tr>
|
||||
* <tr><td>Reset body.xRot</td><td>Not needed</td><td>Yes (prevents double rotation)</td></tr>
|
||||
* <tr><td>Vertical offset</td><td>-6 model units</td><td>-7 model units</td></tr>
|
||||
* </table>
|
||||
*
|
||||
* <h2>Usage</h2>
|
||||
* <p>Used by:
|
||||
* <ul>
|
||||
* <li>MixinPlayerModel - for player head compensation</li>
|
||||
* <li>DamselModel - for NPC head compensation</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see RotationSmoother for Y rotation smoothing
|
||||
* @see com.tiedup.remake.mixin.client.MixinPlayerModel
|
||||
* @see com.tiedup.remake.client.model.DamselModel
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@@ -130,7 +110,14 @@ public final class DogPoseHelper {
|
||||
* @param headPitch Player's up/down look angle in degrees
|
||||
* @param headYaw Head yaw relative to body in degrees
|
||||
* @param maxYaw Maximum allowed yaw angle in degrees
|
||||
* @deprecated since Phase 2.8 — player dog pose head compensation was
|
||||
* previously applied via {@code MixinPlayerModel.setupAnim @TAIL}
|
||||
* (removed Phase 2.8 RIG cleanup). No remaining call site; retained
|
||||
* only to preserve the API until V3-REW-07 re-expresses the behavior
|
||||
* as a RIG {@code StaticAnimation pose_dog.json}. See
|
||||
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
|
||||
*/
|
||||
@Deprecated(since = "2.8")
|
||||
public static void applyHeadCompensationClamped(
|
||||
ModelPart head,
|
||||
ModelPart hat,
|
||||
|
||||
@@ -137,6 +137,14 @@ public class TiedUpMod {
|
||||
|
||||
// Register dispenser behaviors (must be on main thread)
|
||||
event.enqueueWork(DispenserBehaviors::register);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,9 +193,14 @@ public class TiedUpMod {
|
||||
|
||||
// Initialize animation system
|
||||
event.enqueueWork(() -> {
|
||||
// Initialize unified BondageAnimationManager
|
||||
com.tiedup.remake.client.animation.BondageAnimationManager.init();
|
||||
LOGGER.info("BondageAnimationManager initialized");
|
||||
// RIG Phase 2 — override client dispatch PLAYER → Local/Client/ServerPlayerPatch
|
||||
com.tiedup.remake.rig.patch.EntityPatchProvider.registerEntityPatchesClient();
|
||||
|
||||
// Phase 2.8 RIG cleanup : BondageAnimationManager.init() (factory
|
||||
// registrations PlayerAnimator côté joueur) a été supprimé — le RIG
|
||||
// prend le relai pour les joueurs via RigAnimationTickHandler.
|
||||
// Les NPCs continuent d'être animés via BondageAnimationManager en
|
||||
// accès direct animation stack (cf. NpcAnimationTickHandler).
|
||||
|
||||
// Initialize OBJ model registry for 3D bondage items
|
||||
com.tiedup.remake.client.renderer.obj.ObjModelRegistry.init();
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.client.animation.render.DogPoseRenderHandler;
|
||||
import com.tiedup.remake.client.animation.util.DogPoseHelper;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin for PlayerModel to handle DOG pose head adjustments.
|
||||
*
|
||||
* When in DOG pose (body horizontal):
|
||||
* - Head pitch offset so player looks forward
|
||||
* - Head yaw converted to zRot (roll) since yRot axis is sideways when body is horizontal
|
||||
*/
|
||||
@Mixin(PlayerModel.class)
|
||||
public class MixinPlayerModel {
|
||||
|
||||
@Inject(method = "setupAnim", at = @At("TAIL"))
|
||||
private void tiedup$adjustDogPose(
|
||||
LivingEntity entity,
|
||||
float limbSwing,
|
||||
float limbSwingAmount,
|
||||
float ageInTicks,
|
||||
float netHeadYaw,
|
||||
float headPitch,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
if (!(entity instanceof AbstractClientPlayer player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
|
||||
if (bind.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerModel<?> model = (PlayerModel<?>) (Object) this;
|
||||
|
||||
// === HEAD ROTATION FOR HORIZONTAL BODY ===
|
||||
// Body is at -90° pitch (horizontal, face down)
|
||||
// We apply a rotation delta to the poseStack in PlayerArmHideEventHandler
|
||||
// The head needs to compensate for this transformation
|
||||
|
||||
float rotationDelta = DogPoseRenderHandler.getAppliedRotationDelta(
|
||||
player.getUUID()
|
||||
);
|
||||
boolean moving = DogPoseRenderHandler.isDogPoseMoving(player.getUUID());
|
||||
|
||||
// netHeadYaw is head relative to vanilla body (yHeadRot - yBodyRot)
|
||||
// We rotated the model by rotationDelta, so compensate:
|
||||
// effectiveHeadYaw = netHeadYaw + rotationDelta
|
||||
float headYaw = netHeadYaw + rotationDelta;
|
||||
|
||||
// Clamp based on movement state and apply head compensation
|
||||
float maxYaw = moving ? 60f : 90f;
|
||||
DogPoseHelper.applyHeadCompensationClamped(
|
||||
model.head,
|
||||
model.hat,
|
||||
headPitch,
|
||||
headYaw,
|
||||
maxYaw
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
/**
|
||||
* Remplace {@code yesman.epicfight.config.ClientConfig} du fork upstream.
|
||||
* Expose uniquement les flags pertinents au pipeline animation/rendu RIG.
|
||||
*
|
||||
* <p><b>Note Phase 0</b> : les flags sont des {@code static final} avec
|
||||
* valeurs par défaut hardcodées. À convertir en {@code ForgeConfigSpec} réel
|
||||
* (TOML config file) Phase 2 ou plus tard si on veut permettre la
|
||||
* configuration utilisateur.</p>
|
||||
*/
|
||||
public final class TiedUpAnimationConfig {
|
||||
|
||||
private TiedUpAnimationConfig() {}
|
||||
|
||||
/**
|
||||
* Toggle pour le chemin "compute shader" de {@code SkinnedMesh.draw} —
|
||||
* quand true et qu'un {@code ComputeShaderSetup} est disponible, la mesh
|
||||
* est skinnée côté GPU (plus rapide sur modèles lourds). False (défaut)
|
||||
* = skin CPU comme vanilla.
|
||||
*/
|
||||
public static final boolean activateComputeShader = false;
|
||||
}
|
||||
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 (0.15s = 3 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;
|
||||
}
|
||||
}
|
||||
230
src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java
Normal file
230
src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import com.tiedup.remake.rig.armature.HumanoidArmature;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
/**
|
||||
* Registry des armatures TiedUp exposé via {@link AssetAccessor} constants.
|
||||
*
|
||||
* <h2>Phase 2.4 — version procédurale</h2>
|
||||
*
|
||||
* <p>Cette classe construit le biped TiedUp <b>from scratch en Java</b>
|
||||
* (hiérarchie + offsets identity). Suffisant pour débloquer le rendering RIG
|
||||
* Phase 2.4 : les joints existent dans le map, {@code searchJointByName}
|
||||
* fonctionne, le GLB → SkinnedMesh bridge a un mapping valide, etc.</p>
|
||||
*
|
||||
* <p><b>Phase 2.7 remplacera par un JSON Blender-authored hot-reloadable</b>.
|
||||
* Pour l'instant, les joints sont tous à l'identité (offset/rotation nuls).
|
||||
* Visuellement ça donnera un biped "effondré" sur le point d'origine si on
|
||||
* rend sans animation — c'est acceptable car :</p>
|
||||
* <ul>
|
||||
* <li>Phase 2.4 n'a pas encore de renderer player patched complet (Phase 2.5)</li>
|
||||
* <li>Phase 2.7 rechargera des offsets depuis {@code assets/tiedup/armatures/biped.json}
|
||||
* co-authored via addon Blender (cf. MIGRATION.md §2.2.1)</li>
|
||||
* <li>Les tests existants `GltfToSkinnedMeshTest` utilisent déjà le même pattern
|
||||
* (Armature identity, {@code bakeOriginMatrices}) et sont verts</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Hiérarchie biped EF (20 joints)</h2>
|
||||
*
|
||||
* <pre>
|
||||
* Root id=0
|
||||
* ├─ Thigh_R ── Leg_R ── Knee_R id=1,2,3
|
||||
* ├─ Thigh_L ── Leg_L ── Knee_L id=4,5,6
|
||||
* └─ Torso id=7
|
||||
* └─ Chest id=8
|
||||
* ├─ Head id=9
|
||||
* ├─ Shoulder_R ── Arm_R ── Elbow_R ── Hand_R ── Tool_R ids=10,11,14,12,13
|
||||
* └─ Shoulder_L ── Arm_L ── Elbow_L ── Hand_L ── Tool_L ids=15,16,19,17,18
|
||||
* </pre>
|
||||
*
|
||||
* <p>Les IDs des bras ne suivent pas l'ordre hiérarchique parent→enfant : c'est
|
||||
* voulu pour rester aligné avec le layout attendu par {@code VanillaModelTransformer}
|
||||
* (upperJoint=Arm, lowerJoint=Hand, middleJoint=Elbow). Voir {@link #buildBiped()}.</p>
|
||||
*
|
||||
* <p><b>Noms conservés verbatim EF</b> (pas renommés en TiedUp style) car :</p>
|
||||
* <ul>
|
||||
* <li>Le {@code VanillaModelTransformer} forké EF (Phase 2.2) référence ces
|
||||
* noms dans ses AABB / {@code WEIGHT_ALONG_Y} / {@code yClipCoord}</li>
|
||||
* <li>Le bridge GLB ({@code LegacyJointNameMapper}) mappe déjà les joints
|
||||
* PlayerAnimator legacy sur ces noms-là</li>
|
||||
* <li>Re-authored serait un risque régression sans gain fonctionnel</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class TiedUpArmatures {
|
||||
|
||||
private TiedUpArmatures() {}
|
||||
|
||||
/** ResourceLocation registry pour l'accessor (même path que EF pour cohérence doc). */
|
||||
private static final ResourceLocation BIPED_REGISTRY_NAME =
|
||||
ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "armature/biped");
|
||||
|
||||
/**
|
||||
* Holder idiome pour init lazy + thread-safe sans synchronized.
|
||||
*
|
||||
* <p>Le class loader JVM garantit qu'une classe est initialisée au plus une
|
||||
* fois, et que l'init est visible à tous les threads (JLS §12.4.1 — the class
|
||||
* initialization lock est acquis automatiquement). Deux threads qui touchent
|
||||
* {@code Holder.INSTANCE} simultanément ne peuvent pas observer l'instance
|
||||
* non-initialisée ni en créer deux exemplaires. Intégré SP (client + server
|
||||
* threads concurrents sur la même JVM) safe.</p>
|
||||
*
|
||||
* <p>Raison du fix (review Phase 2.4, P0-BUG-002) : le pattern précédent
|
||||
* {@code if (BIPED_INSTANCE == null) BIPED_INSTANCE = buildBiped();} est
|
||||
* un double-init race — deux threads entrent tous les deux dans le if,
|
||||
* les deux créent un HumanoidArmature distinct, dernier gagne et pollue
|
||||
* le cache.</p>
|
||||
*/
|
||||
private static final class Holder {
|
||||
static final HumanoidArmature INSTANCE;
|
||||
static {
|
||||
// Signal visible au dev que les joints sont en identity transform.
|
||||
// Sans ça, Phase 2.6+ câblera le renderer et le mesh apparaîtra
|
||||
// "effondré à l'origine" sans signal — debug cauchemar. Le warn
|
||||
// n'apparaît qu'une fois (class-init lock JVM).
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"TiedUpArmatures.BIPED initialized with IDENTITY joint transforms (Phase 2.4 stub). "
|
||||
+ "Mesh will render collapsed-to-origin until Phase 2.7 provides biped.json "
|
||||
+ "Blender-authored offsets. See docs/plans/rig/PHASE0_DEGRADATIONS.md "
|
||||
+ "Phase 2.4 backlog entry #1."
|
||||
);
|
||||
INSTANCE = buildBiped();
|
||||
}
|
||||
private Holder() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* AssetAccessor biped TiedUp. L'instance est construite lazy à la première
|
||||
* référence {@code Holder.INSTANCE} (thread-safe via class-init lock JVM).
|
||||
*
|
||||
* <p>Utilisé par {@link com.tiedup.remake.rig.patch.PlayerPatch#getArmature()}
|
||||
* et par les futurs {@code StaticAnimation(… , BIPED)} Phase 2.7+.</p>
|
||||
*/
|
||||
public static final AssetAccessor<HumanoidArmature> BIPED = new AssetAccessor<>() {
|
||||
@Override
|
||||
public HumanoidArmature get() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return BIPED_REGISTRY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
// Pas dans un JsonAssetLoader registry tant que Phase 2.7 n'a pas
|
||||
// posé le biped.json. Une fois fait, ce flag repassera à true via
|
||||
// un nouveau registry layer.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build procédural de la hiérarchie biped EF. 20 joints, IDs 0..19 assignés
|
||||
* explicitement pour matcher le layout attendu par
|
||||
* {@link com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer}
|
||||
* (cf. constantes {@code RIGHT_ARM}/{@code LEFT_ARM} — upperJoint/lowerJoint/middleJoint).
|
||||
*
|
||||
* <p>Ordre d'insertion LinkedHashMap ≠ ordre des IDs pour les bras (hand
|
||||
* vient AVANT elbow dans les IDs, pour aligner sur la sémantique EF où
|
||||
* {@code middleJoint = elbow}). Voir commentaires inline.</p>
|
||||
*
|
||||
* <p>Toutes les transforms sont identity — Phase 2.7 remplacera par les
|
||||
* offsets Blender mesurés (cf. doc header).</p>
|
||||
*/
|
||||
private static HumanoidArmature buildBiped() {
|
||||
// ID Joint assigné explicitement au constructeur (pas par position dans
|
||||
// la map) — Armature.jointById est construit depuis joint.getId().
|
||||
// On utilise LinkedHashMap malgré tout pour garantir un ordre d'itération
|
||||
// stable (utile pour le debug et pour OpenMatrix4f.allocateMatrixArray
|
||||
// qui dimensionne sur jointCount).
|
||||
Map<String, Joint> joints = new LinkedHashMap<>(20);
|
||||
|
||||
// Pattern EF : tous les joints démarrent avec une localTransform identity.
|
||||
// bakeOriginMatrices() calcule ensuite les toOrigin relatives à la
|
||||
// hiérarchie parent→enfant.
|
||||
Joint root = joint(joints, "Root", 0);
|
||||
|
||||
// Jambes
|
||||
Joint thighR = joint(joints, "Thigh_R", 1);
|
||||
Joint legR = joint(joints, "Leg_R", 2);
|
||||
Joint kneeR = joint(joints, "Knee_R", 3);
|
||||
Joint thighL = joint(joints, "Thigh_L", 4);
|
||||
Joint legL = joint(joints, "Leg_L", 5);
|
||||
Joint kneeL = joint(joints, "Knee_L", 6);
|
||||
|
||||
// Tronc
|
||||
Joint torso = joint(joints, "Torso", 7);
|
||||
Joint chest = joint(joints, "Chest", 8);
|
||||
Joint head = joint(joints, "Head", 9);
|
||||
|
||||
// Bras droit — IDs alignés sur le layout EF (VanillaModelTransformer.RIGHT_ARM
|
||||
// encode upperJoint=11, lowerJoint=12, middleJoint=14, cf.
|
||||
// VanillaModelTransformer:50). L'insertion dans le LinkedHashMap reste
|
||||
// dans l'ordre hiérarchique (shoulder → arm → elbow → hand → tool) pour
|
||||
// préserver la lisibilité de l'iteration ; les IDs déterminent le
|
||||
// mapping jointById utilisé par VanillaModelTransformer + SimpleTransformer.
|
||||
Joint shoulderR = joint(joints, "Shoulder_R", 10);
|
||||
Joint armR = joint(joints, "Arm_R", 11);
|
||||
Joint handR = joint(joints, "Hand_R", 12);
|
||||
Joint toolR = joint(joints, "Tool_R", 13);
|
||||
Joint elbowR = joint(joints, "Elbow_R", 14);
|
||||
|
||||
// Bras gauche — symétrique : Arm_L=16, Hand_L=17, Tool_L=18, Elbow_L=19
|
||||
// (VanillaModelTransformer.LEFT_ARM upperJoint=16, lowerJoint=17, middleJoint=19).
|
||||
Joint shoulderL = joint(joints, "Shoulder_L", 15);
|
||||
Joint armL = joint(joints, "Arm_L", 16);
|
||||
Joint handL = joint(joints, "Hand_L", 17);
|
||||
Joint toolL = joint(joints, "Tool_L", 18);
|
||||
Joint elbowL = joint(joints, "Elbow_L", 19);
|
||||
|
||||
// Hiérarchie. addSubJoints est idempotent (skip si déjà présent) — safe
|
||||
// de le réappeler, utile si on étend plus tard.
|
||||
root.addSubJoints(thighR, thighL, torso);
|
||||
thighR.addSubJoints(legR);
|
||||
legR.addSubJoints(kneeR);
|
||||
thighL.addSubJoints(legL);
|
||||
legL.addSubJoints(kneeL);
|
||||
|
||||
torso.addSubJoints(chest);
|
||||
chest.addSubJoints(head, shoulderR, shoulderL);
|
||||
|
||||
shoulderR.addSubJoints(armR);
|
||||
armR.addSubJoints(elbowR);
|
||||
elbowR.addSubJoints(handR);
|
||||
handR.addSubJoints(toolR);
|
||||
|
||||
shoulderL.addSubJoints(armL);
|
||||
armL.addSubJoints(elbowL);
|
||||
elbowL.addSubJoints(handL);
|
||||
handL.addSubJoints(toolL);
|
||||
|
||||
HumanoidArmature arm = new HumanoidArmature("biped", joints.size(), root, joints);
|
||||
|
||||
// Calcule les toOrigin relatifs — obligatoire après la construction
|
||||
// sinon Pose.orElseEmpty retournerait des matrices non initialisées.
|
||||
arm.bakeOriginMatrices();
|
||||
|
||||
return arm;
|
||||
}
|
||||
|
||||
private static Joint joint(Map<String, Joint> target, String name, int id) {
|
||||
Joint j = new Joint(name, id, new OpenMatrix4f());
|
||||
target.put(name, j);
|
||||
return j;
|
||||
}
|
||||
}
|
||||
126
src/main/java/com/tiedup/remake/rig/TiedUpRigConstants.java
Normal file
126
src/main/java/com/tiedup/remake/rig/TiedUpRigConstants.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.anim.ServerAnimator;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Remplace {@code yesman.epicfight.main.EpicFightMod} + {@code EpicFightSharedConstants}
|
||||
* du fork upstream. Expose les singletons nécessaires au runtime RIG :
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #MODID} — ID du mod TiedUp (tiedup)</li>
|
||||
* <li>{@link #LOGGER} — logger commun RIG</li>
|
||||
* <li>{@link #identifier(String)} — helper ResourceLocation</li>
|
||||
* <li>{@link #ANIMATOR_PROVIDER} — factory client/server split pour instancier l'Animator</li>
|
||||
* <li>{@link #isPhysicalClient()} — détection side runtime</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Pattern lazy method-ref : {@code ClientAnimator::getAnimator} n'est chargé
|
||||
* que si {@link #isPhysicalClient()} est true. Sur serveur dedié, la classe
|
||||
* client n'est jamais référencée, donc jamais chargée → pas de
|
||||
* {@code NoClassDefFoundError}.</p>
|
||||
*/
|
||||
public final class TiedUpRigConstants {
|
||||
|
||||
public static final String MODID = TiedUpMod.MOD_ID;
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
/** Détection dev env (Gradle runClient) — utilisé pour les logs debug EF. */
|
||||
public static final boolean IS_DEV_ENV = !FMLEnvironment.production;
|
||||
|
||||
/** Durée d'un tick MC en secondes (20 TPS). */
|
||||
public static final float A_TICK = 1.0F / 20.0F;
|
||||
|
||||
/** Durée de transition inter-animation par défaut (en secondes — 0.15s = 3 ticks). */
|
||||
public static final float GENERAL_ANIMATION_TRANSITION_TIME = 0.15F;
|
||||
|
||||
/** Nombre max de joints supportés par une armature (limite matrice pool). */
|
||||
public static final int MAX_JOINTS = 128;
|
||||
|
||||
/**
|
||||
* Factory lazy : crée un Animator approprié au side runtime courant.
|
||||
* Client → {@link com.tiedup.remake.rig.anim.client.ClientAnimator#getAnimator}
|
||||
* Server → {@link ServerAnimator#getAnimator} (forké verbatim EF)
|
||||
*
|
||||
* <p>Pattern lazy method-ref : {@code ClientAnimator::getAnimator} n'est
|
||||
* chargé que si {@link #isPhysicalClient()} est true. Sur serveur dédié,
|
||||
* la classe client n'est jamais référencée, donc jamais chargée → pas de
|
||||
* {@code NoClassDefFoundError}.</p>
|
||||
*/
|
||||
public static final Function<LivingEntityPatch<?>, Animator> ANIMATOR_PROVIDER =
|
||||
isPhysicalClient()
|
||||
? com.tiedup.remake.rig.anim.client.ClientAnimator::getAnimator
|
||||
: ServerAnimator::getAnimator;
|
||||
|
||||
private TiedUpRigConstants() {}
|
||||
|
||||
public static ResourceLocation identifier(String path) {
|
||||
return ResourceLocation.fromNamespaceAndPath(MODID, path);
|
||||
}
|
||||
|
||||
/** Alias d'{@link #identifier(String)} — équivalent TiedUpRigConstants.prefix upstream. */
|
||||
public static ResourceLocation prefix(String path) {
|
||||
return identifier(path);
|
||||
}
|
||||
|
||||
public static boolean isPhysicalClient() {
|
||||
return FMLEnvironment.dist == Dist.CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* En dev env : log un message + throw l'exception fournie.
|
||||
* En prod : log WARN seulement. Équivalent EF {@code TiedUpRigConstants.stacktraceIfDevSide}.
|
||||
*/
|
||||
public static <E extends RuntimeException> void stacktraceIfDevSide(
|
||||
String message,
|
||||
java.util.function.Function<String, E> exceptionFactory
|
||||
) {
|
||||
if (IS_DEV_ENV) {
|
||||
throw exceptionFactory.apply(message);
|
||||
} else {
|
||||
LOGGER.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* En dev env : log via le consumer + throw l'exception.
|
||||
* En prod : log seulement. Équivalent EF {@code EpicFightMod.logAndStacktraceIfDevSide}.
|
||||
*/
|
||||
public static void logAndStacktraceIfDevSide(
|
||||
java.util.function.BiConsumer<Logger, String> logAction,
|
||||
String message,
|
||||
java.util.function.Function<String, ? extends Throwable> exceptionFactory
|
||||
) {
|
||||
logAndStacktraceIfDevSide(logAction, message, exceptionFactory, message);
|
||||
}
|
||||
|
||||
public static void logAndStacktraceIfDevSide(
|
||||
java.util.function.BiConsumer<Logger, String> logAction,
|
||||
String message,
|
||||
java.util.function.Function<String, ? extends Throwable> exceptionFactory,
|
||||
String stackTraceMessage
|
||||
) {
|
||||
logAction.accept(LOGGER, message);
|
||||
if (IS_DEV_ENV) {
|
||||
Throwable t = exceptionFactory.apply(stackTraceMessage);
|
||||
if (t instanceof RuntimeException re) throw re;
|
||||
if (t instanceof Error err) throw err;
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/main/java/com/tiedup/remake/rig/TiedUpRigRegistry.java
Normal file
50
src/main/java/com/tiedup/remake/rig/TiedUpRigRegistry.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
|
||||
/**
|
||||
* Remplace les registries {@code yesman.epicfight.gameasset.Animations} et
|
||||
* {@code yesman.epicfight.gameasset.Armatures} du fork upstream. TiedUp
|
||||
* n'utilise PAS les animations combat EF (BIPED_IDLE, BIPED_WALK, etc. —
|
||||
* ARR assets) — on authore les nôtres en Phase 4 via addon Blender.
|
||||
*
|
||||
* <p>Ce registry expose juste {@link #EMPTY_ANIMATION} — animation singleton
|
||||
* "ne fait rien", référencée par LayerOffAnimation et StaticAnimation pour
|
||||
* le défaut.</p>
|
||||
*
|
||||
* <p>Les vrais registries TiedUp (TiedUpAnimationRegistry, TiedUpArmatures,
|
||||
* TiedUpMeshRegistry) sont prévus en Phase 2-3 et gèreront le scan resource
|
||||
* pack + lookup par ResourceLocation.</p>
|
||||
*/
|
||||
public final class TiedUpRigRegistry {
|
||||
|
||||
private TiedUpRigRegistry() {}
|
||||
|
||||
/**
|
||||
* Animation singleton "ne fait rien". Utilisée par le runtime comme
|
||||
* fallback quand aucune animation n'est active sur une layer.
|
||||
*
|
||||
* <p>Équivalent de {@code TiedUpRigRegistry.EMPTY_ANIMATION} du fork upstream
|
||||
* (cf. Animations.java:27 EF).</p>
|
||||
*/
|
||||
public static final DirectStaticAnimation EMPTY_ANIMATION = new DirectStaticAnimation() {
|
||||
public static final ResourceLocation EMPTY_ANIMATION_REGISTRY_NAME =
|
||||
ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "empty");
|
||||
|
||||
@Override
|
||||
public void loadAnimation() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return EMPTY_ANIMATION_REGISTRY_NAME;
|
||||
}
|
||||
};
|
||||
}
|
||||
140
src/main/java/com/tiedup/remake/rig/anim/AnimationClip.java
Normal file
140
src/main/java/com/tiedup/remake/rig/anim/AnimationClip.java
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableInt;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class AnimationClip {
|
||||
public static final AnimationClip EMPTY_CLIP = new AnimationClip();
|
||||
|
||||
protected Map<String, TransformSheet> jointTransforms = new HashMap<> ();
|
||||
protected float clipTime;
|
||||
protected float[] bakedTimes;
|
||||
|
||||
/// To modify existing keyframes in runtime and keep the baked state, call [#setBaked] again
|
||||
/// after finishing clip modification. (Frequent calls of this method will cause a performance issue)
|
||||
public void addJointTransform(String jointName, TransformSheet sheet) {
|
||||
this.jointTransforms.put(jointName, sheet);
|
||||
this.bakedTimes = null;
|
||||
}
|
||||
|
||||
public boolean hasJointTransform(String jointName) {
|
||||
return this.jointTransforms.containsKey(jointName);
|
||||
}
|
||||
|
||||
/// Bakes all keyframes to optimize calculating current pose,
|
||||
public void bakeKeyframes() {
|
||||
Set<Float> timestamps = new HashSet<> ();
|
||||
|
||||
this.jointTransforms.values().forEach(transformSheet -> {
|
||||
transformSheet.forEach((i, keyframe) -> {
|
||||
timestamps.add(keyframe.time());
|
||||
});
|
||||
});
|
||||
|
||||
float[] bakedTimestamps = new float[timestamps.size()];
|
||||
MutableInt mi = new MutableInt(0);
|
||||
|
||||
timestamps.stream().sorted().toList().forEach(f -> {
|
||||
bakedTimestamps[mi.getAndAdd(1)] = f;
|
||||
});
|
||||
|
||||
Map<String, TransformSheet> bakedJointTransforms = new HashMap<> ();
|
||||
|
||||
this.jointTransforms.forEach((jointName, transformSheet) -> {
|
||||
bakedJointTransforms.put(jointName, transformSheet.createInterpolated(bakedTimestamps));
|
||||
});
|
||||
|
||||
this.jointTransforms = bakedJointTransforms;
|
||||
this.bakedTimes = bakedTimestamps;
|
||||
}
|
||||
|
||||
/// Bake keyframes supposing all keyframes are aligned (mainly used when creating link animations)
|
||||
public void setBaked() {
|
||||
TransformSheet transformSheet = this.jointTransforms.get("Root");
|
||||
|
||||
if (transformSheet != null) {
|
||||
this.bakedTimes = new float[transformSheet.getKeyframes().length];
|
||||
|
||||
for (int i = 0; i < transformSheet.getKeyframes().length; i++) {
|
||||
this.bakedTimes[i] = transformSheet.getKeyframes()[i].time();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TransformSheet getJointTransform(String jointName) {
|
||||
return this.jointTransforms.get(jointName);
|
||||
}
|
||||
|
||||
public final Pose getPoseInTime(float time) {
|
||||
Pose pose = new Pose();
|
||||
|
||||
if (time < 0.0F) {
|
||||
time = this.clipTime + time;
|
||||
}
|
||||
|
||||
if (this.bakedTimes != null && this.bakedTimes.length > 0) {
|
||||
// Binary search
|
||||
int begin = 0, end = this.bakedTimes.length - 1;
|
||||
|
||||
while (end - begin > 1) {
|
||||
int i = begin + (end - begin) / 2;
|
||||
|
||||
if (this.bakedTimes[i] <= time && this.bakedTimes[i+1] > time) {
|
||||
begin = i;
|
||||
end = i+1;
|
||||
break;
|
||||
} else {
|
||||
if (this.bakedTimes[i] > time) {
|
||||
end = i;
|
||||
} else if (this.bakedTimes[i+1] <= time) {
|
||||
begin = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float delta = Mth.clamp((time - this.bakedTimes[begin]) / (this.bakedTimes[end] - this.bakedTimes[begin]), 0.0F, 1.0F);
|
||||
TransformSheet.InterpolationInfo iInfo = new TransformSheet.InterpolationInfo(begin, end, delta);
|
||||
|
||||
for (String jointName : this.jointTransforms.keySet()) {
|
||||
pose.putJointData(jointName, this.jointTransforms.get(jointName).getInterpolatedTransform(iInfo));
|
||||
}
|
||||
} else {
|
||||
for (String jointName : this.jointTransforms.keySet()) {
|
||||
pose.putJointData(jointName, this.jointTransforms.get(jointName).getInterpolatedTransform(time));
|
||||
}
|
||||
}
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
/// @return returns protected keyframes of each joint to keep the baked state of keyframes.
|
||||
public Map<String, TransformSheet> getJointTransforms() {
|
||||
return Collections.unmodifiableMap(this.jointTransforms);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.jointTransforms.clear();
|
||||
this.bakedTimes = null;
|
||||
}
|
||||
|
||||
public void setClipTime(float clipTime) {
|
||||
this.clipTime = clipTime;
|
||||
}
|
||||
|
||||
public float getClipTime() {
|
||||
return this.clipTime;
|
||||
}
|
||||
}
|
||||
436
src/main/java/com/tiedup/remake/rig/anim/AnimationManager.java
Normal file
436
src/main/java/com/tiedup/remake/rig/anim/AnimationManager.java
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty;
|
||||
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.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader;
|
||||
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
||||
import com.tiedup.remake.rig.util.InstantiateInvoker;
|
||||
import com.tiedup.remake.rig.util.MutableBoolean;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class AnimationManager extends SimplePreparableReloadListener<List<ResourceLocation>> {
|
||||
private static final AnimationManager INSTANCE = new AnimationManager();
|
||||
private static ResourceManager serverResourceManager = null;
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static final String DIRECTORY = "animmodels/animations";
|
||||
|
||||
public static AnimationManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final Map<Integer, AnimationAccessor<? extends StaticAnimation>> animationById = Maps.newHashMap();
|
||||
private final Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> animationByName = Maps.newHashMap();
|
||||
private final Map<AnimationAccessor<? extends StaticAnimation>, StaticAnimation> animations = Maps.newHashMap();
|
||||
private final Map<AnimationAccessor<? extends StaticAnimation>, String> resourcepackAnimationCommands = Maps.newHashMap();
|
||||
|
||||
public static boolean checkNull(AssetAccessor<? extends StaticAnimation> animation) {
|
||||
if (animation == null || animation.isEmpty()) {
|
||||
if (animation != null) {
|
||||
TiedUpRigConstants.stacktraceIfDevSide("Empty animation accessor: " + animation.registryName(), NoSuchElementException::new);
|
||||
} else {
|
||||
TiedUpRigConstants.stacktraceIfDevSide("Null animation accessor", NoSuchElementException::new);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends StaticAnimation> AnimationAccessor<T> byKey(String registryName) {
|
||||
return byKey(ResourceLocation.parse(registryName));
|
||||
}
|
||||
|
||||
public static <T extends StaticAnimation> AnimationAccessor<T> byKey(ResourceLocation registryName) {
|
||||
return (AnimationAccessor<T>)getInstance().animationByName.get(registryName);
|
||||
}
|
||||
|
||||
public static <T extends StaticAnimation> AnimationAccessor<T> byId(int animationId) {
|
||||
return (AnimationAccessor<T>)getInstance().animationById.get(animationId);
|
||||
}
|
||||
|
||||
public Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> getAnimations(Predicate<AssetAccessor<? extends StaticAnimation>> filter) {
|
||||
Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> filteredItems =
|
||||
this.animationByName.entrySet().stream()
|
||||
.filter(entry -> {
|
||||
return filter.test(entry.getValue());
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
return ImmutableMap.copyOf(filteredItems);
|
||||
}
|
||||
|
||||
public AnimationClip loadAnimationClip(StaticAnimation animation, BiFunction<JsonAssetLoader, StaticAnimation, AnimationClip> clipLoader) {
|
||||
try {
|
||||
if (getAnimationResourceManager() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonAssetLoader modelLoader = new JsonAssetLoader(getAnimationResourceManager(), animation.getLocation());
|
||||
AnimationClip loadedClip = clipLoader.apply(modelLoader, animation);
|
||||
|
||||
return loadedClip;
|
||||
} catch (AssetLoadingException e) {
|
||||
throw new AssetLoadingException("Failed to load animation clip from: " + animation, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void readAnimationProperties(StaticAnimation animation) {
|
||||
ResourceLocation dataLocation = getSubAnimationFileLocation(animation.getLocation(), AnimationSubFileReader.SUBFILE_CLIENT_PROPERTY);
|
||||
ResourceLocation povLocation = getSubAnimationFileLocation(animation.getLocation(), AnimationSubFileReader.SUBFILE_POV_ANIMATION);
|
||||
|
||||
getAnimationResourceManager().getResource(dataLocation).ifPresent((rs) -> {
|
||||
AnimationSubFileReader.readAndApply(animation, rs, AnimationSubFileReader.SUBFILE_CLIENT_PROPERTY);
|
||||
});
|
||||
|
||||
getAnimationResourceManager().getResource(povLocation).ifPresent((rs) -> {
|
||||
AnimationSubFileReader.readAndApply(animation, rs, AnimationSubFileReader.SUBFILE_POV_ANIMATION);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ResourceLocation> prepare(ResourceManager resourceManager, ProfilerFiller profilerIn) {
|
||||
if (!TiedUpRigConstants.isPhysicalClient() && serverResourceManager == null) {
|
||||
serverResourceManager = resourceManager;
|
||||
}
|
||||
|
||||
this.animations.clear();
|
||||
this.animationById.entrySet().removeIf(entry -> !entry.getValue().inRegistry());
|
||||
this.animationByName.entrySet().removeIf(entry -> !entry.getValue().inRegistry());
|
||||
this.resourcepackAnimationCommands.clear();
|
||||
|
||||
List<ResourceLocation> directories = new ArrayList<> ();
|
||||
scanDirectoryNames(resourceManager, directories);
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
private static void scanDirectoryNames(ResourceManager resourceManager, List<ResourceLocation> output) {
|
||||
FileToIdConverter filetoidconverter = FileToIdConverter.json(DIRECTORY);
|
||||
filetoidconverter.listMatchingResources(resourceManager).keySet().stream().map(AnimationManager::pathToId).forEach(output::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void apply(List<ResourceLocation> objects, ResourceManager resourceManager, ProfilerFiller profilerIn) {
|
||||
// RIG : Armatures.reload() (EF gameasset registry) retiré.
|
||||
// TiedUpArmatures.reload() sera appelé ici en Phase 2 quand le registry
|
||||
// sera créé. En Phase 0, no-op.
|
||||
|
||||
Set<ResourceLocation> registeredAnimation =
|
||||
this.animationById.values().stream()
|
||||
.reduce(
|
||||
new HashSet<> (),
|
||||
(set, accessor) -> {
|
||||
set.add(accessor.registryName());
|
||||
|
||||
for (AssetAccessor<? extends StaticAnimation> subAnimAccessor : accessor.get().getSubAnimations()) {
|
||||
set.add(subAnimAccessor.registryName());
|
||||
}
|
||||
|
||||
return set;
|
||||
},
|
||||
(set1, set2) -> {
|
||||
set1.addAll(set2);
|
||||
return set1;
|
||||
}
|
||||
);
|
||||
|
||||
// Load animations that are not registered by AnimationRegistryEvent
|
||||
// Reads from /assets folder in physical client, /datapack in physical server.
|
||||
objects.stream()
|
||||
.filter(animId -> !registeredAnimation.contains(animId) && !animId.getPath().contains("/data/") && !animId.getPath().contains("/pov/"))
|
||||
.sorted(Comparator.comparing(ResourceLocation::toString))
|
||||
.forEach(animId -> {
|
||||
Optional<Resource> resource = resourceManager.getResource(idToPath(animId));
|
||||
|
||||
try (Reader reader = resource.orElseThrow().openAsReader()) {
|
||||
JsonElement jsonelement = GsonHelper.fromJson(GSON, reader, JsonElement.class);
|
||||
this.readResourcepackAnimation(animId, jsonelement.getAsJsonObject());
|
||||
} catch (IOException | JsonParseException | IllegalArgumentException resourceReadException) {
|
||||
TiedUpRigConstants.LOGGER.error("Couldn't parse animation data from {}", animId, resourceReadException);
|
||||
} catch (Exception e) {
|
||||
TiedUpRigConstants.LOGGER.error("Failed at constructing {}", animId, e);
|
||||
}
|
||||
});
|
||||
|
||||
// RIG : upstream EF appelait ici yesman.epicfight.skill.SkillManager.reloadAllSkillsAnimations()
|
||||
// (re-link des animations aux Skills Java). Combat system hors scope TiedUp → appel strippé.
|
||||
|
||||
this.animations.entrySet().stream()
|
||||
.reduce(
|
||||
new ArrayList<AssetAccessor<? extends StaticAnimation>>(),
|
||||
(list, entry) -> {
|
||||
MutableBoolean init = new MutableBoolean(true);
|
||||
|
||||
if (entry.getValue() == null || entry.getValue().getAccessor() == null) {
|
||||
TiedUpRigConstants.logAndStacktraceIfDevSide(Logger::error, "Invalid animation implementation: " + entry.getKey(), AssetLoadingException::new);
|
||||
init.set(false);
|
||||
}
|
||||
|
||||
entry.getValue().getSubAnimations().forEach((subAnimation) -> {
|
||||
if (subAnimation == null || subAnimation.get() == null) {
|
||||
TiedUpRigConstants.logAndStacktraceIfDevSide(Logger::error, "Invalid sub animation implementation: " + entry.getKey(), AssetLoadingException::new);
|
||||
init.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (init.value()) {
|
||||
list.add(entry.getValue().getAccessor());
|
||||
list.addAll(entry.getValue().getSubAnimations());
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
(list1, list2) -> {
|
||||
list1.addAll(list2);
|
||||
return list1;
|
||||
}
|
||||
)
|
||||
.forEach(accessor -> {
|
||||
accessor.doOrThrow(StaticAnimation::postInit);
|
||||
|
||||
if (TiedUpRigConstants.isPhysicalClient()) {
|
||||
AnimationManager.readAnimationProperties(accessor.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static ResourceLocation getSubAnimationFileLocation(ResourceLocation location, AnimationSubFileReader.SubFileType<?> subFileType) {
|
||||
int splitIdx = location.getPath().lastIndexOf('/');
|
||||
|
||||
if (splitIdx < 0) {
|
||||
splitIdx = 0;
|
||||
}
|
||||
|
||||
return ResourceLocation.fromNamespaceAndPath(location.getNamespace(), String.format("%s/" + subFileType.getDirectory() + "%s", location.getPath().substring(0, splitIdx), location.getPath().substring(splitIdx)));
|
||||
}
|
||||
|
||||
/// Converts animation id, acquired by [StaticAnimation#getRegistryName], to animation resource path acquired by [StaticAnimation#getLocation]
|
||||
public static ResourceLocation idToPath(ResourceLocation rl) {
|
||||
return rl.getPath().matches(DIRECTORY + "/.*\\.json") ? rl : ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), DIRECTORY + "/" + rl.getPath() + ".json");
|
||||
}
|
||||
|
||||
/// Converts animation resource path, acquired by [StaticAnimation#getLocation], to animation id acquired by [StaticAnimation#getRegistryName]
|
||||
public static ResourceLocation pathToId(ResourceLocation rl) {
|
||||
return ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), rl.getPath().replace(DIRECTORY + "/", "").replace(".json", ""));
|
||||
}
|
||||
|
||||
public static void setServerResourceManager(ResourceManager pResourceManager) {
|
||||
serverResourceManager = pResourceManager;
|
||||
}
|
||||
|
||||
public static ResourceManager getAnimationResourceManager() {
|
||||
return TiedUpRigConstants.isPhysicalClient() ? Minecraft.getInstance().getResourceManager() : serverResourceManager;
|
||||
}
|
||||
|
||||
public int getResourcepackAnimationCount() {
|
||||
return this.resourcepackAnimationCommands.size();
|
||||
}
|
||||
|
||||
public Stream<CompoundTag> getResourcepackAnimationStream() {
|
||||
return this.resourcepackAnimationCommands.entrySet().stream().map((entry) -> {
|
||||
CompoundTag compTag = new CompoundTag();
|
||||
compTag.putString("registry_name", entry.getKey().registryName().toString());
|
||||
compTag.putInt("id", entry.getKey().id());
|
||||
compTag.putString("invoke_command", entry.getValue());
|
||||
|
||||
return compTag;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mandatoryPack : creates dummy animations for animations from the server without animation clips when the server has mandatory resource pack.
|
||||
* custom weapon types & mob capabilities won't be created because they won't be able to find the animations from the server
|
||||
* dummy animations will be automatically removed right after reloading resourced as the server forces using resource pack
|
||||
*/
|
||||
// RIG : processServerPacket + validateClientAnimationRegistry strippés.
|
||||
// C'était le protocole datapack-sync EF pour valider que le client a les
|
||||
// mêmes animations que le serveur au login (important pour les animations
|
||||
// combat stockées en data/). TiedUp utilise resource pack uniquement
|
||||
// (assets/) côté client, pas de sync datapack nécessaire.
|
||||
// Ré-introduire Phase 2+ si on veut un warning quand un pack d'animations
|
||||
// custom diverge.
|
||||
|
||||
private static final Set<String> NO_WARNING_MODID = Sets.newHashSet();
|
||||
|
||||
public static void addNoWarningModId(String modid) {
|
||||
NO_WARNING_MODID.add(modid);
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* User-animation loader
|
||||
**************************************************/
|
||||
@SuppressWarnings({ "deprecation" })
|
||||
private void readResourcepackAnimation(ResourceLocation rl, JsonObject json) throws Exception {
|
||||
JsonElement constructorElement = json.get("constructor");
|
||||
|
||||
if (constructorElement == null) {
|
||||
if (NO_WARNING_MODID.contains(rl.getNamespace())) {
|
||||
return;
|
||||
} else {
|
||||
TiedUpRigConstants.logAndStacktraceIfDevSide(
|
||||
Logger::error
|
||||
, "Datapack animation reading failed: No constructor information has provided: " + rl
|
||||
, IllegalStateException::new
|
||||
, "No constructor information has provided in User animation, " + rl + "\nPlease remove this resource if it's unnecessary to optimize your project."
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject constructorObject = constructorElement.getAsJsonObject();
|
||||
String invocationCommand = constructorObject.get("invocation_command").getAsString();
|
||||
StaticAnimation animation = InstantiateInvoker.invoke(invocationCommand, StaticAnimation.class).getResult();
|
||||
JsonElement propertiesElement = json.getAsJsonObject().get("properties");
|
||||
|
||||
if (propertiesElement != null) {
|
||||
JsonObject propertiesObject = propertiesElement.getAsJsonObject();
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : propertiesObject.entrySet()) {
|
||||
AnimationProperty<?> propertyKey = AnimationProperty.getSerializableProperty(entry.getKey());
|
||||
Object value = propertyKey.parseFrom(entry.getValue());
|
||||
animation.addPropertyUnsafe(propertyKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationAccessor<StaticAnimation> accessor = AnimationAccessorImpl.create(rl, this.animations.size() + 1, false, null);
|
||||
animation.setAccessor(accessor);
|
||||
|
||||
this.resourcepackAnimationCommands.put(accessor, invocationCommand);
|
||||
this.animationById.put(accessor.id(), accessor);
|
||||
this.animationByName.put(accessor.registryName(), accessor);
|
||||
this.animations.put(accessor, animation);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface AnimationAccessor<A extends DynamicAnimation> extends AssetAccessor<A> {
|
||||
int id();
|
||||
|
||||
default boolean idBetween(AnimationAccessor<? extends StaticAnimation> a1, AnimationAccessor<? extends StaticAnimation> a2) {
|
||||
return a1.id() <= this.id() && a2.id() >= this.id();
|
||||
}
|
||||
}
|
||||
|
||||
public static record AnimationAccessorImpl<A extends StaticAnimation> (ResourceLocation registryName, int id, boolean inRegistry, Function<AnimationAccessor<A>, A> onLoad) implements AnimationAccessor<A> {
|
||||
private static <A extends StaticAnimation> AnimationAccessor<A> create(ResourceLocation registryName, int id, boolean inRegistry, Function<AnimationAccessor<A>, A> onLoad) {
|
||||
return new AnimationAccessorImpl<A> (registryName, id, inRegistry, onLoad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public A get() {
|
||||
if (!INSTANCE.animations.containsKey(this)) {
|
||||
INSTANCE.animations.put(this, this.onLoad.apply(this));
|
||||
}
|
||||
|
||||
return (A)INSTANCE.animations.get(this);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.registryName.toString();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.registryName.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
} else if (obj instanceof AnimationAccessor armatureAccessor) {
|
||||
return this.registryName.equals(armatureAccessor.registryName());
|
||||
} else if (obj instanceof ResourceLocation rl) {
|
||||
return this.registryName.equals(rl);
|
||||
} else if (obj instanceof String name) {
|
||||
return this.registryName.toString().equals(name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AnimationRegistryEvent extends Event implements IModBusEvent {
|
||||
private List<AnimationBuilder> builders = Lists.newArrayList();
|
||||
private Set<String> namespaces = Sets.newHashSet();
|
||||
|
||||
public void newBuilder(String namespace, Consumer<AnimationBuilder> build) {
|
||||
if (this.namespaces.contains(namespace)) {
|
||||
throw new IllegalArgumentException("Animation builder namespace '" + namespace + "' already exists!");
|
||||
}
|
||||
|
||||
this.namespaces.add(namespace);
|
||||
this.builders.add(new AnimationBuilder(namespace, build));
|
||||
}
|
||||
|
||||
public List<AnimationBuilder> getBuilders() {
|
||||
return this.builders;
|
||||
}
|
||||
}
|
||||
|
||||
public static record AnimationBuilder(String namespace, Consumer<AnimationBuilder> task) {
|
||||
public <T extends StaticAnimation> AnimationManager.AnimationAccessor<T> nextAccessor(String id, Function<AnimationManager.AnimationAccessor<T>, T> onLoad) {
|
||||
AnimationAccessor<T> accessor = AnimationAccessorImpl.create(ResourceLocation.fromNamespaceAndPath(this.namespace, id), INSTANCE.animations.size() + 1, true, onLoad);
|
||||
|
||||
INSTANCE.animationById.put(accessor.id(), accessor);
|
||||
INSTANCE.animationByName.put(accessor.registryName(), accessor);
|
||||
INSTANCE.animations.put(accessor, null);
|
||||
|
||||
return accessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
162
src/main/java/com/tiedup/remake/rig/anim/AnimationPlayer.java
Normal file
162
src/main/java/com/tiedup/remake/rig/anim/AnimationPlayer.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackSpeedModifier;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackTimeModifier;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
|
||||
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.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class AnimationPlayer {
|
||||
protected float elapsedTime;
|
||||
protected float prevElapsedTime;
|
||||
protected boolean isEnd;
|
||||
protected boolean doNotResetTime;
|
||||
protected boolean reversed;
|
||||
protected AssetAccessor<? extends DynamicAnimation> play;
|
||||
|
||||
public AnimationPlayer() {
|
||||
this.setPlayAnimation(TiedUpRigRegistry.EMPTY_ANIMATION);
|
||||
}
|
||||
|
||||
public void tick(LivingEntityPatch<?> entitypatch) {
|
||||
DynamicAnimation currentPlay = this.getAnimation().get();
|
||||
DynamicAnimation currentPlayStatic = currentPlay.getRealAnimation().get();
|
||||
this.prevElapsedTime = this.elapsedTime;
|
||||
|
||||
float playbackSpeed = currentPlay.getPlaySpeed(entitypatch, currentPlay);
|
||||
PlaybackSpeedModifier playSpeedModifier = currentPlayStatic.getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).orElse(null);
|
||||
|
||||
if (playSpeedModifier != null) {
|
||||
playbackSpeed = playSpeedModifier.modify(currentPlay, entitypatch, playbackSpeed, this.prevElapsedTime, this.elapsedTime);
|
||||
}
|
||||
|
||||
this.elapsedTime += TiedUpRigConstants.A_TICK * playbackSpeed * (this.isReversed() && currentPlay.canBePlayedReverse() ? -1.0F : 1.0F);
|
||||
PlaybackTimeModifier playTimeModifier = currentPlayStatic.getProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER).orElse(null);
|
||||
|
||||
if (playTimeModifier != null) {
|
||||
Pair<Float, Float> time = playTimeModifier.modify(currentPlay, entitypatch, playbackSpeed, this.prevElapsedTime, this.elapsedTime);
|
||||
this.prevElapsedTime = time.getFirst();
|
||||
this.elapsedTime = time.getSecond();
|
||||
}
|
||||
|
||||
if (this.elapsedTime > currentPlay.getTotalTime()) {
|
||||
if (currentPlay.isRepeat()) {
|
||||
this.prevElapsedTime = this.prevElapsedTime - currentPlay.getTotalTime();
|
||||
this.elapsedTime %= currentPlay.getTotalTime();
|
||||
} else {
|
||||
this.elapsedTime = currentPlay.getTotalTime();
|
||||
currentPlay.end(entitypatch, null, true);
|
||||
this.isEnd = true;
|
||||
}
|
||||
} else if (this.elapsedTime < 0) {
|
||||
if (currentPlay.isRepeat()) {
|
||||
this.prevElapsedTime = currentPlay.getTotalTime() - this.elapsedTime;
|
||||
this.elapsedTime = currentPlay.getTotalTime() + this.elapsedTime;
|
||||
} else {
|
||||
this.elapsedTime = 0.0F;
|
||||
currentPlay.end(entitypatch, null, true);
|
||||
this.isEnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.elapsedTime = 0;
|
||||
this.prevElapsedTime = 0;
|
||||
this.isEnd = false;
|
||||
}
|
||||
|
||||
public void setPlayAnimation(AssetAccessor<? extends DynamicAnimation> animation) {
|
||||
if (this.doNotResetTime) {
|
||||
this.doNotResetTime = false;
|
||||
this.isEnd = false;
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.play = animation;
|
||||
}
|
||||
|
||||
public Pose getCurrentPose(LivingEntityPatch<?> entitypatch, float partialTicks) {
|
||||
return this.play.get().getPoseByTime(entitypatch, this.prevElapsedTime + (this.elapsedTime - this.prevElapsedTime) * partialTicks, partialTicks);
|
||||
}
|
||||
|
||||
public float getElapsedTime() {
|
||||
return this.elapsedTime;
|
||||
}
|
||||
|
||||
public float getPrevElapsedTime() {
|
||||
return this.prevElapsedTime;
|
||||
}
|
||||
|
||||
public void setElapsedTimeCurrent(float elapsedTime) {
|
||||
this.elapsedTime = elapsedTime;
|
||||
this.isEnd = false;
|
||||
}
|
||||
|
||||
public void setElapsedTime(float elapsedTime) {
|
||||
this.elapsedTime = elapsedTime;
|
||||
this.prevElapsedTime = elapsedTime;
|
||||
this.isEnd = false;
|
||||
}
|
||||
|
||||
public void setElapsedTime(float prevElapsedTime, float elapsedTime) {
|
||||
this.elapsedTime = elapsedTime;
|
||||
this.prevElapsedTime = prevElapsedTime;
|
||||
this.isEnd = false;
|
||||
}
|
||||
|
||||
public void begin(AssetAccessor<? extends DynamicAnimation> animation, LivingEntityPatch<?> entitypatch) {
|
||||
animation.get().tick(entitypatch);
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends DynamicAnimation> getAnimation() {
|
||||
return this.play;
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
|
||||
return this.play.get().getRealAnimation();
|
||||
}
|
||||
|
||||
public void markDoNotResetTime() {
|
||||
this.doNotResetTime = true;
|
||||
}
|
||||
|
||||
public boolean isEnd() {
|
||||
return this.isEnd;
|
||||
}
|
||||
|
||||
public void terminate(LivingEntityPatch<?> entitypatch) {
|
||||
this.play.get().end(entitypatch, this.play, true);
|
||||
this.isEnd = true;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return this.reversed;
|
||||
}
|
||||
|
||||
public void setReversed(boolean reversed) {
|
||||
this.reversed = reversed;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.play == TiedUpRigRegistry.EMPTY_ANIMATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAnimation() + " " + this.prevElapsedTime + " " + this.elapsedTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
/**
|
||||
* RIG stub. Upstream EF : packet commun client/serveur pour sync des
|
||||
* animation variables (variables partagées entre deux sides pendant une
|
||||
* animation combat).
|
||||
*
|
||||
* <p>TiedUp Phase 0 : la classe est conservée en stub juste pour l'enum
|
||||
* {@link Action} utilisé par {@code AnimationVariables.put/remove} et
|
||||
* {@code SynchedAnimationVariableKey.sync}. Le vrai packet réseau n'est
|
||||
* pas implémenté — les `sync()` calls sont no-ops côté runtime pour
|
||||
* l'instant (cf. {@code SynchedAnimationVariableKey.sync}).</p>
|
||||
*
|
||||
* <p>Phase 2+ : si on a besoin de sync d'animation variables entre
|
||||
* serveur et client (cas d'usage non identifié en TiedUp), implémenter
|
||||
* un vrai packet. Sinon garder le stub et stripper {@code sync()} plus
|
||||
* tard.</p>
|
||||
*/
|
||||
public class AnimationVariablePacket {
|
||||
|
||||
public enum Action {
|
||||
PUT,
|
||||
REMOVE,
|
||||
}
|
||||
}
|
||||
267
src/main/java/com/tiedup/remake/rig/anim/AnimationVariables.java
Normal file
267
src/main/java/com/tiedup/remake/rig/anim/AnimationVariables.java
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap.TypeKey;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariablePacket;
|
||||
|
||||
public class AnimationVariables {
|
||||
protected final Animator animator;
|
||||
protected final TypeFlexibleHashMap<AnimationVariableKey<?>> animationVariables = new TypeFlexibleHashMap<> (false);
|
||||
|
||||
public AnimationVariables(Animator animator) {
|
||||
this.animator = animator;
|
||||
}
|
||||
|
||||
public <T> Optional<T> getSharedVariable(SharedAnimationVariableKey<T> key) {
|
||||
return Optional.ofNullable(this.animationVariables.get(key));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrDefaultSharedVariable(SharedAnimationVariableKey<T> key) {
|
||||
return ParseUtil.orElse((T)this.animationVariables.get(key), () -> key.defaultValue(this.animator));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> get(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
if (animation == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Map<ResourceLocation, Object> subMap = this.animationVariables.get(key);
|
||||
|
||||
if (subMap == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.ofNullable((T)subMap.get(animation.registryName()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrDefault(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
if (animation == null) {
|
||||
return Objects.requireNonNull(key.defaultValue(this.animator), "Null value returned by default provider.");
|
||||
}
|
||||
|
||||
Map<ResourceLocation, Object> subMap = this.animationVariables.get(key);
|
||||
|
||||
if (subMap == null) {
|
||||
return Objects.requireNonNull(key.defaultValue(this.animator), "Null value returned by default provider.");
|
||||
} else {
|
||||
return ParseUtil.orElse((T)subMap.get(animation.registryName()), () -> key.defaultValue(this.animator));
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void putDefaultSharedVariable(SharedAnimationVariableKey<T> key) {
|
||||
T value = key.defaultValue(this.animator);
|
||||
Objects.requireNonNull(value, "Null value returned by default provider.");
|
||||
|
||||
this.putSharedVariable(key, value);
|
||||
}
|
||||
|
||||
public <T> void putSharedVariable(SharedAnimationVariableKey<T> key, T value) {
|
||||
this.putSharedVariable(key, value, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated // Avoid direct use
|
||||
public <T> void putSharedVariable(SharedAnimationVariableKey<T> key, T value, boolean synchronize) {
|
||||
if (this.animationVariables.containsKey(key) && !key.mutable()) {
|
||||
throw new UnsupportedOperationException("Can't modify a const variable");
|
||||
}
|
||||
|
||||
this.animationVariables.put((AnimationVariableKey<?>)key, value);
|
||||
|
||||
if (synchronize && key instanceof SynchedAnimationVariableKey) {
|
||||
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
|
||||
synchedanimationvariablekey.sync(this.animator.entitypatch, (AssetAccessor<? extends StaticAnimation>)null, value, AnimationVariablePacket.Action.PUT);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void putDefaultValue(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
T value = key.defaultValue(this.animator);
|
||||
Objects.requireNonNull(value, "Null value returned by default provider.");
|
||||
|
||||
this.put(key, animation, value);
|
||||
}
|
||||
|
||||
public <T> void put(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation, T value) {
|
||||
this.put(key, animation, value, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated // Avoid direct use
|
||||
public <T> void put(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation, T value, boolean synchronize) {
|
||||
if (animation == TiedUpRigRegistry.EMPTY_ANIMATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.animationVariables.computeIfPresent(key, (k, v) -> {
|
||||
Map<ResourceLocation, Object> variablesByAnimations = ((Map<ResourceLocation, Object>)v);
|
||||
|
||||
if (!key.mutable() && variablesByAnimations.containsKey(animation.registryName())) {
|
||||
throw new UnsupportedOperationException("Can't modify a const variable");
|
||||
}
|
||||
|
||||
variablesByAnimations.put(animation.registryName(), value);
|
||||
|
||||
return v;
|
||||
});
|
||||
|
||||
this.animationVariables.computeIfAbsent(key, (k) -> {
|
||||
return new HashMap<> (Map.of(animation.registryName(), value));
|
||||
});
|
||||
|
||||
if (synchronize && key instanceof SynchedAnimationVariableKey) {
|
||||
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
|
||||
synchedanimationvariablekey.sync(this.animator.entitypatch, animation, value, AnimationVariablePacket.Action.PUT);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T removeSharedVariable(SharedAnimationVariableKey<T> key) {
|
||||
return this.removeSharedVariable(key, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated // Avoid direct use
|
||||
public <T> T removeSharedVariable(SharedAnimationVariableKey<T> key, boolean synchronize) {
|
||||
if (!key.mutable()) {
|
||||
throw new UnsupportedOperationException("Can't remove a const variable");
|
||||
}
|
||||
|
||||
if (synchronize && key instanceof SynchedAnimationVariableKey) {
|
||||
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
|
||||
synchedanimationvariablekey.sync(this.animator.entitypatch, null, null, AnimationVariablePacket.Action.REMOVE);
|
||||
}
|
||||
|
||||
return (T)this.animationVariables.remove(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void removeAll(AnimationAccessor<? extends StaticAnimation> animation) {
|
||||
if (animation == TiedUpRigRegistry.EMPTY_ANIMATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<AnimationVariableKey<?>, Object> entry : this.animationVariables.entrySet()) {
|
||||
if (entry.getKey().isSharedKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<ResourceLocation, Object> map = (Map<ResourceLocation, Object>)entry.getValue();
|
||||
|
||||
if (map != null) {
|
||||
map.remove(animation.registryName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(IndependentAnimationVariableKey<?> key, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
this.remove(key, animation, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated // Avoid direct use
|
||||
public void remove(IndependentAnimationVariableKey<?> key, AssetAccessor<? extends StaticAnimation> animation, boolean synchronize) {
|
||||
if (animation == TiedUpRigRegistry.EMPTY_ANIMATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<ResourceLocation, Object> map = (Map<ResourceLocation, Object>)this.animationVariables.get(key);
|
||||
|
||||
if (map != null) {
|
||||
map.remove(animation.registryName());
|
||||
}
|
||||
|
||||
if (synchronize && key instanceof SynchedAnimationVariableKey) {
|
||||
SynchedAnimationVariableKey<?> synchedanimationvariablekey = (SynchedAnimationVariableKey<?>)key;
|
||||
synchedanimationvariablekey.sync(this.animator.entitypatch, null, null, AnimationVariablePacket.Action.REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> SharedAnimationVariableKey<T> shared(Function<Animator, T> defaultValueSupplier, boolean mutable) {
|
||||
return new SharedAnimationVariableKey<> (defaultValueSupplier, mutable);
|
||||
}
|
||||
|
||||
public static <T> IndependentAnimationVariableKey<T> independent(Function<Animator, T> defaultValueSupplier, boolean mutable) {
|
||||
return new IndependentAnimationVariableKey<> (defaultValueSupplier, mutable);
|
||||
}
|
||||
|
||||
protected abstract static class AnimationVariableKey<T> implements TypeKey<T> {
|
||||
protected final Function<Animator, T> defaultValueSupplier;
|
||||
protected final boolean mutable;
|
||||
|
||||
protected AnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable) {
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
this.mutable = mutable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public T defaultValue(Animator animator) {
|
||||
return this.defaultValueSupplier.apply(animator);
|
||||
}
|
||||
|
||||
public boolean mutable() {
|
||||
return this.mutable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T defaultValue() {
|
||||
throw new UnsupportedOperationException("Use defaultValue(Animator animator) to get default value of animation variable key");
|
||||
}
|
||||
|
||||
public abstract boolean isSharedKey();
|
||||
public abstract boolean isSynched();
|
||||
}
|
||||
|
||||
public static class SharedAnimationVariableKey<T> extends AnimationVariableKey<T> {
|
||||
protected SharedAnimationVariableKey(Function<Animator, T> initValueSupplier, boolean mutable) {
|
||||
super(initValueSupplier, mutable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharedKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynched() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IndependentAnimationVariableKey<T> extends AnimationVariableKey<T> {
|
||||
protected IndependentAnimationVariableKey(Function<Animator, T> initValueSupplier, boolean mutable) {
|
||||
super(initValueSupplier, mutable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharedKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynched() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/main/java/com/tiedup/remake/rig/anim/Animator.java
Normal file
148
src/main/java/com/tiedup/remake/rig/anim/Animator.java
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.event.InitAnimatorEvent;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public abstract class Animator {
|
||||
protected final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> livingAnimations = Maps.newHashMap();
|
||||
protected final AnimationVariables animationVariables = new AnimationVariables(this);
|
||||
protected final LivingEntityPatch<?> entitypatch;
|
||||
|
||||
public Animator(LivingEntityPatch<?> entitypatch) {
|
||||
this.entitypatch = entitypatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation
|
||||
*
|
||||
* @param nextAnimation the animation that is meant to be played.
|
||||
* @param transitionTimeModifier extends the transition time if positive value provided, or starts in time as an amount of time (e.g. -0.1F starts in 0.1F frame time)
|
||||
*/
|
||||
public abstract void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier);
|
||||
|
||||
public final void playAnimation(int id, float transitionTimeModifier) {
|
||||
this.playAnimation(AnimationManager.byId(id), transitionTimeModifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a given animation without transition animation.
|
||||
* @param nextAnimation
|
||||
*/
|
||||
public abstract void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation);
|
||||
|
||||
public final void playAnimationInstantly(int id) {
|
||||
this.playAnimationInstantly(AnimationManager.byId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserve a given animation until the current animation ends.
|
||||
* If the given animation has a higher priority than current animation, it terminates the current animation by force and play the next animation
|
||||
* @param nextAnimation
|
||||
*/
|
||||
public abstract void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation);
|
||||
|
||||
public final void reserveAnimation(int id) {
|
||||
this.reserveAnimation(AnimationManager.byId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing given animation if exist
|
||||
* @param targetAnimation
|
||||
* @return true when found and successfully stop the target animation
|
||||
*/
|
||||
public abstract boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation);
|
||||
|
||||
/**
|
||||
* Play an shooting animation to end aiming pose
|
||||
*/
|
||||
public abstract void playShootingAnimation();
|
||||
|
||||
public final boolean stopPlaying(int id) {
|
||||
return this.stopPlaying(AnimationManager.byId(id));
|
||||
}
|
||||
|
||||
public abstract void setSoftPause(boolean paused);
|
||||
public abstract void setHardPause(boolean paused);
|
||||
public abstract void tick();
|
||||
|
||||
public abstract EntityState getEntityState();
|
||||
|
||||
/**
|
||||
* Searches an animation player playing the given animation parameter or return base layer if it's null
|
||||
* Secure non-null but returned animation player won't match with a given animation
|
||||
*/
|
||||
@Nullable
|
||||
public abstract AnimationPlayer getPlayerFor(@Nullable AssetAccessor<? extends DynamicAnimation> playingAnimation);
|
||||
|
||||
/**
|
||||
* Searches an animation player playing the given animation parameter
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation);
|
||||
|
||||
public abstract <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType);
|
||||
public abstract Pose getPose(float partialTicks);
|
||||
|
||||
public void postInit() {
|
||||
InitAnimatorEvent initAnimatorEvent = new InitAnimatorEvent(this.entitypatch, this);
|
||||
MinecraftForge.EVENT_BUS.post(initAnimatorEvent);
|
||||
}
|
||||
|
||||
public void playDeathAnimation() {
|
||||
// RIG : Animations.BIPED_DEATH (EF combat asset ARR) retiré.
|
||||
// Fallback sur EMPTY_ANIMATION — TiedUp authored ses propres
|
||||
// anims de mort Phase 4 et les binde via addLivingAnimation(DEATH, ...).
|
||||
this.playAnimation(this.livingAnimations.getOrDefault(LivingMotions.DEATH, TiedUpRigRegistry.EMPTY_ANIMATION), 0);
|
||||
}
|
||||
|
||||
public void addLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
if (AnimationManager.checkNull(animation)) {
|
||||
TiedUpRigConstants.LOGGER.warn("Unable to put an empty animation for " + livingMotion);
|
||||
return;
|
||||
}
|
||||
|
||||
this.livingAnimations.put(livingMotion, animation);
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends StaticAnimation> getLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> defaultGetter) {
|
||||
return this.livingAnimations.getOrDefault(livingMotion, defaultGetter);
|
||||
}
|
||||
|
||||
public Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> getLivingAnimations() {
|
||||
return ImmutableMap.copyOf(this.livingAnimations);
|
||||
}
|
||||
|
||||
public AnimationVariables getVariables() {
|
||||
return this.animationVariables;
|
||||
}
|
||||
|
||||
public LivingEntityPatch<?> getEntityPatch() {
|
||||
return this.entitypatch;
|
||||
}
|
||||
|
||||
public void resetLivingAnimations() {
|
||||
this.livingAnimations.clear();
|
||||
}
|
||||
}
|
||||
49
src/main/java/com/tiedup/remake/rig/anim/Keyframe.java
Normal file
49
src/main/java/com/tiedup/remake/rig/anim/Keyframe.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class Keyframe {
|
||||
private float timeStamp;
|
||||
private final JointTransform transform;
|
||||
|
||||
public Keyframe(float timeStamp, JointTransform trasnform) {
|
||||
this.timeStamp = timeStamp;
|
||||
this.transform = trasnform;
|
||||
}
|
||||
|
||||
public Keyframe(Keyframe original) {
|
||||
this.transform = JointTransform.empty();
|
||||
this.copyFrom(original);
|
||||
}
|
||||
|
||||
public void copyFrom(Keyframe target) {
|
||||
this.timeStamp = target.timeStamp;
|
||||
this.transform.copyFrom(target.transform);
|
||||
}
|
||||
|
||||
public float time() {
|
||||
return this.timeStamp;
|
||||
}
|
||||
|
||||
public void setTime(float time) {
|
||||
this.timeStamp = time;
|
||||
}
|
||||
|
||||
public JointTransform transform() {
|
||||
return this.transform;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Keyframe[Time: " + this.timeStamp + ", " + (this.transform == null ? "null" : this.transform.toString()) + "]";
|
||||
}
|
||||
|
||||
public static Keyframe empty() {
|
||||
return new Keyframe(0.0F, JointTransform.empty());
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/tiedup/remake/rig/anim/LivingMotion.java
Normal file
24
src/main/java/com/tiedup/remake/rig/anim/LivingMotion.java
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import com.tiedup.remake.rig.util.ExtendableEnum;
|
||||
import com.tiedup.remake.rig.util.ExtendableEnumManager;
|
||||
|
||||
public interface LivingMotion extends ExtendableEnum {
|
||||
ExtendableEnumManager<LivingMotion> ENUM_MANAGER = new ExtendableEnumManager<> ("living_motion");
|
||||
|
||||
default boolean isSame(LivingMotion livingMotion) {
|
||||
if (this == LivingMotions.IDLE && livingMotion == LivingMotions.INACTION) {
|
||||
return true;
|
||||
} else if (this == LivingMotions.INACTION && livingMotion == LivingMotions.IDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this == livingMotion;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/tiedup/remake/rig/anim/LivingMotions.java
Normal file
23
src/main/java/com/tiedup/remake/rig/anim/LivingMotions.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
public enum LivingMotions implements LivingMotion {
|
||||
ALL, // Datapack edit option
|
||||
INACTION, IDLE, CONFRONT, ANGRY, FLOAT, WALK, RUN, SWIM, FLY, SNEAK, KNEEL, FALL, SIT, MOUNT, DEATH, CHASE, SPELLCAST, JUMP, CELEBRATE, LANDING_RECOVERY, CREATIVE_FLY, CREATIVE_IDLE, SLEEP, // Base
|
||||
DIGGING, ADMIRE, CLIMB, DRINK, EAT, NONE, AIM, BLOCK, BLOCK_SHIELD, RELOAD, SHOT, SPECTATE; // Mix
|
||||
|
||||
final int id;
|
||||
|
||||
LivingMotions() {
|
||||
this.id = LivingMotion.ENUM_MANAGER.assign(this);
|
||||
}
|
||||
|
||||
public int universalOrdinal() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
115
src/main/java/com/tiedup/remake/rig/anim/Pose.java
Normal file
115
src/main/java/com/tiedup/remake/rig/anim/Pose.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class Pose {
|
||||
public static final Pose EMPTY_POSE = new Pose();
|
||||
|
||||
public static Pose interpolatePose(Pose pose1, Pose pose2, float pregression) {
|
||||
Pose pose = new Pose();
|
||||
|
||||
Set<String> mergedSet = new HashSet<>(pose1.jointTransformData.keySet());
|
||||
mergedSet.addAll(pose2.jointTransformData.keySet());
|
||||
|
||||
for (String jointName : mergedSet) {
|
||||
pose.putJointData(jointName, JointTransform.interpolate(pose1.orElseEmpty(jointName), pose2.orElseEmpty(jointName), pregression));
|
||||
}
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
protected final Map<String, JointTransform> jointTransformData;
|
||||
|
||||
public Pose() {
|
||||
this(Maps.newHashMap());
|
||||
}
|
||||
|
||||
public Pose(Map<String, JointTransform> jointTransforms) {
|
||||
this.jointTransformData = jointTransforms;
|
||||
}
|
||||
|
||||
public void putJointData(String name, JointTransform transform) {
|
||||
this.jointTransformData.put(name, transform);
|
||||
}
|
||||
|
||||
public Map<String, JointTransform> getJointTransformData() {
|
||||
return this.jointTransformData;
|
||||
}
|
||||
|
||||
public void disableJoint(Predicate<? super Map.Entry<String, JointTransform>> predicate) {
|
||||
this.jointTransformData.entrySet().removeIf(predicate);
|
||||
}
|
||||
|
||||
public void disableAllJoints() {
|
||||
this.jointTransformData.clear();
|
||||
}
|
||||
|
||||
public boolean hasTransform(String jointName) {
|
||||
return this.jointTransformData.containsKey(jointName);
|
||||
}
|
||||
|
||||
public JointTransform get(String jointName) {
|
||||
return this.jointTransformData.get(jointName);
|
||||
}
|
||||
|
||||
public JointTransform orElseEmpty(String jointName) {
|
||||
return this.jointTransformData.getOrDefault(jointName, JointTransform.empty());
|
||||
}
|
||||
|
||||
public JointTransform orElse(String jointName, JointTransform orElse) {
|
||||
return this.jointTransformData.getOrDefault(jointName, orElse);
|
||||
}
|
||||
|
||||
public void forEachEnabledTransforms(BiConsumer<String, JointTransform> task) {
|
||||
this.jointTransformData.forEach(task);
|
||||
}
|
||||
|
||||
public void load(Pose pose, LoadOperation operation) {
|
||||
switch (operation) {
|
||||
case SET -> {
|
||||
this.disableAllJoints();
|
||||
pose.forEachEnabledTransforms(this::putJointData);
|
||||
}
|
||||
case OVERWRITE -> {
|
||||
pose.forEachEnabledTransforms(this::putJointData);
|
||||
}
|
||||
case APPEND_ABSENT -> {
|
||||
pose.forEachEnabledTransforms((name, transform) -> {
|
||||
if (!this.hasTransform(name)) {
|
||||
this.putJointData(name, transform);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Pose: ");
|
||||
|
||||
for (Map.Entry<String, JointTransform> entry : this.jointTransformData.entrySet()) {
|
||||
sb.append(String.format("%s{%s, %s}, ", entry.getKey(), entry.getValue().translation().toString(), entry.getValue().rotation().toString()) + "\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public enum LoadOperation {
|
||||
SET, OVERWRITE, APPEND_ABSENT
|
||||
}
|
||||
}
|
||||
160
src/main/java/com/tiedup/remake/rig/anim/ServerAnimator.java
Normal file
160
src/main/java/com/tiedup/remake/rig/anim/ServerAnimator.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState;
|
||||
import com.tiedup.remake.rig.anim.types.LinkAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class ServerAnimator extends Animator {
|
||||
public static Animator getAnimator(LivingEntityPatch<?> entitypatch) {
|
||||
return new ServerAnimator(entitypatch);
|
||||
}
|
||||
|
||||
private final LinkAnimation linkAnimation;
|
||||
public final AnimationPlayer animationPlayer;
|
||||
|
||||
protected AssetAccessor<? extends DynamicAnimation> nextAnimation;
|
||||
public boolean hardPaused = false;
|
||||
public boolean softPaused = false;
|
||||
|
||||
public ServerAnimator(LivingEntityPatch<?> entitypatch) {
|
||||
super(entitypatch);
|
||||
|
||||
this.linkAnimation = new LinkAnimation();
|
||||
this.animationPlayer = new AnimationPlayer();
|
||||
}
|
||||
|
||||
/** Play an animation by animation instance **/
|
||||
@Override
|
||||
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier) {
|
||||
this.softPaused = false;
|
||||
Pose lastPose = this.animationPlayer.getAnimation().get().getPoseByTime(this.entitypatch, 0.0F, 0.0F);
|
||||
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
nextAnimation.get().begin(this.entitypatch);
|
||||
|
||||
if (!nextAnimation.get().isMetaAnimation()) {
|
||||
nextAnimation.get().setLinkAnimation(this.animationPlayer.getAnimation(), lastPose, true, transitionTimeModifier, this.entitypatch, this.linkAnimation);
|
||||
this.linkAnimation.getAnimationClip().setBaked();
|
||||
this.linkAnimation.putOnPlayer(this.animationPlayer, this.entitypatch);
|
||||
this.entitypatch.updateEntityState();
|
||||
this.nextAnimation = nextAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation) {
|
||||
this.softPaused = false;
|
||||
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
nextAnimation.get().begin(this.entitypatch);
|
||||
nextAnimation.get().putOnPlayer(this.animationPlayer, this.entitypatch);
|
||||
this.entitypatch.updateEntityState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation) {
|
||||
this.softPaused = false;
|
||||
this.nextAnimation = nextAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation) {
|
||||
if (this.animationPlayer.getRealAnimation() == targetAnimation) {
|
||||
this.animationPlayer.terminate(this.entitypatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playShootingAnimation() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (this.hardPaused || this.softPaused) {
|
||||
this.entitypatch.updateEntityState();
|
||||
return;
|
||||
}
|
||||
|
||||
this.animationPlayer.tick(this.entitypatch);
|
||||
this.entitypatch.updateEntityState();
|
||||
|
||||
if (this.animationPlayer.isEnd()) {
|
||||
if (this.nextAnimation == null) {
|
||||
TiedUpRigRegistry.EMPTY_ANIMATION.putOnPlayer(this.animationPlayer, this.entitypatch);
|
||||
this.softPaused = true;
|
||||
} else {
|
||||
if (!this.animationPlayer.getAnimation().get().isLinkAnimation() && !this.nextAnimation.get().isLinkAnimation()) {
|
||||
this.nextAnimation.get().begin(this.entitypatch);
|
||||
}
|
||||
|
||||
this.nextAnimation.get().putOnPlayer(this.animationPlayer, this.entitypatch);
|
||||
this.nextAnimation = null;
|
||||
}
|
||||
} else {
|
||||
this.animationPlayer.getAnimation().get().tick(this.entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose getPose(float partialTicks) {
|
||||
return this.animationPlayer.getCurrentPose(this.entitypatch, partialTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationPlayer getPlayerFor(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
|
||||
return this.animationPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
|
||||
if (this.animationPlayer.getRealAnimation() == playingAnimation.get().getRealAnimation()) {
|
||||
return Optional.of(this.animationPlayer);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType) {
|
||||
return animationType.isAssignableFrom(this.animationPlayer.getAnimation().getClass()) ? Pair.of(this.animationPlayer, (T)this.animationPlayer.getAnimation()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityState getEntityState() {
|
||||
return this.animationPlayer.getAnimation().get().getState(this.entitypatch, this.animationPlayer.getElapsedTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSoftPause(boolean paused) {
|
||||
this.softPaused = paused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHardPause(boolean paused) {
|
||||
this.hardPaused = paused;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import net.minecraftforge.registries.IForgeRegistryInternal;
|
||||
import net.minecraftforge.registries.RegistryManager;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables.SharedAnimationVariableKey;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.util.PacketBufferCodec;
|
||||
import com.tiedup.remake.rig.util.datastruct.ClearableIdMapper;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariablePacket;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariablePacket;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariablePacket;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public interface SynchedAnimationVariableKey<T> {
|
||||
public static <T> SynchedSharedAnimationVariableKey<T> shared(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> codec) {
|
||||
return new SynchedSharedAnimationVariableKey<> (defaultValueSupplier, mutable, codec);
|
||||
}
|
||||
|
||||
public static <T> SynchedIndependentAnimationVariableKey<T> independent(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> codec) {
|
||||
return new SynchedIndependentAnimationVariableKey<> (defaultValueSupplier, mutable, codec);
|
||||
}
|
||||
|
||||
public static final ResourceLocation BY_ID_REGISTRY = TiedUpRigConstants.identifier("variablekeytoid");
|
||||
|
||||
public static class SynchedAnimationVariableKeyCallbacks implements IForgeRegistry.BakeCallback<SynchedAnimationVariableKey<?>>, IForgeRegistry.CreateCallback<SynchedAnimationVariableKey<?>>, IForgeRegistry.ClearCallback<SynchedAnimationVariableKey<?>> {
|
||||
private static final SynchedAnimationVariableKeyCallbacks INSTANCE = new SynchedAnimationVariableKeyCallbacks();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onBake(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
|
||||
final ClearableIdMapper<SynchedAnimationVariableKey<?>> synchedanimationvariablekeybyid = owner.getSlaveMap(BY_ID_REGISTRY, ClearableIdMapper.class);
|
||||
owner.forEach(synchedanimationvariablekeybyid::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
|
||||
owner.setSlaveMap(BY_ID_REGISTRY, new ClearableIdMapper<SynchedAnimationVariableKey<?>> (owner.getKeys().size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
|
||||
owner.getSlaveMap(BY_ID_REGISTRY, ClearableIdMapper.class).clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static SynchedAnimationVariableKeyCallbacks getRegistryCallback() {
|
||||
return SynchedAnimationVariableKeyCallbacks.INSTANCE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static IdMapper<SynchedAnimationVariableKey<?>> getIdMap() {
|
||||
return SynchedAnimationVariableKeys.REGISTRY.get().getSlaveMap(BY_ID_REGISTRY, IdMapper.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> SynchedAnimationVariableKey<T> byId(int id) {
|
||||
return (SynchedAnimationVariableKey<T>)getIdMap().byId(id);
|
||||
}
|
||||
|
||||
public PacketBufferCodec<T> getPacketBufferCodec();
|
||||
|
||||
public boolean isSharedKey();
|
||||
|
||||
default int getId() {
|
||||
return getIdMap().getId(this);
|
||||
}
|
||||
|
||||
default void sync(LivingEntityPatch<?> entitypatch, @Nullable AssetAccessor<? extends StaticAnimation> animation, T value, AnimationVariablePacket.Action action) {
|
||||
// RIG : sync réseau des animation variables strippé.
|
||||
// Pas d'usage bondage identifié — ré-implémenter Phase 2 avec packet
|
||||
// dédié si besoin. Voir AnimationVariablePacket stub.
|
||||
}
|
||||
|
||||
public static class SynchedSharedAnimationVariableKey<T> extends SharedAnimationVariableKey<T> implements SynchedAnimationVariableKey<T> {
|
||||
private final PacketBufferCodec<T> packetBufferCodec;
|
||||
|
||||
protected SynchedSharedAnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> packetBufferCodec) {
|
||||
super(defaultValueSupplier, mutable);
|
||||
this.packetBufferCodec = packetBufferCodec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynched() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketBufferCodec<T> getPacketBufferCodec() {
|
||||
return this.packetBufferCodec;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SynchedIndependentAnimationVariableKey<T> extends IndependentAnimationVariableKey<T> implements SynchedAnimationVariableKey<T> {
|
||||
private final PacketBufferCodec<T> packetBufferCodec;
|
||||
|
||||
protected SynchedIndependentAnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> packetBufferCodec) {
|
||||
super(defaultValueSupplier, mutable);
|
||||
this.packetBufferCodec = packetBufferCodec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharedKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketBufferCodec<T> getPacketBufferCodec() {
|
||||
return this.packetBufferCodec;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import net.minecraftforge.registries.RegistryBuilder;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
import com.tiedup.remake.rig.anim.SynchedAnimationVariableKey.SynchedIndependentAnimationVariableKey;
|
||||
import com.tiedup.remake.rig.util.PacketBufferCodec;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class SynchedAnimationVariableKeys {
|
||||
private static final Supplier<RegistryBuilder<SynchedAnimationVariableKey<?>>> BUILDER = () -> new RegistryBuilder<SynchedAnimationVariableKey<?>>().addCallback(SynchedAnimationVariableKey.getRegistryCallback());
|
||||
|
||||
public static final DeferredRegister<SynchedAnimationVariableKey<?>> SYNCHED_ANIMATION_VARIABLE_KEYS = DeferredRegister.create(TiedUpRigConstants.identifier("synched_animation_variable_keys"), TiedUpRigConstants.MODID);
|
||||
public static final Supplier<IForgeRegistry<SynchedAnimationVariableKey<?>>> REGISTRY = SYNCHED_ANIMATION_VARIABLE_KEYS.makeRegistry(BUILDER);
|
||||
|
||||
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Vec3>> DESTINATION = SYNCHED_ANIMATION_VARIABLE_KEYS.register("destination", () ->
|
||||
SynchedAnimationVariableKey.independent(animator -> animator.getEntityPatch().getOriginal().position(), true, PacketBufferCodec.VEC3));
|
||||
|
||||
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Integer>> TARGET_ENTITY = SYNCHED_ANIMATION_VARIABLE_KEYS.register("target_entity", () ->
|
||||
SynchedAnimationVariableKey.independent(animator -> (Integer)null, true, PacketBufferCodec.INTEGER));
|
||||
|
||||
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Integer>> CHARGING_TICKS = SYNCHED_ANIMATION_VARIABLE_KEYS.register("animation_playing_speed", () ->
|
||||
SynchedAnimationVariableKey.independent(animator -> 0, true, PacketBufferCodec.INTEGER));
|
||||
}
|
||||
302
src/main/java/com/tiedup/remake/rig/anim/TransformSheet.java
Normal file
302
src/main/java/com/tiedup/remake/rig/anim/TransformSheet.java
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class TransformSheet {
|
||||
public static final TransformSheet EMPTY_SHEET = new TransformSheet(List.of(new Keyframe(0.0F, JointTransform.empty()), new Keyframe(Float.MAX_VALUE, JointTransform.empty())));
|
||||
public static final Function<Vec3, TransformSheet> EMPTY_SHEET_PROVIDER = translation -> {
|
||||
return new TransformSheet(List.of(new Keyframe(0.0F, JointTransform.translation(new Vec3f(translation))), new Keyframe(Float.MAX_VALUE, JointTransform.empty())));
|
||||
};
|
||||
|
||||
private Keyframe[] keyframes;
|
||||
|
||||
public TransformSheet() {
|
||||
this(new Keyframe[0]);
|
||||
}
|
||||
|
||||
public TransformSheet(int size) {
|
||||
this(new Keyframe[size]);
|
||||
}
|
||||
|
||||
public TransformSheet(List<Keyframe> keyframeList) {
|
||||
this(keyframeList.toArray(new Keyframe[0]));
|
||||
}
|
||||
|
||||
public TransformSheet(Keyframe[] keyframes) {
|
||||
this.keyframes = keyframes;
|
||||
}
|
||||
|
||||
public Keyframe[] getKeyframes() {
|
||||
return this.keyframes;
|
||||
}
|
||||
|
||||
public TransformSheet copyAll() {
|
||||
return this.copy(0, this.keyframes.length);
|
||||
}
|
||||
|
||||
public TransformSheet copy(int start, int end) {
|
||||
int len = end - start;
|
||||
Keyframe[] newKeyframes = new Keyframe[len];
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
Keyframe kf = this.keyframes[i + start];
|
||||
newKeyframes[i] = new Keyframe(kf);
|
||||
}
|
||||
|
||||
return new TransformSheet(newKeyframes);
|
||||
}
|
||||
|
||||
public TransformSheet readFrom(TransformSheet opponent) {
|
||||
if (opponent.keyframes.length != this.keyframes.length) {
|
||||
this.keyframes = new Keyframe[opponent.keyframes.length];
|
||||
|
||||
for (int i = 0; i < this.keyframes.length; i++) {
|
||||
this.keyframes[i] = Keyframe.empty();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.keyframes.length; i++) {
|
||||
this.keyframes[i].copyFrom(opponent.keyframes[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransformSheet createInterpolated(float[] timestamp) {
|
||||
TransformSheet interpolationCreated = new TransformSheet(timestamp.length);
|
||||
|
||||
for (int i = 0; i < timestamp.length; i++) {
|
||||
interpolationCreated.keyframes[i] = new Keyframe(timestamp[i], this.getInterpolatedTransform(timestamp[i]));
|
||||
}
|
||||
|
||||
return interpolationCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform each joint
|
||||
*/
|
||||
public void forEach(BiConsumer<Integer, Keyframe> task) {
|
||||
this.forEach(task, 0, this.keyframes.length);
|
||||
}
|
||||
|
||||
public void forEach(BiConsumer<Integer, Keyframe> task, int start, int end) {
|
||||
end = Math.min(end, this.keyframes.length);
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
task.accept(i, this.keyframes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3f getInterpolatedTranslation(float currentTime) {
|
||||
InterpolationInfo interpolInfo = this.getInterpolationInfo(currentTime);
|
||||
|
||||
if (interpolInfo == InterpolationInfo.INVALID) {
|
||||
return new Vec3f();
|
||||
}
|
||||
|
||||
Vec3f vec3f = MathUtils.lerpVector(this.keyframes[interpolInfo.prev].transform().translation(), this.keyframes[interpolInfo.next].transform().translation(), interpolInfo.delta);
|
||||
return vec3f;
|
||||
}
|
||||
|
||||
public JointTransform getInterpolatedTransform(float currentTime) {
|
||||
return this.getInterpolatedTransform(this.getInterpolationInfo(currentTime));
|
||||
}
|
||||
|
||||
public JointTransform getInterpolatedTransform(InterpolationInfo interpolationInfo) {
|
||||
if (interpolationInfo == InterpolationInfo.INVALID) {
|
||||
return JointTransform.empty();
|
||||
}
|
||||
|
||||
JointTransform trasnform = JointTransform.interpolate(this.keyframes[interpolationInfo.prev].transform(), this.keyframes[interpolationInfo.next].transform(), interpolationInfo.delta);
|
||||
return trasnform;
|
||||
}
|
||||
|
||||
public TransformSheet extend(TransformSheet target) {
|
||||
int newKeyLength = this.keyframes.length + target.keyframes.length;
|
||||
Keyframe[] newKeyfrmaes = new Keyframe[newKeyLength];
|
||||
|
||||
for (int i = 0; i < this.keyframes.length; i++) {
|
||||
newKeyfrmaes[i] = this.keyframes[i];
|
||||
}
|
||||
|
||||
for (int i = this.keyframes.length; i < newKeyLength; i++) {
|
||||
newKeyfrmaes[i] = new Keyframe(target.keyframes[i - this.keyframes.length]);
|
||||
}
|
||||
|
||||
this.keyframes = newKeyfrmaes;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransformSheet getFirstFrame() {
|
||||
TransformSheet part = this.copy(0, 2);
|
||||
Keyframe[] keyframes = part.getKeyframes();
|
||||
keyframes[1].transform().copyFrom(keyframes[0].transform());
|
||||
|
||||
return part;
|
||||
}
|
||||
|
||||
public void correctAnimationByNewPosition(Vec3f startpos, Vec3f startToEnd, Vec3f modifiedStart, Vec3f modifiedStartToEnd) {
|
||||
Keyframe[] keyframes = this.getKeyframes();
|
||||
Keyframe startKeyframe = keyframes[0];
|
||||
Keyframe endKeyframe = keyframes[keyframes.length - 1];
|
||||
float pitchDeg = (float) Math.toDegrees(Mth.atan2(modifiedStartToEnd.y - startToEnd.y, modifiedStartToEnd.length()));
|
||||
float yawDeg = (float) MathUtils.getAngleBetween(modifiedStartToEnd.copy().multiply(1.0F, 0.0F, 1.0F), startToEnd.copy().multiply(1.0F, 0.0F, 1.0F));
|
||||
|
||||
for (Keyframe kf : keyframes) {
|
||||
float lerp = (kf.time() - startKeyframe.time()) / (endKeyframe.time() - startKeyframe.time());
|
||||
Vec3f line = MathUtils.lerpVector(new Vec3f(0F, 0F, 0F), startToEnd, lerp);
|
||||
Vec3f modifiedLine = MathUtils.lerpVector(new Vec3f(0F, 0F, 0F), modifiedStartToEnd, lerp);
|
||||
Vec3f keyTransform = kf.transform().translation();
|
||||
Vec3f startToKeyTransform = keyTransform.copy().sub(startpos).multiply(-1.0F, 1.0F, -1.0F);
|
||||
Vec3f animOnLine = startToKeyTransform.copy().sub(line);
|
||||
OpenMatrix4f rotator = OpenMatrix4f.createRotatorDeg(pitchDeg, Vec3f.X_AXIS).mulFront(OpenMatrix4f.createRotatorDeg(yawDeg, Vec3f.Y_AXIS));
|
||||
Vec3f toNewKeyTransform = modifiedLine.add(OpenMatrix4f.transform3v(rotator, animOnLine, null));
|
||||
keyTransform.set(modifiedStart.copy().add((toNewKeyTransform)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the animation coord system to world coord system regarding origin point as @param worldDest
|
||||
*
|
||||
* @param entitypatch
|
||||
* @param worldStart
|
||||
* @param worldDest
|
||||
* @param xRot
|
||||
* @param entityYRot
|
||||
* @param startFrame
|
||||
* @param endFrame
|
||||
* @return
|
||||
*/
|
||||
public TransformSheet transformToWorldCoordOriginAsDest(LivingEntityPatch<?> entitypatch, Vec3 startInWorld, Vec3 destInWorld, float entityYRot, float destYRot, int startFrmae, int destFrame) {
|
||||
TransformSheet byStart = this.copy(0, destFrame + 1);
|
||||
TransformSheet byDest = this.copy(0, destFrame + 1);
|
||||
TransformSheet result = new TransformSheet(destFrame + 1);
|
||||
Vec3 toTargetInWorld = destInWorld.subtract(startInWorld);
|
||||
double worldMagnitude = toTargetInWorld.horizontalDistance();
|
||||
double animMagnitude = this.keyframes[0].transform().translation().horizontalDistance();
|
||||
float scale = (float)(worldMagnitude / animMagnitude);
|
||||
|
||||
byStart.forEach((idx, keyframe) -> {
|
||||
keyframe.transform().translation().sub(this.keyframes[0].transform().translation());
|
||||
keyframe.transform().translation().multiply(1.0F, 1.0F, scale);
|
||||
keyframe.transform().translation().rotate(-entityYRot, Vec3f.Y_AXIS);
|
||||
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
|
||||
keyframe.transform().translation().add(startInWorld);
|
||||
});
|
||||
|
||||
byDest.forEach((idx, keyframe) -> {
|
||||
keyframe.transform().translation().multiply(1.0F, 1.0F, Mth.lerp((idx / (float)destFrame), scale, 1.0F));
|
||||
keyframe.transform().translation().rotate(-destYRot, Vec3f.Y_AXIS);
|
||||
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
|
||||
keyframe.transform().translation().add(destInWorld);
|
||||
});
|
||||
|
||||
for (int i = 0; i < destFrame + 1; i++) {
|
||||
if (i <= startFrmae) {
|
||||
result.getKeyframes()[i] = new Keyframe(this.keyframes[i].time(), JointTransform.translation(byStart.getKeyframes()[i].transform().translation()));
|
||||
} else {
|
||||
float lerp = this.keyframes[i].time() == 0.0F ? 0.0F : this.keyframes[i].time() / this.keyframes[destFrame].time();
|
||||
Vec3f lerpTranslation = Vec3f.interpolate(byStart.getKeyframes()[i].transform().translation(), byDest.getKeyframes()[i].transform().translation(), lerp, null);
|
||||
result.getKeyframes()[i] = new Keyframe(this.keyframes[i].time(), JointTransform.translation(lerpTranslation));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.keyframes.length > destFrame) {
|
||||
TransformSheet behindDestination = this.copy(destFrame + 1, this.keyframes.length);
|
||||
|
||||
behindDestination.forEach((idx, keyframe) -> {
|
||||
keyframe.transform().translation().sub(this.keyframes[destFrame].transform().translation());
|
||||
keyframe.transform().translation().rotate(entityYRot, Vec3f.Y_AXIS);
|
||||
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
|
||||
keyframe.transform().translation().add(result.getKeyframes()[destFrame].transform().translation());
|
||||
});
|
||||
|
||||
result.extend(behindDestination);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public InterpolationInfo getInterpolationInfo(float currentTime) {
|
||||
if (this.keyframes.length == 0) {
|
||||
return InterpolationInfo.INVALID;
|
||||
}
|
||||
|
||||
if (currentTime < 0.0F) {
|
||||
currentTime = this.keyframes[this.keyframes.length - 1].time() + currentTime;
|
||||
}
|
||||
|
||||
// Binary search
|
||||
int begin = 0, end = this.keyframes.length - 1;
|
||||
|
||||
while (end - begin > 1) {
|
||||
int i = begin + (end - begin) / 2;
|
||||
|
||||
if (this.keyframes[i].time() <= currentTime && this.keyframes[i+1].time() > currentTime) {
|
||||
begin = i;
|
||||
end = i+1;
|
||||
break;
|
||||
} else {
|
||||
if (this.keyframes[i].time() > currentTime) {
|
||||
end = i;
|
||||
} else if (this.keyframes[i+1].time() <= currentTime) {
|
||||
begin = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float progression = Mth.clamp((currentTime - this.keyframes[begin].time()) / (this.keyframes[end].time() - this.keyframes[begin].time()), 0.0F, 1.0F);
|
||||
return new InterpolationInfo(begin, end, Float.isNaN(progression) ? 1.0F : progression);
|
||||
}
|
||||
|
||||
public float maxFrameTime() {
|
||||
float maxFrameTime = -1.0F;
|
||||
|
||||
for (Keyframe kf : this.keyframes) {
|
||||
if (kf.time() > maxFrameTime) {
|
||||
maxFrameTime = kf.time();
|
||||
}
|
||||
}
|
||||
|
||||
return maxFrameTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int idx = 0;
|
||||
|
||||
for (Keyframe kf : this.keyframes) {
|
||||
sb.append(kf);
|
||||
|
||||
if (++idx < this.keyframes.length) {
|
||||
sb.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static record InterpolationInfo(int prev, int next, float delta) {
|
||||
public static final InterpolationInfo INVALID = new InterpolationInfo(-1, -1, -1.0F);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskReloadListener;
|
||||
import com.tiedup.remake.rig.anim.client.property.LayerInfo;
|
||||
import com.tiedup.remake.rig.anim.client.property.TrailInfo;
|
||||
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class AnimationSubFileReader {
|
||||
public static final SubFileType<ClientProperty> SUBFILE_CLIENT_PROPERTY = new ClientPropertyType();
|
||||
public static final SubFileType<PovSettings> SUBFILE_POV_ANIMATION = new PovAnimationType();
|
||||
|
||||
public static void readAndApply(StaticAnimation animation, Resource iresource, SubFileType<?> subFileType) {
|
||||
InputStream inputstream = null;
|
||||
|
||||
try {
|
||||
inputstream = iresource.open();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
assert inputstream != null : "Input stream is null";
|
||||
|
||||
try {
|
||||
subFileType.apply(inputstream, animation);
|
||||
} catch (JsonParseException e) {
|
||||
TiedUpRigConstants.LOGGER.warn("Can't read sub file " + subFileType.directory + " for " + animation);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class SubFileType<T> {
|
||||
private final String directory;
|
||||
private final AnimationSubFileDeserializer<T> deserializer;
|
||||
|
||||
private SubFileType(String directory, AnimationSubFileDeserializer<T> deserializer) {
|
||||
this.directory = directory;
|
||||
this.deserializer = deserializer;
|
||||
}
|
||||
|
||||
// Deserialize from input stream
|
||||
public void apply(InputStream inputstream, StaticAnimation animation) {
|
||||
Reader reader = new InputStreamReader(inputstream, StandardCharsets.UTF_8);
|
||||
JsonReader jsonReader = new JsonReader(reader);
|
||||
jsonReader.setLenient(true);
|
||||
T deserialized = this.deserializer.deserialize(animation, Streams.parse(jsonReader));
|
||||
this.applySubFileInfo(deserialized, animation);
|
||||
}
|
||||
|
||||
// Deserialize from json object
|
||||
public void apply(JsonElement jsonElement, StaticAnimation animation) {
|
||||
T deserialized = this.deserializer.deserialize(animation, jsonElement);
|
||||
this.applySubFileInfo(deserialized, animation);
|
||||
}
|
||||
|
||||
protected abstract void applySubFileInfo(T deserialized, StaticAnimation animation);
|
||||
|
||||
public String getDirectory() {
|
||||
return this.directory;
|
||||
}
|
||||
}
|
||||
|
||||
private record ClientProperty(LayerInfo layerInfo, LayerInfo multilayerInfo, List<TrailInfo> trailInfo) {
|
||||
}
|
||||
|
||||
private static class ClientPropertyType extends SubFileType<ClientProperty> {
|
||||
private ClientPropertyType() {
|
||||
super("data", new AnimationSubFileReader.ClientAnimationPropertyDeserializer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySubFileInfo(ClientProperty deserialized, StaticAnimation animation) {
|
||||
if (deserialized.layerInfo() != null) {
|
||||
if (deserialized.layerInfo().jointMaskEntry.isValid()) {
|
||||
animation.addProperty(ClientAnimationProperties.JOINT_MASK, deserialized.layerInfo().jointMaskEntry);
|
||||
}
|
||||
|
||||
animation.addProperty(ClientAnimationProperties.LAYER_TYPE, deserialized.layerInfo().layerType);
|
||||
animation.addProperty(ClientAnimationProperties.PRIORITY, deserialized.layerInfo().priority);
|
||||
}
|
||||
|
||||
if (deserialized.multilayerInfo() != null) {
|
||||
DirectStaticAnimation multilayerAnimation = new DirectStaticAnimation(animation.getLocation(), animation.getTransitionTime(), animation.isRepeat(), animation.getRegistryName().toString() + "_multilayer", animation.getArmature());
|
||||
|
||||
if (deserialized.multilayerInfo().jointMaskEntry.isValid()) {
|
||||
multilayerAnimation.addProperty(ClientAnimationProperties.JOINT_MASK, deserialized.multilayerInfo().jointMaskEntry);
|
||||
}
|
||||
|
||||
multilayerAnimation.addProperty(ClientAnimationProperties.LAYER_TYPE, deserialized.multilayerInfo().layerType);
|
||||
multilayerAnimation.addProperty(ClientAnimationProperties.PRIORITY, deserialized.multilayerInfo().priority);
|
||||
multilayerAnimation.addProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER, (self, entitypatch, speed, prevElapsedTime, elapsedTime) -> {
|
||||
Layer baseLayer = entitypatch.getClientAnimator().baseLayer;
|
||||
|
||||
if (baseLayer.animationPlayer.getAnimation().get().getRealAnimation().get() != animation) {
|
||||
return Pair.of(prevElapsedTime, elapsedTime);
|
||||
}
|
||||
|
||||
if (!self.isStaticAnimation() && baseLayer.animationPlayer.getAnimation().get().isStaticAnimation()) {
|
||||
return Pair.of(prevElapsedTime + speed, elapsedTime + speed);
|
||||
}
|
||||
|
||||
return Pair.of(baseLayer.animationPlayer.getPrevElapsedTime(), baseLayer.animationPlayer.getElapsedTime());
|
||||
});
|
||||
|
||||
animation.addProperty(ClientAnimationProperties.MULTILAYER_ANIMATION, multilayerAnimation);
|
||||
}
|
||||
|
||||
if (deserialized.trailInfo().size() > 0) {
|
||||
animation.addProperty(ClientAnimationProperties.TRAIL_EFFECT, deserialized.trailInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClientAnimationPropertyDeserializer implements AnimationSubFileDeserializer<ClientProperty> {
|
||||
private static LayerInfo deserializeLayerInfo(JsonObject jsonObject) {
|
||||
return deserializeLayerInfo(jsonObject, null);
|
||||
}
|
||||
|
||||
private static LayerInfo deserializeLayerInfo(JsonObject jsonObject, @Nullable Layer.LayerType defaultLayerType) {
|
||||
JointMaskEntry.Builder builder = JointMaskEntry.builder();
|
||||
Layer.Priority priority = jsonObject.has("priority") ? Layer.Priority.valueOf(GsonHelper.getAsString(jsonObject, "priority")) : null;
|
||||
Layer.LayerType layerType = jsonObject.has("layer") ? Layer.LayerType.valueOf(GsonHelper.getAsString(jsonObject, "layer")) : Layer.LayerType.BASE_LAYER;
|
||||
|
||||
if (jsonObject.has("masks")) {
|
||||
JsonArray maskArray = jsonObject.get("masks").getAsJsonArray();
|
||||
|
||||
if (!maskArray.isEmpty()) {
|
||||
builder.defaultMask(JointMaskReloadListener.getNoneMask());
|
||||
|
||||
maskArray.forEach(element -> {
|
||||
JsonObject jointMaskEntry = element.getAsJsonObject();
|
||||
String livingMotionName = GsonHelper.getAsString(jointMaskEntry, "livingmotion");
|
||||
String type = GsonHelper.getAsString(jointMaskEntry, "type");
|
||||
|
||||
if (!type.contains(":")) {
|
||||
type = (new StringBuilder(TiedUpRigConstants.MODID)).append(":").append(type).toString();
|
||||
}
|
||||
|
||||
if (livingMotionName.equals("ALL")) {
|
||||
builder.defaultMask(JointMaskReloadListener.getJointMaskEntry(type));
|
||||
} else {
|
||||
builder.mask((LivingMotion) LivingMotion.ENUM_MANAGER.getOrThrow(livingMotionName), JointMaskReloadListener.getJointMaskEntry(type));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new LayerInfo(builder.create(), priority, (defaultLayerType == null) ? layerType : defaultLayerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientProperty deserialize(StaticAnimation animation, JsonElement json) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
LayerInfo layerInfo = null;
|
||||
LayerInfo multilayerInfo = null;
|
||||
|
||||
if (jsonObject.has("multilayer")) {
|
||||
JsonObject multiplayerJson = jsonObject.get("multilayer").getAsJsonObject();
|
||||
layerInfo = deserializeLayerInfo(multiplayerJson.get("base").getAsJsonObject());
|
||||
multilayerInfo = deserializeLayerInfo(multiplayerJson.get("composite").getAsJsonObject(), Layer.LayerType.COMPOSITE_LAYER);
|
||||
} else {
|
||||
layerInfo = deserializeLayerInfo(jsonObject);
|
||||
}
|
||||
|
||||
List<TrailInfo> trailInfos = Lists.newArrayList();
|
||||
|
||||
if (jsonObject.has("trail_effects")) {
|
||||
JsonArray trailArray = jsonObject.get("trail_effects").getAsJsonArray();
|
||||
trailArray.forEach(element -> trailInfos.add(TrailInfo.deserialize(element)));
|
||||
}
|
||||
|
||||
return new ClientProperty(layerInfo, multilayerInfo, trailInfos);
|
||||
}
|
||||
}
|
||||
|
||||
public static record PovSettings(
|
||||
@Nullable TransformSheet cameraTransform,
|
||||
Map<String, Boolean> visibilities,
|
||||
RootTransformation rootTransformation,
|
||||
@Nullable ViewLimit viewLimit,
|
||||
boolean visibilityOthers,
|
||||
boolean hasUniqueAnimation,
|
||||
boolean syncFrame
|
||||
) {
|
||||
public enum RootTransformation {
|
||||
CAMERA, WORLD
|
||||
}
|
||||
|
||||
public record ViewLimit(float xRotMin, float xRotMax, float yRotMin, float yRotMax) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class PovAnimationType extends SubFileType<PovSettings> {
|
||||
private PovAnimationType() {
|
||||
super("pov", new AnimationSubFileReader.PovAnimationDeserializer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySubFileInfo(PovSettings deserialized, StaticAnimation animation) {
|
||||
ResourceLocation povAnimationLocation = deserialized.hasUniqueAnimation() ? AnimationManager.getSubAnimationFileLocation(animation.getLocation(), SUBFILE_POV_ANIMATION) : animation.getLocation();
|
||||
DirectStaticAnimation povAnimation = new DirectStaticAnimation(povAnimationLocation, animation.getTransitionTime(), animation.isRepeat(), animation.getRegistryName().toString() + "_pov", animation.getArmature()) {
|
||||
@Override
|
||||
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation pAnimation) {
|
||||
return animation.getPlaySpeed(entitypatch, pAnimation);
|
||||
}
|
||||
};
|
||||
|
||||
animation.getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).ifPresent(speedModifier -> {
|
||||
povAnimation.addProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER, speedModifier);
|
||||
});
|
||||
|
||||
if (deserialized.syncFrame()) {
|
||||
animation.getProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER).ifPresent(elapsedTimeModifier -> {
|
||||
povAnimation.addProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER, elapsedTimeModifier);
|
||||
});
|
||||
}
|
||||
|
||||
animation.addProperty(ClientAnimationProperties.POV_ANIMATION, povAnimation);
|
||||
animation.addProperty(ClientAnimationProperties.POV_SETTINGS, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PovAnimationDeserializer implements AnimationSubFileDeserializer<PovSettings> {
|
||||
@Override
|
||||
public PovSettings deserialize(StaticAnimation animation, JsonElement json) throws AssetLoadingException, JsonParseException {
|
||||
JsonObject jObject = json.getAsJsonObject();
|
||||
TransformSheet cameraTransform = null;
|
||||
PovSettings.ViewLimit viewLimit = null;
|
||||
PovSettings.RootTransformation rootTrasnformation = null;
|
||||
|
||||
if (jObject.has("root")) {
|
||||
rootTrasnformation = PovSettings.RootTransformation.valueOf(ParseUtil.toUpperCase(GsonHelper.getAsString(jObject, "root")));
|
||||
} else {
|
||||
if (animation instanceof ActionAnimation) {
|
||||
rootTrasnformation = PovSettings.RootTransformation.WORLD;
|
||||
} else {
|
||||
rootTrasnformation = PovSettings.RootTransformation.CAMERA;
|
||||
}
|
||||
}
|
||||
|
||||
if (jObject.has("camera")) {
|
||||
JsonObject cameraTransformJObject = jObject.getAsJsonObject("camera");
|
||||
cameraTransform = JsonAssetLoader.getTransformSheet(cameraTransformJObject, null, false, JsonAssetLoader.TransformFormat.ATTRIBUTES);
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<String, Boolean> visibilitiesBuilder = ImmutableMap.builder();
|
||||
boolean others = false;
|
||||
|
||||
if (jObject.has("visibilities")) {
|
||||
JsonObject visibilitiesObject = jObject.getAsJsonObject("visibilities");
|
||||
visibilitiesObject.entrySet().stream().filter((e) -> !"others".equals(e.getKey())).forEach((entry) -> visibilitiesBuilder.put(entry.getKey(), entry.getValue().getAsBoolean()));
|
||||
others = visibilitiesObject.get("others").getAsBoolean();
|
||||
} else {
|
||||
visibilitiesBuilder.put("leftArm", true);
|
||||
visibilitiesBuilder.put("leftSleeve", true);
|
||||
visibilitiesBuilder.put("rightArm", true);
|
||||
visibilitiesBuilder.put("rightSleeve", true);
|
||||
}
|
||||
|
||||
if (jObject.has("limited_view_degrees")) {
|
||||
JsonObject limitedViewDegrees = jObject.getAsJsonObject("limited_view_degrees");
|
||||
JsonArray xRot = limitedViewDegrees.get("xRot").getAsJsonArray();
|
||||
JsonArray yRot = limitedViewDegrees.get("yRot").getAsJsonArray();
|
||||
|
||||
float xRotMin = Math.min(xRot.get(0).getAsFloat(), xRot.get(1).getAsFloat());
|
||||
float xRotMax = Math.max(xRot.get(0).getAsFloat(), xRot.get(1).getAsFloat());
|
||||
float yRotMin = Math.min(yRot.get(0).getAsFloat(), yRot.get(1).getAsFloat());
|
||||
float yRotMax = Math.max(yRot.get(0).getAsFloat(), yRot.get(1).getAsFloat());
|
||||
viewLimit = new PovSettings.ViewLimit(xRotMin, xRotMax, yRotMin, yRotMax);
|
||||
}
|
||||
|
||||
return new PovSettings(cameraTransform, visibilitiesBuilder.build(), rootTrasnformation, viewLimit, others, jObject.has("animation"), GsonHelper.getAsBoolean(jObject, "sync_frame", false));
|
||||
}
|
||||
}
|
||||
|
||||
public interface AnimationSubFileDeserializer<T> {
|
||||
public T deserialize(StaticAnimation animation, JsonElement json) throws JsonParseException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager;
|
||||
import com.tiedup.remake.rig.anim.AnimationPlayer;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.LivingMotions;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.ServerAnimator;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.Layer.Priority;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMask.BindModifier;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class ClientAnimator extends Animator {
|
||||
public static Animator getAnimator(LivingEntityPatch<?> entitypatch) {
|
||||
return entitypatch.isLogicalClient() ? new ClientAnimator(entitypatch) : ServerAnimator.getAnimator(entitypatch);
|
||||
}
|
||||
|
||||
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> compositeLivingAnimations;
|
||||
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> defaultLivingAnimations;
|
||||
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> defaultCompositeLivingAnimations;
|
||||
public final Layer.BaseLayer baseLayer;
|
||||
private LivingMotion currentMotion;
|
||||
private LivingMotion currentCompositeMotion;
|
||||
private boolean hardPaused;
|
||||
|
||||
public ClientAnimator(LivingEntityPatch<?> entitypatch) {
|
||||
this(entitypatch, Layer.BaseLayer::new);
|
||||
}
|
||||
|
||||
public ClientAnimator(LivingEntityPatch<?> entitypatch, Supplier<Layer.BaseLayer> layerSupplier) {
|
||||
super(entitypatch);
|
||||
|
||||
this.currentMotion = LivingMotions.IDLE;
|
||||
this.currentCompositeMotion = LivingMotions.IDLE;
|
||||
this.compositeLivingAnimations = Maps.newHashMap();
|
||||
this.defaultLivingAnimations = Maps.newHashMap();
|
||||
this.defaultCompositeLivingAnimations = Maps.newHashMap();
|
||||
this.baseLayer = layerSupplier.get();
|
||||
}
|
||||
|
||||
/** Play an animation by animation instance **/
|
||||
@Override
|
||||
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier) {
|
||||
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
|
||||
layer.paused = false;
|
||||
layer.playAnimation(nextAnimation, this.entitypatch, transitionTimeModifier);
|
||||
}
|
||||
|
||||
// RIG : playAnimationAt(..., AnimatorControlPacket.Layer, AnimatorControlPacket.Priority)
|
||||
// strippé — re-implem Phase 2 avec packet dédié. Voir AnimationVariablePacket.
|
||||
|
||||
|
||||
@Override
|
||||
public void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation) {
|
||||
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
|
||||
layer.paused = false;
|
||||
layer.playAnimationInstantly(nextAnimation, this.entitypatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation) {
|
||||
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
|
||||
|
||||
if (nextAnimation.get().getPriority().isHigherThan(layer.animationPlayer.getRealAnimation().get().getPriority())) {
|
||||
if (!layer.animationPlayer.isEnd() && layer.animationPlayer.getAnimation() != null) {
|
||||
layer.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
layer.animationPlayer.terminate(this.entitypatch);
|
||||
}
|
||||
|
||||
layer.nextAnimation = nextAnimation;
|
||||
layer.paused = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation) {
|
||||
Layer layer = targetAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(targetAnimation.get().getPriority());
|
||||
|
||||
if (layer.animationPlayer.getRealAnimation() == targetAnimation) {
|
||||
layer.animationPlayer.terminate(this.entitypatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSoftPause(boolean paused) {
|
||||
this.iterAllLayers(layer -> layer.paused = paused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHardPause(boolean paused) {
|
||||
this.hardPaused = paused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> animation) {
|
||||
if (AnimationManager.checkNull(animation)) {
|
||||
TiedUpRigConstants.LOGGER.warn("Unable to put an empty animation for " + livingMotion);
|
||||
return;
|
||||
}
|
||||
|
||||
Layer.LayerType layerType = animation.get().getLayerType();
|
||||
boolean isBaseLayer = (layerType == Layer.LayerType.BASE_LAYER);
|
||||
|
||||
Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> storage = layerType == Layer.LayerType.BASE_LAYER ? this.livingAnimations : this.compositeLivingAnimations;
|
||||
LivingMotion compareMotion = layerType == Layer.LayerType.BASE_LAYER ? this.currentMotion : this.currentCompositeMotion;
|
||||
Layer layer = layerType == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(animation.get().getPriority());
|
||||
storage.put(livingMotion, animation);
|
||||
|
||||
if (livingMotion == compareMotion) {
|
||||
EntityState state = this.getEntityState();
|
||||
|
||||
if (!state.inaction()) {
|
||||
layer.playLivingAnimation(animation, this.entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBaseLayer) {
|
||||
animation.get().getProperty(ClientAnimationProperties.MULTILAYER_ANIMATION).ifPresent(multilayerAnimation -> {
|
||||
this.compositeLivingAnimations.put(livingMotion, multilayerAnimation);
|
||||
|
||||
if (livingMotion == this.currentCompositeMotion) {
|
||||
EntityState state = this.getEntityState();
|
||||
|
||||
if (!state.inaction()) {
|
||||
layer.playLivingAnimation(multilayerAnimation, this.entitypatch);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentMotionsAsDefault() {
|
||||
this.defaultLivingAnimations.putAll(this.livingAnimations);
|
||||
this.defaultCompositeLivingAnimations.putAll(this.compositeLivingAnimations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLivingAnimations() {
|
||||
super.resetLivingAnimations();
|
||||
this.compositeLivingAnimations.clear();
|
||||
this.defaultLivingAnimations.forEach((key, val) -> this.addLivingAnimation(key, val));
|
||||
this.defaultCompositeLivingAnimations.forEach((key, val) -> this.addLivingAnimation(key, val));
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends StaticAnimation> getLivingMotion(LivingMotion motion) {
|
||||
return this.livingAnimations.getOrDefault(motion, this.livingAnimations.get(LivingMotions.IDLE));
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends StaticAnimation> getCompositeLivingMotion(LivingMotion motion) {
|
||||
return this.compositeLivingAnimations.get(motion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit() {
|
||||
super.postInit();
|
||||
|
||||
this.setCurrentMotionsAsDefault();
|
||||
|
||||
AssetAccessor<? extends StaticAnimation> idleMotion = this.livingAnimations.get(this.currentMotion);
|
||||
this.baseLayer.playAnimationInstantly(idleMotion, this.entitypatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
/**
|
||||
// Layer debugging
|
||||
for (Layer layer : this.getAllLayers()) {
|
||||
System.out.println(layer);
|
||||
}
|
||||
System.out.println();
|
||||
**/
|
||||
|
||||
if (this.hardPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.baseLayer.update(this.entitypatch);
|
||||
|
||||
if (this.baseLayer.animationPlayer.isEnd() && this.baseLayer.nextAnimation == null && this.currentMotion != LivingMotions.DEATH) {
|
||||
this.entitypatch.updateMotion(false);
|
||||
|
||||
if (this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
|
||||
this.playAnimation(this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion), 0.0F);
|
||||
}
|
||||
|
||||
this.baseLayer.playAnimation(this.getLivingMotion(this.entitypatch.currentLivingMotion), this.entitypatch, 0.0F);
|
||||
} else {
|
||||
if (!this.compareCompositeMotion(this.entitypatch.currentCompositeMotion)) {
|
||||
/* Turns off the multilayer of the base layer */
|
||||
this.getLivingMotion(this.currentCompositeMotion).get().getProperty(ClientAnimationProperties.MULTILAYER_ANIMATION).ifPresent((multilayerAnimation) -> {
|
||||
if (!this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
|
||||
this.getCompositeLayer(multilayerAnimation.get().getPriority()).off(this.entitypatch);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
|
||||
AssetAccessor<? extends StaticAnimation> nextLivingAnimation = this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion);
|
||||
|
||||
if (nextLivingAnimation == null || nextLivingAnimation.get().getPriority() != this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()) {
|
||||
this.getCompositeLayer(this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()).off(this.entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
|
||||
this.playAnimation(this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion), 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.compareMotion(this.entitypatch.currentLivingMotion) && this.entitypatch.currentLivingMotion != LivingMotions.DEATH) {
|
||||
if (this.livingAnimations.containsKey(this.entitypatch.currentLivingMotion)) {
|
||||
this.baseLayer.playAnimation(this.getLivingMotion(this.entitypatch.currentLivingMotion), this.entitypatch, 0.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.currentMotion = this.entitypatch.currentLivingMotion;
|
||||
this.currentCompositeMotion = this.entitypatch.currentCompositeMotion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playDeathAnimation() {
|
||||
if (!this.getPlayerFor(null).getAnimation().get().getProperty(ActionAnimationProperty.IS_DEATH_ANIMATION).orElse(false)) {
|
||||
this.playAnimation(this.livingAnimations.getOrDefault(LivingMotions.DEATH, TiedUpRigRegistry.EMPTY_ANIMATION), 0.0F);
|
||||
this.currentMotion = LivingMotions.DEATH;
|
||||
}
|
||||
}
|
||||
|
||||
public Layer getCompositeLayer(Layer.Priority priority) {
|
||||
return this.baseLayer.compositeLayers.get(priority);
|
||||
}
|
||||
|
||||
public void renderDebuggingInfoForAllLayers(PoseStack poseStack, MultiBufferSource buffer, float partialTicks) {
|
||||
this.iterAllLayers((layer) -> {
|
||||
if (layer.isOff()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimationPlayer animPlayer = layer.animationPlayer;
|
||||
float playTime = Mth.lerp(partialTicks, animPlayer.getPrevElapsedTime(), animPlayer.getElapsedTime());
|
||||
animPlayer.getAnimation().get().renderDebugging(poseStack, buffer, entitypatch, playTime, partialTicks);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all layers
|
||||
* @param task
|
||||
*/
|
||||
public void iterAllLayers(Consumer<Layer> task) {
|
||||
task.accept(this.baseLayer);
|
||||
this.baseLayer.compositeLayers.values().forEach(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all activated layers from the highest layer
|
||||
* when base layer = highest, iterates only base layer
|
||||
* when base layer = middle, iterates base layer and highest composite layer
|
||||
* when base layer = lowest, iterates base layer and all composite layers
|
||||
*
|
||||
* @param task
|
||||
* @return true if all layers didn't return false by @param task
|
||||
*/
|
||||
public boolean iterVisibleLayersUntilFalse(Function<Layer, Boolean> task) {
|
||||
Layer.Priority[] highers = this.baseLayer.baseLayerPriority.highers();
|
||||
|
||||
for (int i = highers.length - 1; i >= 0; i--) {
|
||||
Layer layer = this.baseLayer.getLayer(highers[i]);
|
||||
|
||||
if (layer.isDisabled() || layer.animationPlayer.isEmpty()) {
|
||||
if (highers[i] == this.baseLayer.baseLayerPriority) {
|
||||
return task.apply(this.baseLayer);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!task.apply(layer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (highers[i] == this.baseLayer.baseLayerPriority) {
|
||||
return task.apply(this.baseLayer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose getPose(float partialTicks) {
|
||||
return this.getPose(partialTicks, true);
|
||||
}
|
||||
|
||||
public Pose getPose(float partialTicks, boolean useCurrentMotion) {
|
||||
Pose composedPose = new Pose();
|
||||
Pose baseLayerPose = this.baseLayer.getEnabledPose(this.entitypatch, useCurrentMotion, partialTicks);
|
||||
|
||||
Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> layerPoses = Maps.newLinkedHashMap();
|
||||
composedPose.load(baseLayerPose, Pose.LoadOperation.OVERWRITE);
|
||||
|
||||
for (Layer.Priority priority : this.baseLayer.baseLayerPriority.highers()) {
|
||||
Layer compositeLayer = this.baseLayer.compositeLayers.get(priority);
|
||||
|
||||
if (!compositeLayer.isDisabled() && !compositeLayer.animationPlayer.isEmpty()) {
|
||||
Pose layerPose = compositeLayer.getEnabledPose(this.entitypatch, useCurrentMotion, partialTicks);
|
||||
layerPoses.put(priority, Pair.of(compositeLayer.animationPlayer.getAnimation(), layerPose));
|
||||
composedPose.load(layerPose, Pose.LoadOperation.OVERWRITE);
|
||||
}
|
||||
}
|
||||
|
||||
Joint rootJoint = this.entitypatch.getArmature().rootJoint;
|
||||
this.applyBindModifier(baseLayerPose, composedPose, rootJoint, layerPoses, useCurrentMotion);
|
||||
|
||||
return composedPose;
|
||||
}
|
||||
|
||||
public Pose getComposedLayerPoseBelow(Layer.Priority priorityLimit, float partialTicks) {
|
||||
Pose composedPose = this.baseLayer.getEnabledPose(this.entitypatch, true, partialTicks);
|
||||
Pose baseLayerPose = this.baseLayer.getEnabledPose(this.entitypatch, true, partialTicks);
|
||||
Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> layerPoses = Maps.newLinkedHashMap();
|
||||
|
||||
for (Layer.Priority priority : priorityLimit.lowers()) {
|
||||
Layer compositeLayer = this.baseLayer.compositeLayers.get(priority);
|
||||
|
||||
if (!compositeLayer.isDisabled()) {
|
||||
Pose layerPose = compositeLayer.getEnabledPose(this.entitypatch, true, partialTicks);
|
||||
layerPoses.put(priority, Pair.of(compositeLayer.animationPlayer.getAnimation(), layerPose));
|
||||
composedPose.load(layerPose, Pose.LoadOperation.OVERWRITE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!layerPoses.isEmpty()) {
|
||||
this.applyBindModifier(baseLayerPose, composedPose, this.entitypatch.getArmature().rootJoint, layerPoses, true);
|
||||
}
|
||||
|
||||
return composedPose;
|
||||
}
|
||||
|
||||
public void applyBindModifier(Pose basePose, Pose result, Joint joint, Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> poses, boolean useCurrentMotion) {
|
||||
List<Priority> list = Lists.newArrayList(poses.keySet());
|
||||
Collections.reverse(list);
|
||||
|
||||
for (Layer.Priority priority : list) {
|
||||
AssetAccessor<? extends DynamicAnimation> nowPlaying = poses.get(priority).getFirst();
|
||||
JointMaskEntry jointMaskEntry = nowPlaying.get().getJointMaskEntry(this.entitypatch, useCurrentMotion).orElse(null);
|
||||
|
||||
if (jointMaskEntry != null) {
|
||||
LivingMotion livingMotion = this.getCompositeLayer(priority).getLivingMotion(this.entitypatch, useCurrentMotion);
|
||||
|
||||
if (nowPlaying.get().hasTransformFor(joint.getName()) && !jointMaskEntry.isMasked(livingMotion, joint.getName())) {
|
||||
JointMaskSet jointmaskset = jointMaskEntry.getMask(livingMotion);
|
||||
BindModifier bindModifier = jointmaskset.getBindModifier(joint.getName());
|
||||
|
||||
if (bindModifier != null) {
|
||||
bindModifier.modify(this.entitypatch, basePose, result, livingMotion, jointMaskEntry, priority, joint, poses);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Joint subJoints : joint.getSubJoints()) {
|
||||
this.applyBindModifier(basePose, result, subJoints, poses, useCurrentMotion);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean compareMotion(LivingMotion motion) {
|
||||
return this.currentMotion.isSame(motion);
|
||||
}
|
||||
|
||||
public boolean compareCompositeMotion(LivingMotion motion) {
|
||||
return this.currentCompositeMotion.isSame(motion);
|
||||
}
|
||||
|
||||
public void forceResetBeforeAction(LivingMotion livingMotion, LivingMotion compositeLivingMotion) {
|
||||
if (!this.currentMotion.equals(livingMotion)) {
|
||||
if (this.livingAnimations.containsKey(livingMotion)) {
|
||||
this.baseLayer.playAnimation(this.getLivingMotion(livingMotion), this.entitypatch, 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
this.entitypatch.currentLivingMotion = livingMotion;
|
||||
this.currentMotion = livingMotion;
|
||||
|
||||
if (!this.currentCompositeMotion.equals(compositeLivingMotion)) {
|
||||
if (this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
|
||||
this.getCompositeLayer(this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()).off(this.entitypatch);
|
||||
}
|
||||
|
||||
if (this.compositeLivingAnimations.containsKey(compositeLivingMotion)) {
|
||||
this.playAnimation(this.getCompositeLivingMotion(compositeLivingMotion), 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentCompositeMotion = LivingMotions.NONE;
|
||||
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
|
||||
}
|
||||
|
||||
public void resetMotion(boolean resetPrevMotion) {
|
||||
if (resetPrevMotion) this.currentMotion = LivingMotions.IDLE;
|
||||
this.entitypatch.currentLivingMotion = LivingMotions.IDLE;
|
||||
}
|
||||
|
||||
public void resetCompositeMotion() {
|
||||
if (this.currentCompositeMotion != LivingMotions.IDLE && this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
|
||||
AssetAccessor<? extends StaticAnimation> currentPlaying = this.getCompositeLivingMotion(this.currentCompositeMotion);
|
||||
AssetAccessor<? extends StaticAnimation> resetPlaying = this.getCompositeLivingMotion(LivingMotions.IDLE);
|
||||
|
||||
if (resetPlaying != null && currentPlaying != resetPlaying) {
|
||||
this.playAnimation(resetPlaying, 0.0F);
|
||||
} else if (currentPlaying != null) {
|
||||
this.getCompositeLayer(currentPlaying.get().getPriority()).off(this.entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentCompositeMotion = LivingMotions.NONE;
|
||||
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
|
||||
}
|
||||
|
||||
public void offAllLayers() {
|
||||
for (Layer layer : this.baseLayer.compositeLayers.values()) {
|
||||
layer.off(this.entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playShootingAnimation() {
|
||||
if (this.compositeLivingAnimations.containsKey(LivingMotions.SHOT)) {
|
||||
this.playAnimation(this.compositeLivingAnimations.get(LivingMotions.SHOT), 0.0F);
|
||||
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
|
||||
this.currentCompositeMotion = LivingMotions.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationPlayer getPlayerFor(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
|
||||
if (playingAnimation == null) {
|
||||
return this.baseLayer.animationPlayer;
|
||||
}
|
||||
|
||||
DynamicAnimation animation = playingAnimation.get();
|
||||
|
||||
if (animation instanceof StaticAnimation staticAnimation) {
|
||||
Layer layer = staticAnimation.getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(staticAnimation.getPriority());
|
||||
if (layer.animationPlayer.getAnimation() == playingAnimation) return layer.animationPlayer;
|
||||
}
|
||||
|
||||
for (Layer layer : this.baseLayer.compositeLayers.values()) {
|
||||
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation)) {
|
||||
return layer.animationPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
return this.baseLayer.animationPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
|
||||
DynamicAnimation animation = playingAnimation.get();
|
||||
|
||||
if (animation instanceof StaticAnimation staticAnimation) {
|
||||
Layer layer = staticAnimation.getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(staticAnimation.getPriority());
|
||||
|
||||
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation)) {
|
||||
return Optional.of(layer.animationPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.baseLayer.animationPlayer.getRealAnimation().equals(playingAnimation.get().getRealAnimation())) {
|
||||
return Optional.of(this.baseLayer.animationPlayer);
|
||||
}
|
||||
|
||||
for (Layer layer : this.baseLayer.compositeLayers.values()) {
|
||||
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation.get().getRealAnimation())) {
|
||||
return Optional.of(layer.animationPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public LivingMotion currentMotion() {
|
||||
return this.currentMotion;
|
||||
}
|
||||
|
||||
public LivingMotion currentCompositeMotion() {
|
||||
return this.currentCompositeMotion;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType) {
|
||||
for (Layer layer : this.baseLayer.compositeLayers.values()) {
|
||||
if (animationType.isAssignableFrom(layer.animationPlayer.getAnimation().getClass())) {
|
||||
return Pair.of(layer.animationPlayer, (T)layer.animationPlayer.getAnimation());
|
||||
}
|
||||
}
|
||||
|
||||
return animationType.isAssignableFrom(this.baseLayer.animationPlayer.getAnimation().getClass()) ? Pair.of(this.baseLayer.animationPlayer, (T)this.baseLayer.animationPlayer.getAnimation()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityState getEntityState() {
|
||||
TypeFlexibleHashMap<StateFactor<?>> stateMap = new TypeFlexibleHashMap<> (false);
|
||||
|
||||
for (Layer layer : this.baseLayer.compositeLayers.values()) {
|
||||
if (this.baseLayer.baseLayerPriority.isHigherThan(layer.priority)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!layer.isOff()) {
|
||||
stateMap.putAll(layer.animationPlayer.getAnimation().get().getStatesMap(this.entitypatch, layer.animationPlayer.getElapsedTime()));
|
||||
}
|
||||
|
||||
// put base layer states
|
||||
if (layer.priority == this.baseLayer.baseLayerPriority) {
|
||||
stateMap.putAll(this.baseLayer.animationPlayer.getAnimation().get().getStatesMap(this.entitypatch, this.baseLayer.animationPlayer.getElapsedTime()));
|
||||
}
|
||||
}
|
||||
|
||||
return new EntityState(stateMap);
|
||||
}
|
||||
}
|
||||
359
src/main/java/com/tiedup/remake/rig/anim/client/Layer.java
Normal file
359
src/main/java/com/tiedup/remake/rig/anim/client/Layer.java
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.tiedup.remake.rig.anim.AnimationPlayer;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.types.ConcurrentLinkAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.LayerOffAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.LinkAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class Layer {
|
||||
protected AssetAccessor<? extends StaticAnimation> nextAnimation;
|
||||
protected final LinkAnimation linkAnimation;
|
||||
protected final ConcurrentLinkAnimation concurrentLinkAnimation;
|
||||
protected final LayerOffAnimation layerOffAnimation;
|
||||
protected final Layer.Priority priority;
|
||||
protected boolean disabled;
|
||||
protected boolean paused;
|
||||
public final AnimationPlayer animationPlayer;
|
||||
|
||||
public Layer(Priority priority) {
|
||||
this(priority, AnimationPlayer::new);
|
||||
}
|
||||
|
||||
public Layer(Priority priority, Supplier<AnimationPlayer> animationPlayerProvider) {
|
||||
this.animationPlayer = animationPlayerProvider.get();
|
||||
this.linkAnimation = new LinkAnimation();
|
||||
this.concurrentLinkAnimation = new ConcurrentLinkAnimation();
|
||||
this.layerOffAnimation = new LayerOffAnimation(priority);
|
||||
this.priority = priority;
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, float transitionTimeModifier) {
|
||||
// Get pose before StaticAnimation#end is called
|
||||
Pose lastPose = this.getCurrentPose(entitypatch);
|
||||
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
this.resume();
|
||||
nextAnimation.get().begin(entitypatch);
|
||||
|
||||
if (!nextAnimation.get().isMetaAnimation()) {
|
||||
this.setLinkAnimation(nextAnimation, entitypatch, lastPose, transitionTimeModifier);
|
||||
this.linkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
|
||||
entitypatch.updateEntityState();
|
||||
this.nextAnimation = nextAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays an animation without a link animation
|
||||
*/
|
||||
public void playAnimationInstantly(AssetAccessor<? extends DynamicAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
this.resume();
|
||||
|
||||
nextAnimation.get().begin(entitypatch);
|
||||
nextAnimation.get().putOnPlayer(this.animationPlayer, entitypatch);
|
||||
entitypatch.updateEntityState();
|
||||
this.nextAnimation = null;
|
||||
}
|
||||
|
||||
protected void playLivingAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
this.resume();
|
||||
nextAnimation.get().begin(entitypatch);
|
||||
|
||||
if (!nextAnimation.get().isMetaAnimation()) {
|
||||
this.concurrentLinkAnimation.acceptFrom(this.animationPlayer.getRealAnimation(), nextAnimation, this.animationPlayer.getElapsedTime());
|
||||
this.concurrentLinkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
|
||||
entitypatch.updateEntityState();
|
||||
this.nextAnimation = nextAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
protected Pose getCurrentPose(LivingEntityPatch<?> entitypatch) {
|
||||
return entitypatch.getClientAnimator().getPose(0.0F, false);
|
||||
}
|
||||
|
||||
protected void setLinkAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, Pose lastPose, float transitionTimeModifier) {
|
||||
AssetAccessor<? extends DynamicAnimation> fromAnimation = this.animationPlayer.isEmpty() ? entitypatch.getClientAnimator().baseLayer.animationPlayer.getAnimation() : this.animationPlayer.getAnimation();
|
||||
|
||||
if (fromAnimation.get() instanceof LinkAnimation linkAnimation) {
|
||||
fromAnimation = linkAnimation.getFromAnimation();
|
||||
}
|
||||
|
||||
nextAnimation.get().setLinkAnimation(fromAnimation, lastPose, !this.animationPlayer.isEmpty(), transitionTimeModifier, entitypatch, this.linkAnimation);
|
||||
this.linkAnimation.getAnimationClip().setBaked();
|
||||
}
|
||||
|
||||
public void update(LivingEntityPatch<?> entitypatch) {
|
||||
if (this.paused) {
|
||||
this.animationPlayer.setElapsedTime(this.animationPlayer.getElapsedTime());
|
||||
} else {
|
||||
this.animationPlayer.tick(entitypatch);
|
||||
}
|
||||
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().tick(entitypatch);
|
||||
} else if (!this.paused) {
|
||||
if (this.nextAnimation != null) {
|
||||
if (!this.animationPlayer.getAnimation().get().isLinkAnimation() && !this.nextAnimation.get().isLinkAnimation()) {
|
||||
this.nextAnimation.get().begin(entitypatch);
|
||||
}
|
||||
|
||||
this.nextAnimation.get().putOnPlayer(this.animationPlayer, entitypatch);
|
||||
this.nextAnimation = null;
|
||||
} else {
|
||||
if (this.animationPlayer.getAnimation() instanceof LayerOffAnimation) {
|
||||
this.animationPlayer.getAnimation().get().end(entitypatch, TiedUpRigRegistry.EMPTY_ANIMATION, true);
|
||||
} else {
|
||||
this.off(entitypatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isBaseLayer()) {
|
||||
entitypatch.updateEntityState();
|
||||
entitypatch.updateMotion(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
this.paused = true;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
this.paused = false;
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
protected boolean isDisabled() {
|
||||
return this.disabled;
|
||||
}
|
||||
|
||||
public boolean isOff() {
|
||||
return this.isDisabled() || this.animationPlayer.isEmpty();
|
||||
}
|
||||
|
||||
protected boolean isBaseLayer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void copyLayerTo(Layer layer, float playbackTime) {
|
||||
AssetAccessor<? extends DynamicAnimation> animation;
|
||||
|
||||
if (this.animationPlayer.getAnimation() == this.linkAnimation) {
|
||||
this.linkAnimation.copyTo(layer.linkAnimation);
|
||||
animation = layer.linkAnimation;
|
||||
} else {
|
||||
animation = this.animationPlayer.getAnimation();
|
||||
}
|
||||
|
||||
layer.animationPlayer.setPlayAnimation(animation);
|
||||
layer.animationPlayer.setElapsedTime(this.animationPlayer.getPrevElapsedTime() + playbackTime, this.animationPlayer.getElapsedTime() + playbackTime);
|
||||
layer.nextAnimation = this.nextAnimation;
|
||||
layer.resume();
|
||||
}
|
||||
|
||||
public LivingMotion getLivingMotion(LivingEntityPatch<?> entitypatch, boolean current) {
|
||||
return current ? entitypatch.currentLivingMotion : entitypatch.getClientAnimator().currentMotion();
|
||||
}
|
||||
|
||||
public Pose getEnabledPose(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion, float partialTick) {
|
||||
Pose pose = this.animationPlayer.getCurrentPose(entitypatch, partialTick);
|
||||
this.animationPlayer.getAnimation().get().getJointMaskEntry(entitypatch, useCurrentMotion).ifPresent((jointEntry) -> pose.disableJoint((entry) -> jointEntry.isMasked(this.getLivingMotion(entitypatch, useCurrentMotion), entry.getKey())));
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
public void off(LivingEntityPatch<?> entitypatch) {
|
||||
if (!this.isDisabled() && !(this.animationPlayer.getAnimation() instanceof LayerOffAnimation)) {
|
||||
if (this.priority == null) {
|
||||
this.disableLayer();
|
||||
} else {
|
||||
float transitionTimeModifier = entitypatch.getClientAnimator().baseLayer.animationPlayer.getAnimation().get().getTransitionTime();
|
||||
setLayerOffAnimation(this.animationPlayer.getAnimation(), this.getEnabledPose(entitypatch, false, 1.0F), this.layerOffAnimation, transitionTimeModifier);
|
||||
this.playAnimationInstantly(this.layerOffAnimation, entitypatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disableLayer() {
|
||||
this.disabled = true;
|
||||
this.animationPlayer.setPlayAnimation(TiedUpRigRegistry.EMPTY_ANIMATION);
|
||||
}
|
||||
|
||||
public static void setLayerOffAnimation(AssetAccessor<? extends DynamicAnimation> currentAnimation, Pose currentPose, LayerOffAnimation offAnimation, float transitionTimeModifier) {
|
||||
offAnimation.setLastAnimation(currentAnimation.get().getRealAnimation());
|
||||
offAnimation.setLastPose(currentPose);
|
||||
offAnimation.setTotalTime(transitionTimeModifier);
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends DynamicAnimation> getNextAnimation() {
|
||||
return this.nextAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(this.isBaseLayer() ? "Base Layer(" + ((BaseLayer)this).baseLayerPriority + ") : " : " Composite Layer(" + this.priority + ") : ");
|
||||
sb.append(this.animationPlayer.getAnimation() + " ");
|
||||
sb.append(", prev elapsed time: " + this.animationPlayer.getPrevElapsedTime() + " ");
|
||||
sb.append(", elapsed time: " + this.animationPlayer.getElapsedTime() + " ");
|
||||
sb.append(", total time: " + this.animationPlayer.getAnimation().get().getTotalTime() + " ");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class BaseLayer extends Layer {
|
||||
protected Map<Layer.Priority, Layer> compositeLayers = Maps.newLinkedHashMap();
|
||||
protected Layer.Priority baseLayerPriority;
|
||||
|
||||
public BaseLayer() {
|
||||
this(AnimationPlayer::new);
|
||||
}
|
||||
|
||||
public BaseLayer(Supplier<AnimationPlayer> animationPlayerProvider) {
|
||||
super(null, animationPlayerProvider);
|
||||
|
||||
for (Priority priority : Priority.values()) {
|
||||
this.compositeLayers.computeIfAbsent(priority, Layer::new);
|
||||
}
|
||||
|
||||
this.baseLayerPriority = Priority.LOWEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, float transitionTimeModifier) {
|
||||
this.offCompositeLayersLowerThan(entitypatch, nextAnimation);
|
||||
super.playAnimation(nextAnimation, entitypatch, transitionTimeModifier);
|
||||
this.baseLayerPriority = nextAnimation.get().getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playLivingAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
|
||||
if (!this.animationPlayer.isEnd()) {
|
||||
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
|
||||
}
|
||||
|
||||
this.resume();
|
||||
nextAnimation.get().begin(entitypatch);
|
||||
|
||||
if (!nextAnimation.get().isMetaAnimation()) {
|
||||
this.concurrentLinkAnimation.acceptFrom(this.animationPlayer.getRealAnimation(), nextAnimation, this.animationPlayer.getElapsedTime());
|
||||
this.concurrentLinkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
|
||||
entitypatch.updateEntityState();
|
||||
this.nextAnimation = nextAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(LivingEntityPatch<?> entitypatch) {
|
||||
super.update(entitypatch);
|
||||
|
||||
for (Layer layer : this.compositeLayers.values()) {
|
||||
layer.update(entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void offCompositeLayersLowerThan(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> nextAnimation) {
|
||||
Priority[] layersToOff = nextAnimation.get().isMainFrameAnimation() ? nextAnimation.get().getPriority().lowersAndEqual() : nextAnimation.get().getPriority().lowers();
|
||||
|
||||
for (Priority p : layersToOff) {
|
||||
this.compositeLayers.get(p).off(entitypatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableLayer(Priority priority) {
|
||||
this.compositeLayers.get(priority).disableLayer();
|
||||
}
|
||||
|
||||
public Layer getLayer(Priority priority) {
|
||||
return this.compositeLayers.get(priority);
|
||||
}
|
||||
|
||||
public Priority getBaseLayerPriority() {
|
||||
return this.baseLayerPriority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void off(LivingEntityPatch<?> entitypatch) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isBaseLayer() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public enum LayerType {
|
||||
BASE_LAYER, COMPOSITE_LAYER
|
||||
}
|
||||
|
||||
public enum Priority {
|
||||
/**
|
||||
* The common usage of each layer
|
||||
*
|
||||
* LOWEST: Most of living cycle animations. Also a default value for animations doesn't inherit {@link MainFrameAnimation.class}
|
||||
* LOW: A few {@link ActionAnimation.class} that allows showing living cycle animations. e.g. step
|
||||
* MIDDLE: Most of composite living cycle animations. e.g. weapon holding animations
|
||||
* HIGH: A few composite animations that doesn't repeat. e.g. Uchigatana sheathing, Shield hit
|
||||
* HIGHEST: Most of {@link MainFrameAnimation.class} and a few living cycle animations. e.g. ladder animation
|
||||
**/
|
||||
LOWEST, LOW, MIDDLE, HIGH, HIGHEST;
|
||||
|
||||
public Priority[] lowers() {
|
||||
return Arrays.copyOfRange(Priority.values(), 0, this.ordinal());
|
||||
}
|
||||
|
||||
public Priority[] lowersAndEqual() {
|
||||
return Arrays.copyOfRange(Priority.values(), 0, this.ordinal() + 1);
|
||||
}
|
||||
|
||||
public Priority[] highers() {
|
||||
return Arrays.copyOfRange(Priority.values(), this.ordinal(), Priority.values().length);
|
||||
}
|
||||
|
||||
public boolean isHigherThan(Priority priority) {
|
||||
return this.ordinal() > priority.ordinal();
|
||||
}
|
||||
|
||||
public boolean isHigherOrEqual(Priority priority) {
|
||||
return this.ordinal() >= priority.ordinal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
|
||||
public class ClientAnimationProperties {
|
||||
/**
|
||||
* Layer type. (BASE: Living, attack animations, COMPOSITE: Aiming, weapon holding, digging animation)
|
||||
*/
|
||||
public static final StaticAnimationProperty<Layer.LayerType> LAYER_TYPE = new StaticAnimationProperty<Layer.LayerType> ();
|
||||
|
||||
/**
|
||||
* Priority of composite layer.
|
||||
*/
|
||||
public static final StaticAnimationProperty<Layer.Priority> PRIORITY = new StaticAnimationProperty<Layer.Priority> ();
|
||||
|
||||
/**
|
||||
* Joint mask for composite layer.
|
||||
*/
|
||||
public static final StaticAnimationProperty<JointMaskEntry> JOINT_MASK = new StaticAnimationProperty<JointMaskEntry> ();
|
||||
|
||||
/**
|
||||
* Trail particle information
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<TrailInfo>> TRAIL_EFFECT = new StaticAnimationProperty<List<TrailInfo>> ();
|
||||
|
||||
/**
|
||||
* An animation clip being played in first person.
|
||||
*/
|
||||
public static final StaticAnimationProperty<DirectStaticAnimation> POV_ANIMATION = new StaticAnimationProperty<DirectStaticAnimation> ();
|
||||
|
||||
/**
|
||||
* An animation clip being played in first person.
|
||||
*/
|
||||
public static final StaticAnimationProperty<AnimationSubFileReader.PovSettings> POV_SETTINGS = new StaticAnimationProperty<AnimationSubFileReader.PovSettings> ();
|
||||
|
||||
/**
|
||||
* Multilayer for living animations (e.g. Greatsword holding animation should be played simultaneously with jumping animation)
|
||||
*/
|
||||
public static final StaticAnimationProperty<DirectStaticAnimation> MULTILAYER_ANIMATION = new StaticAnimationProperty<DirectStaticAnimation> ();
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class JointMask {
|
||||
@FunctionalInterface
|
||||
public interface BindModifier {
|
||||
public void modify(LivingEntityPatch<?> entitypatch, Pose baseLayerPose, Pose resultPose, LivingMotion livingMotion, JointMaskEntry wholeEntry, Layer.Priority priority, Joint joint, Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> poses);
|
||||
}
|
||||
|
||||
public static final BindModifier KEEP_CHILD_LOCROT = (entitypatch, baseLayerPose, result, livingMotion, wholeEntry, priority, joint, poses) -> {
|
||||
Pose currentPose = poses.get(priority).getSecond();
|
||||
JointTransform lowestTransform = baseLayerPose.orElseEmpty(joint.getName());
|
||||
JointTransform currentTransform = currentPose.orElseEmpty(joint.getName());
|
||||
result.orElseEmpty(joint.getName()).translation().y = lowestTransform.translation().y;
|
||||
|
||||
OpenMatrix4f lowestMatrix = lowestTransform.toMatrix();
|
||||
OpenMatrix4f currentMatrix = currentTransform.toMatrix();
|
||||
OpenMatrix4f currentToLowest = OpenMatrix4f.mul(OpenMatrix4f.invert(currentMatrix, null), lowestMatrix, null);
|
||||
|
||||
for (Joint subJoint : joint.getSubJoints()) {
|
||||
if (wholeEntry.isMasked(livingMotion, subJoint.getName())) {
|
||||
OpenMatrix4f lowestLocalTransform = OpenMatrix4f.mul(joint.getLocalTransform(), lowestMatrix, null);
|
||||
OpenMatrix4f currentLocalTransform = OpenMatrix4f.mul(joint.getLocalTransform(), currentMatrix, null);
|
||||
OpenMatrix4f childTransform = OpenMatrix4f.mul(subJoint.getLocalTransform(), result.orElseEmpty(subJoint.getName()).toMatrix(), null);
|
||||
OpenMatrix4f lowestFinal = OpenMatrix4f.mul(lowestLocalTransform, childTransform, null);
|
||||
OpenMatrix4f currentFinal = OpenMatrix4f.mul(currentLocalTransform, childTransform, null);
|
||||
Vec3f vec = new Vec3f((currentFinal.m30 - lowestFinal.m30) * 0.5F, currentFinal.m31 - lowestFinal.m31, currentFinal.m32 - lowestFinal.m32);
|
||||
JointTransform jt = result.orElseEmpty(subJoint.getName());
|
||||
jt.parent(JointTransform.translation(vec), OpenMatrix4f::mul);
|
||||
jt.jointLocal(JointTransform.fromMatrixWithoutScale(currentToLowest), OpenMatrix4f::mul);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static JointMask of(String jointName, BindModifier bindModifier) {
|
||||
return new JointMask(jointName, bindModifier);
|
||||
}
|
||||
|
||||
public static JointMask of(String jointName) {
|
||||
return new JointMask(jointName, null);
|
||||
}
|
||||
|
||||
private final String jointName;
|
||||
private final BindModifier bindModifier;
|
||||
|
||||
private JointMask(String jointName, BindModifier bindModifier) {
|
||||
this.jointName = jointName;
|
||||
this.bindModifier = bindModifier;
|
||||
}
|
||||
|
||||
public static class JointMaskSet {
|
||||
final Map<String, BindModifier> masks = Maps.newHashMap();
|
||||
|
||||
public boolean contains(String name) {
|
||||
return this.masks.containsKey(name);
|
||||
}
|
||||
|
||||
public BindModifier getBindModifier(String jointName) {
|
||||
return this.masks.get(jointName);
|
||||
}
|
||||
|
||||
public static JointMaskSet of(JointMask... masks) {
|
||||
JointMaskSet jointMaskSet = new JointMaskSet();
|
||||
|
||||
for (JointMask jointMask : masks) {
|
||||
jointMaskSet.masks.put(jointMask.jointName, jointMask.bindModifier);
|
||||
}
|
||||
|
||||
return jointMaskSet;
|
||||
}
|
||||
|
||||
public static JointMaskSet of(Set<JointMask> jointMasks) {
|
||||
JointMaskSet jointMaskSet = new JointMaskSet();
|
||||
|
||||
for (JointMask jointMask : jointMasks) {
|
||||
jointMaskSet.masks.put(jointMask.jointName, jointMask.bindModifier);
|
||||
}
|
||||
|
||||
return jointMaskSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
|
||||
|
||||
public class JointMaskEntry {
|
||||
public static final JointMaskSet BIPED_UPPER_JOINTS_WITH_ROOT = JointMaskSet.of(
|
||||
JointMask.of("Root", JointMask.KEEP_CHILD_LOCROT), JointMask.of("Torso"),
|
||||
JointMask.of("Chest"), JointMask.of("Head"),
|
||||
JointMask.of("Shoulder_R"), JointMask.of("Arm_R"),
|
||||
JointMask.of("Hand_R"), JointMask.of("Elbow_R"),
|
||||
JointMask.of("Tool_R"), JointMask.of("Shoulder_L"),
|
||||
JointMask.of("Arm_L"), JointMask.of("Hand_L"),
|
||||
JointMask.of("Elbow_L"), JointMask.of("Tool_L")
|
||||
);
|
||||
|
||||
public static final JointMaskEntry BASIC_ATTACK_MASK = JointMaskEntry.builder().defaultMask(JointMaskEntry.BIPED_UPPER_JOINTS_WITH_ROOT).create();
|
||||
|
||||
private final Map<LivingMotion, JointMaskSet> masks = Maps.newHashMap();
|
||||
private final JointMaskSet defaultMask;
|
||||
|
||||
public JointMaskEntry(JointMaskSet defaultMask, List<Pair<LivingMotion, JointMaskSet>> masks) {
|
||||
this.defaultMask = defaultMask;
|
||||
|
||||
for (Pair<LivingMotion, JointMaskSet> mask : masks) {
|
||||
this.masks.put(mask.getLeft(), mask.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
public JointMaskSet getMask(LivingMotion livingmotion) {
|
||||
return this.masks.getOrDefault(livingmotion, this.defaultMask);
|
||||
}
|
||||
|
||||
public boolean isMasked(LivingMotion livingmotion, String jointName) {
|
||||
return !this.masks.getOrDefault(livingmotion, this.defaultMask).contains(jointName);
|
||||
}
|
||||
|
||||
public Set<Map.Entry<LivingMotion, JointMaskSet>> getEntries() {
|
||||
return this.masks.entrySet();
|
||||
}
|
||||
|
||||
public JointMaskSet getDefaultMask() {
|
||||
return this.defaultMask;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.defaultMask != null;
|
||||
}
|
||||
|
||||
public static JointMaskEntry.Builder builder() {
|
||||
return new JointMaskEntry.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (Map.Entry<LivingMotion, JointMaskSet> entry : this.masks.entrySet()) {
|
||||
builder.append(entry.getKey() + ": ");
|
||||
builder.append(JointMaskReloadListener.getKey(entry.getValue()) + ", ");
|
||||
}
|
||||
|
||||
ResourceLocation maskKey = JointMaskReloadListener.getKey(this.defaultMask);
|
||||
|
||||
if (maskKey == null) {
|
||||
builder.append("default: custom");
|
||||
} else {
|
||||
builder.append("default: ");
|
||||
builder.append(JointMaskReloadListener.getKey(this.defaultMask));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final List<Pair<LivingMotion, JointMaskSet>> masks = Lists.newArrayList();
|
||||
private JointMaskSet defaultMask = null;
|
||||
|
||||
public JointMaskEntry.Builder mask(LivingMotion motion, JointMaskSet masks) {
|
||||
this.masks.add(Pair.of(motion, masks));
|
||||
return this;
|
||||
}
|
||||
|
||||
public JointMaskEntry.Builder defaultMask(JointMaskSet masks) {
|
||||
this.defaultMask = masks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JointMaskEntry create() {
|
||||
return new JointMaskEntry(this.defaultMask, this.masks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMask.BindModifier;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class JointMaskReloadListener extends SimpleJsonResourceReloadListener {
|
||||
private static final BiMap<ResourceLocation, JointMaskSet> JOINT_MASKS = HashBiMap.create();
|
||||
private static final Map<String, JointMask.BindModifier> BIND_MODIFIERS = Maps.newHashMap();
|
||||
private static final ResourceLocation NONE_MASK = TiedUpRigConstants.identifier("none");
|
||||
|
||||
static {
|
||||
BIND_MODIFIERS.put("keep_child_locrot", JointMask.KEEP_CHILD_LOCROT);
|
||||
}
|
||||
|
||||
public static JointMaskSet getJointMaskEntry(String type) {
|
||||
ResourceLocation rl = ResourceLocation.parse(type);
|
||||
return JOINT_MASKS.getOrDefault(rl, JOINT_MASKS.get(NONE_MASK));
|
||||
}
|
||||
|
||||
public static JointMaskSet getNoneMask() {
|
||||
return JOINT_MASKS.get(NONE_MASK);
|
||||
}
|
||||
|
||||
public static ResourceLocation getKey(JointMaskSet type) {
|
||||
return JOINT_MASKS.inverse().get(type);
|
||||
}
|
||||
|
||||
public static Set<Map.Entry<ResourceLocation, JointMaskSet>> entries() {
|
||||
return JOINT_MASKS.entrySet();
|
||||
}
|
||||
|
||||
public JointMaskReloadListener() {
|
||||
super((new GsonBuilder()).create(), "animmodels/joint_mask");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void apply(Map<ResourceLocation, JsonElement> objectIn, ResourceManager resourceManager, ProfilerFiller profileFiller) {
|
||||
JOINT_MASKS.clear();
|
||||
|
||||
for (Map.Entry<ResourceLocation, JsonElement> entry : objectIn.entrySet()) {
|
||||
Set<JointMask> masks = Sets.newHashSet();
|
||||
JsonObject object = entry.getValue().getAsJsonObject();
|
||||
JsonArray joints = object.getAsJsonArray("joints");
|
||||
JsonObject bindModifiers = object.has("bind_modifiers") ? object.getAsJsonObject("bind_modifiers") : null;
|
||||
|
||||
for (JsonElement joint : joints) {
|
||||
String jointName = joint.getAsString();
|
||||
BindModifier modifier = null;
|
||||
|
||||
if (bindModifiers != null) {
|
||||
String modifierName = bindModifiers.has(jointName) ? bindModifiers.get(jointName).getAsString() : null;
|
||||
modifier = BIND_MODIFIERS.get(modifierName);
|
||||
}
|
||||
|
||||
masks.add(JointMask.of(jointName, modifier));
|
||||
}
|
||||
|
||||
String path = entry.getKey().toString();
|
||||
ResourceLocation key = ResourceLocation.fromNamespaceAndPath(entry.getKey().getNamespace(), path.substring(path.lastIndexOf("/") + 1));
|
||||
|
||||
JOINT_MASKS.put(key, JointMaskSet.of(masks));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
|
||||
public class LayerInfo {
|
||||
public final JointMaskEntry jointMaskEntry;
|
||||
public final Layer.Priority priority;
|
||||
public final Layer.LayerType layerType;
|
||||
|
||||
public LayerInfo(JointMaskEntry jointMaskEntry, Layer.Priority priority, Layer.LayerType layerType) {
|
||||
this.jointMaskEntry = jointMaskEntry;
|
||||
this.priority = priority;
|
||||
this.layerType = layerType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.client.property;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import net.minecraft.core.particles.SimpleParticleType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* Stub RIG Phase 0 — combat weapon particle trail. Pas utilisé dans TiedUp
|
||||
* (bondage, pas d'armes actives), mais on garde l'API typée pour JSON compat.
|
||||
* {@code deserialize} retourne toujours un trail neutre non-playable, donc
|
||||
* le block dans StaticAnimation est court-circuité (voir {@link #playable()}).
|
||||
*/
|
||||
public record TrailInfo(String joint, SimpleParticleType particle, boolean playable) {
|
||||
public static TrailInfo deserialize(JsonElement element) {
|
||||
return new TrailInfo("", null, false);
|
||||
}
|
||||
|
||||
public Vec3 start() { return Vec3.ZERO; }
|
||||
public Vec3 end() { return Vec3.ZERO; }
|
||||
public float startTime() { return 0.0F; }
|
||||
public float endTime() { return 0.0F; }
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.property;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public abstract class AnimationEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, T extends AnimationEvent<EVENT, T>> {
|
||||
protected final AnimationEvent.Side side;
|
||||
protected final EVENT event;
|
||||
protected AnimationParameters params;
|
||||
|
||||
private AnimationEvent(AnimationEvent.Side executionSide, EVENT event) {
|
||||
this.side = executionSide;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
protected abstract boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed);
|
||||
|
||||
public void execute(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
|
||||
if (this.side.predicate.test(entitypatch.getOriginal()) && this.checkCondition(entitypatch, animation, prevElapsed, elapsed)) {
|
||||
this.event.fire(entitypatch, animation, this.params);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeWithNewParams(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed, AnimationParameters parameters) {
|
||||
if (this.side.predicate.test(entitypatch.getOriginal()) && this.checkCondition(entitypatch, animation, prevElapsed, elapsed)) {
|
||||
this.event.fire(entitypatch, animation, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, SimpleEvent<EVENT>> {
|
||||
private SimpleEvent(AnimationEvent.Side executionSide, EVENT event) {
|
||||
super(executionSide, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> SimpleEvent<E> create(E event, AnimationEvent.Side isRemote) {
|
||||
return new SimpleEvent<> (isRemote, event);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InTimeEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, InTimeEvent<EVENT>> implements Comparable<InTimeEvent<EVENT>> {
|
||||
final float time;
|
||||
|
||||
private InTimeEvent(float time, AnimationEvent.Side executionSide, EVENT event) {
|
||||
super(executionSide, event);
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
|
||||
return this.time >= prevElapsed && this.time < elapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(InTimeEvent<EVENT> arg0) {
|
||||
if(this.time == arg0.time) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.time > arg0.time ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> InTimeEvent<E> create(float time, E event, AnimationEvent.Side isRemote) {
|
||||
return new InTimeEvent<> (time, isRemote, event);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InPeriodEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, InPeriodEvent<EVENT>> implements Comparable<InPeriodEvent<EVENT>> {
|
||||
final float start;
|
||||
final float end;
|
||||
|
||||
private InPeriodEvent(float start, float end, AnimationEvent.Side executionSide, EVENT event) {
|
||||
super(executionSide, event);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
|
||||
return this.start <= elapsed && this.end > elapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(InPeriodEvent<EVENT> arg0) {
|
||||
if (this.start == arg0.start) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.start > arg0.start ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> InPeriodEvent<E> create(float start, float end, E event, AnimationEvent.Side isRemote) {
|
||||
return new InPeriodEvent<> (start, end, isRemote, event);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Side {
|
||||
CLIENT((entity) -> entity.level().isClientSide),
|
||||
SERVER((entity) -> !entity.level().isClientSide), BOTH((entity) -> true),
|
||||
LOCAL_CLIENT((entity) -> {
|
||||
if (entity instanceof Player player) {
|
||||
return player.isLocalPlayer();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
Predicate<Entity> predicate;
|
||||
|
||||
Side(Predicate<Entity> predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationParameters<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> getParameters() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
public <A> T params(A p1) {
|
||||
this.params = AnimationParameters.of(p1);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B> T params(A p1, B p2) {
|
||||
this.params = AnimationParameters.of(p1, p2);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C> T params(A p1, B p2, C p3) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D> T params(A p1, B p2, C p3, D p4) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E> T params(A p1, B p2, C p3, D p4, E p5) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E, F> T params(A p1, B p2, C p3, D p4, E p5, F p6) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E, F, G> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E, F, G, H> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E, F, G, H, I> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8, I p9) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8, p9);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public <A, B, C, D, E, F, G, H, I, J> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8, I p9, J p10) {
|
||||
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Event<A, B, C, D, E, F, G, H, I, J> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, J> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E0 extends Event<Void, Void, Void, Void, Void, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<Void, Void, Void, Void, Void, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E1<A> extends Event<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E2<A, B> extends Event<A, B, Void, Void, Void, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, Void, Void, Void, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E3<A, B, C> extends Event<A, B, C, Void, Void, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, Void, Void, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E4<A, B, C, D> extends Event<A, B, C, D, Void, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, Void, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E5<A, B, C, D, E> extends Event<A, B, C, D, E, Void, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, Void, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E6<A, B, C, D, E, F> extends Event<A, B, C, D, E, F, Void, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, Void, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E7<A, B, C, D, E, F, G> extends Event<A, B, C, D, E, F, G, Void, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, Void, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E8<A, B, C, D, E, F, G, H> extends Event<A, B, C, D, E, F, G, H, Void, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, Void, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E9<A, B, C, D, E, F, G, H, I> extends Event<A, B, C, D, E, F, G, H, I, Void> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, Void> params);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface E10<A, B, C, D, E, F, G, H, I, J> extends Event<A, B, C, D, E, F, G, H, I, J> {
|
||||
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, J> params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.property;
|
||||
|
||||
public record AnimationParameters<A, B, C, D, E, F, G, H, I, J> (
|
||||
A first,
|
||||
B second,
|
||||
C third,
|
||||
D fourth,
|
||||
E fifth,
|
||||
F sixth,
|
||||
G seventh,
|
||||
H eighth,
|
||||
I ninth,
|
||||
J tenth
|
||||
) {
|
||||
public static <A> AnimationParameters<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> of(A first) {
|
||||
return new AnimationParameters<> (first, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B> AnimationParameters<A, B, Void, Void, Void, Void, Void, Void, Void, Void> of(A first, B second) {
|
||||
return new AnimationParameters<> (first, second, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C> AnimationParameters<A, B, C, Void, Void, Void, Void, Void, Void, Void> of(A first, B second, C third) {
|
||||
return new AnimationParameters<> (first, second, third, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D> AnimationParameters<A, B, C, D, Void, Void, Void, Void, Void, Void> of(A first, B second, C third, D fourth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E> AnimationParameters<A, B, C, D, E, Void, Void, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F> AnimationParameters<A, B, C, D, E, F, Void, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, (Void)null, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F, G> AnimationParameters<A, B, C, D, E, F, G, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, (Void)null, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F, G, H> AnimationParameters<A, B, C, D, E, F, G, H, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, (Void)null, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F, G, H, I> AnimationParameters<A, B, C, D, E, F, G, H, I, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth, I ninth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, (Void)null);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F, G, H, I, J> AnimationParameters<A, B, C, D, E, F, G, H, I, J> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth, I ninth, J tenth) {
|
||||
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth);
|
||||
}
|
||||
|
||||
public static <A, B, C, D, E, F, G, H, I, J, N> AnimationParameters<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> addParameter(AnimationParameters<A, B, C, D, E, F, G, H, I, J> parameters, N newParam) {
|
||||
if (parameters.first() == null) {
|
||||
return new AnimationParameters<N, Void, Void, Void, Void, Void, Void, Void, Void, Void> (newParam, null, null, null, null, null, null, null, null, null);
|
||||
} else if (parameters.second() == null) {
|
||||
return new AnimationParameters<A, N, Void, Void, Void, Void, Void, Void, Void, Void> (parameters.first(), newParam, null, null, null, null, null, null, null, null);
|
||||
} else if (parameters.third() == null) {
|
||||
return new AnimationParameters<A, B, N, Void, Void, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), newParam, null, null, null, null, null, null, null);
|
||||
} else if (parameters.fourth() == null) {
|
||||
return new AnimationParameters<A, B, C, N, Void, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), newParam, null, null, null, null, null, null);
|
||||
} else if (parameters.fifth() == null) {
|
||||
return new AnimationParameters<A, B, C, D, N, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), newParam, null, null, null, null, null);
|
||||
} else if (parameters.sixth() == null) {
|
||||
return new AnimationParameters<A, B, C, D, E, N, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), newParam, null, null, null, null);
|
||||
} else if (parameters.seventh() == null) {
|
||||
return new AnimationParameters<A, B, C, D, E, F, N, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), newParam, null, null, null);
|
||||
} else if (parameters.eighth() == null) {
|
||||
return new AnimationParameters<A, B, C, D, E, F, G, N, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), newParam, null, null);
|
||||
} else if (parameters.ninth() == null) {
|
||||
return new AnimationParameters<A, B, C, D, E, F, G, H, N, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), parameters.eighth(), newParam, null);
|
||||
} else if (parameters.tenth() == null) {
|
||||
return new AnimationParameters<A, B, C, D, E, F, G, H, I, N> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), parameters.eighth(), parameters.ninth(), newParam);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Parameters are full!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.property;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.damagesource.DamageType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.LivingMotion;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationEvent.SimpleEvent;
|
||||
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordGetter;
|
||||
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordSetter;
|
||||
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.LinkAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulator.BakedInverseKinematicsDefinition;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulator.InverseKinematicsDefinition;
|
||||
import com.tiedup.remake.rig.util.TimePairList;
|
||||
import com.tiedup.remake.rig.math.ValueModifier;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import com.tiedup.remake.rig.patch.item.CapabilityItem;
|
||||
|
||||
public abstract class AnimationProperty<T> {
|
||||
private static final Map<String, AnimationProperty<?>> SERIALIZABLE_ANIMATION_PROPERTY_KEYS = Maps.newHashMap();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> AnimationProperty<T> getSerializableProperty(String name) {
|
||||
if (!SERIALIZABLE_ANIMATION_PROPERTY_KEYS.containsKey(name)) {
|
||||
throw new IllegalStateException("No property key named " + name);
|
||||
}
|
||||
|
||||
return (AnimationProperty<T>) SERIALIZABLE_ANIMATION_PROPERTY_KEYS.get(name);
|
||||
}
|
||||
|
||||
private final Codec<T> codecs;
|
||||
private final String name;
|
||||
|
||||
public AnimationProperty(String name, @Nullable Codec<T> codecs) {
|
||||
this.codecs = codecs;
|
||||
this.name = name;
|
||||
|
||||
if (name != null) {
|
||||
if (SERIALIZABLE_ANIMATION_PROPERTY_KEYS.containsKey(name)) {
|
||||
throw new IllegalStateException("Animation property key " + name + " is already registered.");
|
||||
}
|
||||
|
||||
SERIALIZABLE_ANIMATION_PROPERTY_KEYS.put(name, this);
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationProperty(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public T parseFrom(JsonElement e) {
|
||||
return this.codecs.parse(JsonOps.INSTANCE, e).resultOrPartial((errm) -> TiedUpRigConstants.LOGGER.warn("Failed to parse property " + this.name + " because of " + errm)).orElseThrow();
|
||||
}
|
||||
|
||||
public Codec<T> getCodecs() {
|
||||
return this.codecs;
|
||||
}
|
||||
|
||||
public static class StaticAnimationProperty<T> extends AnimationProperty<T> {
|
||||
public StaticAnimationProperty(String rl, @Nullable Codec<T> codecs) {
|
||||
super(rl, codecs);
|
||||
}
|
||||
|
||||
public StaticAnimationProperty() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Events that are fired in every tick.
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<AnimationEvent<?, ?>>> TICK_EVENTS = new StaticAnimationProperty<List<AnimationEvent<?, ?>>> ();
|
||||
|
||||
/**
|
||||
* Events that are fired when the animation starts.
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<SimpleEvent<?>>> ON_BEGIN_EVENTS = new StaticAnimationProperty<List<SimpleEvent<?>>> ();
|
||||
|
||||
/**
|
||||
* Events that are fired when the animation ends.
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<SimpleEvent<?>>> ON_END_EVENTS = new StaticAnimationProperty<List<SimpleEvent<?>>> ();
|
||||
|
||||
/**
|
||||
* An event triggered when entity changes an item in hand.
|
||||
* Retenu comme hook datapack : un event écrit en JSON par un datapack tiers peut s'abonner
|
||||
* au changement d'item porté pour déclencher une réaction (voir Phase 3 data-driven anims).
|
||||
*/
|
||||
public static final StaticAnimationProperty<SimpleEvent<AnimationEvent.E2<CapabilityItem, CapabilityItem>>> ON_ITEM_CHANGE_EVENT = new StaticAnimationProperty<SimpleEvent<AnimationEvent.E2<CapabilityItem, CapabilityItem>>> ();
|
||||
|
||||
/**
|
||||
* You can modify the playback speed of the animation.
|
||||
*/
|
||||
public static final StaticAnimationProperty<PlaybackSpeedModifier> PLAY_SPEED_MODIFIER = new StaticAnimationProperty<PlaybackSpeedModifier> ();
|
||||
|
||||
/**
|
||||
* You can modify the playback speed of the animation.
|
||||
*/
|
||||
public static final StaticAnimationProperty<PlaybackTimeModifier> ELAPSED_TIME_MODIFIER = new StaticAnimationProperty<PlaybackTimeModifier> ();
|
||||
|
||||
/**
|
||||
* This property will be called both in client and server when modifying the pose
|
||||
*/
|
||||
public static final StaticAnimationProperty<PoseModifier> POSE_MODIFIER = new StaticAnimationProperty<PoseModifier> ();
|
||||
|
||||
/**
|
||||
* Fix the head rotation to the player's body rotation
|
||||
*/
|
||||
public static final StaticAnimationProperty<Boolean> FIXED_HEAD_ROTATION = new StaticAnimationProperty<Boolean> ();
|
||||
|
||||
/**
|
||||
* Defines static animations as link animation when the animation is followed by a specific animation
|
||||
*/
|
||||
public static final StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> TRANSITION_ANIMATIONS_FROM = new StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> ();
|
||||
|
||||
/**
|
||||
* Defines static animations as link animation when the animation is following a specific animation
|
||||
*/
|
||||
public static final StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> TRANSITION_ANIMATIONS_TO = new StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> ();
|
||||
|
||||
/**
|
||||
* Disable physics while playing animation
|
||||
*/
|
||||
public static final StaticAnimationProperty<Boolean> NO_PHYSICS = new StaticAnimationProperty<Boolean> ("no_physics", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* Inverse kinematics information
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<InverseKinematicsDefinition>> IK_DEFINITION = new StaticAnimationProperty<List<InverseKinematicsDefinition>> ();
|
||||
|
||||
/**
|
||||
* This property automatically baked when animation is loaded
|
||||
*/
|
||||
public static final StaticAnimationProperty<List<BakedInverseKinematicsDefinition>> BAKED_IK_DEFINITION = new StaticAnimationProperty<List<BakedInverseKinematicsDefinition>> ();
|
||||
|
||||
/**
|
||||
* This property reset the entity's living motion
|
||||
*/
|
||||
public static final StaticAnimationProperty<LivingMotion> RESET_LIVING_MOTION = new StaticAnimationProperty<LivingMotion> ();
|
||||
}
|
||||
|
||||
public static class ActionAnimationProperty<T> extends StaticAnimationProperty<T> {
|
||||
public ActionAnimationProperty(String rl, @Nullable Codec<T> codecs) {
|
||||
super(rl, codecs);
|
||||
}
|
||||
|
||||
public ActionAnimationProperty() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This property will set the entity's delta movement to (0, 0, 0) at the beginning of an animation if true.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> STOP_MOVEMENT = new ActionAnimationProperty<Boolean> ("stop_movements", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property will set the entity's delta movement to (0, 0, 0) at the beginning of an animation if true.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> REMOVE_DELTA_MOVEMENT = new ActionAnimationProperty<Boolean> ("revmoe_delta_move", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property will move entity's coord also as y axis if true.
|
||||
* Don't recommend using this property because it's old system. Use the coord joint instead.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> MOVE_VERTICAL = new ActionAnimationProperty<Boolean> ("move_vertically", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property determines the time of entity not affected by gravity.
|
||||
*/
|
||||
public static final ActionAnimationProperty<TimePairList> NO_GRAVITY_TIME = new ActionAnimationProperty<TimePairList> ();
|
||||
|
||||
/**
|
||||
* Coord of action animation
|
||||
*/
|
||||
public static final ActionAnimationProperty<TransformSheet> COORD = new ActionAnimationProperty<TransformSheet> ();
|
||||
|
||||
/**
|
||||
* This property determines whether to move the entity in link animation or not.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> MOVE_ON_LINK = new ActionAnimationProperty<Boolean> ("move_during_link", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* You can specify the coord movement time in action animation. Must be registered in order of time.
|
||||
*/
|
||||
public static final ActionAnimationProperty<TimePairList> MOVE_TIME = new ActionAnimationProperty<TimePairList> ();
|
||||
|
||||
/**
|
||||
* Set the dynamic coordinates of {@link ActionAnimation}. Called before creation of {@link LinkAnimation}.
|
||||
*/
|
||||
public static final ActionAnimationProperty<MoveCoordSetter> COORD_SET_BEGIN = new ActionAnimationProperty<MoveCoordSetter> ();
|
||||
|
||||
/**
|
||||
* Set the dynamic coordinates of {@link ActionAnimation}.
|
||||
*/
|
||||
public static final ActionAnimationProperty<MoveCoordSetter> COORD_SET_TICK = new ActionAnimationProperty<MoveCoordSetter> ();
|
||||
|
||||
/**
|
||||
* Set the coordinates of action animation.
|
||||
*/
|
||||
public static final ActionAnimationProperty<MoveCoordGetter> COORD_GET = new ActionAnimationProperty<MoveCoordGetter> ();
|
||||
|
||||
/**
|
||||
* This property determines if the speed effect will increase the move distance.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> AFFECT_SPEED = new ActionAnimationProperty<Boolean> ("move_speed_based_distance", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property determines if the movement can be canceled by {@link LivingEntityPatch#shouldBlockMoving()}.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> CANCELABLE_MOVE = new ActionAnimationProperty<Boolean> ("cancellable_movement", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* Death animations won't be played if this value is true
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> IS_DEATH_ANIMATION = new ActionAnimationProperty<Boolean> ("is_death", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property determines the update time of {@link ActionAnimationProperty#COORD_SET_TICK}. If the current time out of the bound it uses {@link MoveCoordFunctions#RAW_COORD and MoveCoordFunctions#DIFF_FROM_PREV_COORD}}
|
||||
*/
|
||||
public static final ActionAnimationProperty<TimePairList> COORD_UPDATE_TIME = new ActionAnimationProperty<TimePairList> ();
|
||||
|
||||
/**
|
||||
* This property determines if it reset the player basic attack combo counter or not.
|
||||
* RIG : BasicAttack strippé, flag conservé pour compat JSON.
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> RESET_PLAYER_COMBO_COUNTER = new ActionAnimationProperty<Boolean> ("reset_combo_attack_counter", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* Provide destination of action animation {@link MoveCoordFunctions}
|
||||
*/
|
||||
public static final ActionAnimationProperty<DestLocationProvider> DEST_LOCATION_PROVIDER = new ActionAnimationProperty<DestLocationProvider> ();
|
||||
|
||||
/**
|
||||
* Provide y rotation of entity {@link MoveCoordFunctions}
|
||||
*/
|
||||
public static final ActionAnimationProperty<YRotProvider> ENTITY_YROT_PROVIDER = new ActionAnimationProperty<YRotProvider> ();
|
||||
|
||||
/**
|
||||
* Provide y rotation of tracing coord {@link MoveCoordFunctions}
|
||||
*/
|
||||
public static final ActionAnimationProperty<YRotProvider> DEST_COORD_YROT_PROVIDER = new ActionAnimationProperty<YRotProvider> ();
|
||||
|
||||
/**
|
||||
* Decides the index of start key frame for coord transform, See also with {@link MoveCoordFunctions#TRACE_ORIGIN_AS_DESTINATION}
|
||||
*/
|
||||
public static final ActionAnimationProperty<Integer> COORD_START_KEYFRAME_INDEX = new ActionAnimationProperty<Integer> ();
|
||||
|
||||
/**
|
||||
* Decides the index of destination key frame for coord transform, See also with {@link MoveCoordFunctions#TRACE_ORIGIN_AS_DESTINATION}
|
||||
*/
|
||||
public static final ActionAnimationProperty<Integer> COORD_DEST_KEYFRAME_INDEX = new ActionAnimationProperty<Integer> ();
|
||||
|
||||
/**
|
||||
* Determines if an entity should look where a camera is looking at the beginning of an animation (player only)
|
||||
*/
|
||||
public static final ActionAnimationProperty<Boolean> SYNC_CAMERA = new ActionAnimationProperty<Boolean> ("sync_camera", Codec.BOOL);
|
||||
}
|
||||
|
||||
public static class AttackAnimationProperty<T> extends ActionAnimationProperty<T> {
|
||||
public AttackAnimationProperty(String rl, @Nullable Codec<T> codecs) {
|
||||
super(rl, codecs);
|
||||
}
|
||||
|
||||
public AttackAnimationProperty() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This property determines if the animation has a fixed amount of move distance not depending on the distance between attacker and target entity
|
||||
*/
|
||||
public static final AttackAnimationProperty<Boolean> FIXED_MOVE_DISTANCE = new AttackAnimationProperty<Boolean> ("fixed_movement_distance", Codec.BOOL);
|
||||
|
||||
/**
|
||||
* This property determines how much the playback speed will be affected by entity's attack speed.
|
||||
*/
|
||||
public static final AttackAnimationProperty<Float> ATTACK_SPEED_FACTOR = new AttackAnimationProperty<Float> ("attack_speed_factor", Codec.FLOAT);
|
||||
|
||||
/**
|
||||
* This property determines the basis of the speed factor. Default basis is the total animation time.
|
||||
*/
|
||||
public static final AttackAnimationProperty<Float> BASIS_ATTACK_SPEED = new AttackAnimationProperty<Float> ("basis_attack_speed", Codec.FLOAT);
|
||||
|
||||
/**
|
||||
* This property adds interpolated colliders when detecting colliding entities by using @MultiCollider.
|
||||
*/
|
||||
public static final AttackAnimationProperty<Integer> EXTRA_COLLIDERS = new AttackAnimationProperty<Integer> ("extra_colliders", Codec.INT);
|
||||
|
||||
/**
|
||||
* This property determines a minimal distance between attacker and target.
|
||||
*/
|
||||
public static final AttackAnimationProperty<Float> REACH = new AttackAnimationProperty<Float> ("reach", Codec.FLOAT);
|
||||
}
|
||||
|
||||
public static class AttackPhaseProperty<T> {
|
||||
public AttackPhaseProperty(String rl, @Nullable Codec<? extends T> codecs) {
|
||||
//super(rl, codecs);
|
||||
}
|
||||
|
||||
public AttackPhaseProperty() {
|
||||
//this(null, null);
|
||||
}
|
||||
|
||||
public static final AttackPhaseProperty<ValueModifier> MAX_STRIKES_MODIFIER = new AttackPhaseProperty<ValueModifier> ("max_strikes", ValueModifier.CODEC);
|
||||
public static final AttackPhaseProperty<ValueModifier> DAMAGE_MODIFIER = new AttackPhaseProperty<ValueModifier> ("damage", ValueModifier.CODEC);
|
||||
public static final AttackPhaseProperty<ValueModifier> ARMOR_NEGATION_MODIFIER = new AttackPhaseProperty<ValueModifier> ("armor_negation", ValueModifier.CODEC);
|
||||
public static final AttackPhaseProperty<ValueModifier> IMPACT_MODIFIER = new AttackPhaseProperty<ValueModifier> ("impact", ValueModifier.CODEC);
|
||||
// RIG : EXTRA_DAMAGE, STUN_TYPE, PARTICLE strippés (combat).
|
||||
public static final AttackPhaseProperty<SoundEvent> SWING_SOUND = new AttackPhaseProperty<SoundEvent> ();
|
||||
public static final AttackPhaseProperty<SoundEvent> HIT_SOUND = new AttackPhaseProperty<SoundEvent> ();
|
||||
public static final AttackPhaseProperty<Set<TagKey<DamageType>>> SOURCE_TAG = new AttackPhaseProperty<Set<TagKey<DamageType>>> ();
|
||||
public static final AttackPhaseProperty<Function<LivingEntityPatch<?>, Vec3>> SOURCE_LOCATION_PROVIDER = new AttackPhaseProperty<Function<LivingEntityPatch<?>, Vec3>> ();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Registerer<T> {
|
||||
void register(Map<AnimationProperty<T>, Object> properties, AnimationProperty<T> key, T object);
|
||||
}
|
||||
|
||||
/******************************
|
||||
* Static Animation Properties
|
||||
******************************/
|
||||
/**
|
||||
* elapsedTime contains partial tick
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PoseModifier {
|
||||
void modify(DynamicAnimation self, Pose pose, LivingEntityPatch<?> entitypatch, float elapsedTime, float partialTick);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PlaybackSpeedModifier {
|
||||
float modify(DynamicAnimation self, LivingEntityPatch<?> entitypatch, float speed, float prevElapsedTime, float elapsedTime);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PlaybackTimeModifier {
|
||||
Pair<Float, Float> modify(DynamicAnimation self, LivingEntityPatch<?> entitypatch, float speed, float prevElapsedTime, float elapsedTime);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DestLocationProvider {
|
||||
Vec3 get(DynamicAnimation self, LivingEntityPatch<?> entitypatch);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface YRotProvider {
|
||||
float get(DynamicAnimation self, LivingEntityPatch<?> entitypatch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.property;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.anim.AnimationPlayer;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Keyframe;
|
||||
import com.tiedup.remake.rig.anim.SynchedAnimationVariableKeys;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.AttackAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.DestLocationProvider;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.YRotProvider;
|
||||
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.AttackAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.AttackAnimation.Phase;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState;
|
||||
// RIG : GrapplingAttackAnimation strippé (combat grappling hook), ref javadoc conservée
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.math.Vec4f;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import com.tiedup.remake.rig.patch.MobPatch;
|
||||
|
||||
/**
|
||||
* Registry complet des constantes {@code MoveCoord*} consommées par les datapacks EF tiers
|
||||
* via réflection ({@code StaticFieldArgument}). Ne pas purger individuellement sans couper
|
||||
* le support datapack — l'absence d'un nom au runtime crash le chargement JSON.
|
||||
*/
|
||||
public class MoveCoordFunctions {
|
||||
/**
|
||||
* Defines a function that how to interpret given coordinate and return the movement vector from entity's current position
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MoveCoordGetter {
|
||||
Vec3f get(DynamicAnimation animation, LivingEntityPatch<?> entitypatch, TransformSheet transformSheet, float prevElapsedTime, float elapsedTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a function that how to build the coordinate of {@link ActionAnimation}
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MoveCoordSetter {
|
||||
void set(DynamicAnimation animation, LivingEntityPatch<?> entitypatch, TransformSheet transformSheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* MODEL_COORD
|
||||
* - Calculates the coordinate gap between previous and current elapsed time
|
||||
* - the coordinate doesn't reflect the entity's rotation
|
||||
*/
|
||||
public static final MoveCoordGetter MODEL_COORD = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
|
||||
LivingEntity livingentity = entitypatch.getOriginal();
|
||||
JointTransform oJt = coord.getInterpolatedTransform(prevElapsedTime);
|
||||
JointTransform jt = coord.getInterpolatedTransform(elapsedTime);
|
||||
Vec4f prevpos = new Vec4f(oJt.translation());
|
||||
Vec4f currentpos = new Vec4f(jt.translation());
|
||||
|
||||
OpenMatrix4f rotationTransform = entitypatch.getModelMatrix(1.0F).removeTranslation().removeScale();
|
||||
OpenMatrix4f localTransform = entitypatch.getArmature().searchJointByName("Root").getLocalTransform().removeTranslation();
|
||||
rotationTransform.mulBack(localTransform);
|
||||
currentpos.transform(rotationTransform);
|
||||
prevpos.transform(rotationTransform);
|
||||
|
||||
boolean hasNoGravity = entitypatch.getOriginal().isNoGravity();
|
||||
boolean moveVertical = animation.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false) || animation.getProperty(ActionAnimationProperty.COORD).isPresent();
|
||||
float dx = prevpos.x - currentpos.x;
|
||||
float dy = (moveVertical || hasNoGravity) ? currentpos.y - prevpos.y : 0.0F;
|
||||
float dz = prevpos.z - currentpos.z;
|
||||
dx = Math.abs(dx) > 0.0001F ? dx : 0.0F;
|
||||
dz = Math.abs(dz) > 0.0001F ? dz : 0.0F;
|
||||
|
||||
BlockPos blockpos = new BlockPos.MutableBlockPos(livingentity.getX(), livingentity.getBoundingBox().minY - 1.0D, livingentity.getZ());
|
||||
BlockState blockState = livingentity.level().getBlockState(blockpos);
|
||||
AttributeInstance movementSpeed = livingentity.getAttribute(Attributes.MOVEMENT_SPEED);
|
||||
boolean soulboost = blockState.is(BlockTags.SOUL_SPEED_BLOCKS) && EnchantmentHelper.getEnchantmentLevel(Enchantments.SOUL_SPEED, livingentity) > 0;
|
||||
float speedFactor = (float)(soulboost ? 1.0D : livingentity.level().getBlockState(blockpos).getBlock().getSpeedFactor());
|
||||
float moveMultiplier = (float)(animation.getProperty(ActionAnimationProperty.AFFECT_SPEED).orElse(false) ? (movementSpeed.getValue() / movementSpeed.getBaseValue()) : 1.0F);
|
||||
|
||||
return new Vec3f(dx * moveMultiplier * speedFactor, dy, dz * moveMultiplier * speedFactor);
|
||||
};
|
||||
|
||||
/**
|
||||
* WORLD_COORD
|
||||
* - Calculates the coordinate of current elapsed time
|
||||
* - the coordinate is the world position
|
||||
*/
|
||||
public static final MoveCoordGetter WORLD_COORD = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
|
||||
JointTransform jt = coord.getInterpolatedTransform(elapsedTime);
|
||||
Vec3 entityPos = entitypatch.getOriginal().position();
|
||||
|
||||
return jt.translation().copy().sub(Vec3f.fromDoubleVector(entityPos));
|
||||
};
|
||||
|
||||
/**
|
||||
* ATTACHED
|
||||
* Calculates the relative position of a grappling target entity.
|
||||
* - especially used by {@link GrapplingAttackAnimation}
|
||||
* - read by {@link MoveCoordFunctions#RAW_COORD}
|
||||
*/
|
||||
public static final MoveCoordGetter ATTACHED = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
|
||||
LivingEntity target = entitypatch.getGrapplingTarget();
|
||||
|
||||
if (target == null) {
|
||||
return MODEL_COORD.get(animation, entitypatch, coord, prevElapsedTime, elapsedTime);
|
||||
}
|
||||
|
||||
TransformSheet rootCoord = animation.getCoord();
|
||||
LivingEntity livingentity = entitypatch.getOriginal();
|
||||
Vec3f model = rootCoord.getInterpolatedTransform(elapsedTime).translation();
|
||||
Vec3f world = OpenMatrix4f.transform3v(OpenMatrix4f.createRotatorDeg(-target.getYRot(), Vec3f.Y_AXIS), model, null);
|
||||
Vec3f dst = Vec3f.fromDoubleVector(target.position()).add(world);
|
||||
entitypatch.setYRot(Mth.wrapDegrees(target.getYRot() + 180.0F));
|
||||
|
||||
return dst.sub(Vec3f.fromDoubleVector(livingentity.position()));
|
||||
};
|
||||
|
||||
/******************************************************
|
||||
* Action animation properties
|
||||
******************************************************/
|
||||
|
||||
/**
|
||||
* No destination
|
||||
*/
|
||||
public static final DestLocationProvider NO_DEST = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Location of the current attack target
|
||||
*/
|
||||
public static final DestLocationProvider ATTACK_TARGET_LOCATION = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
return entitypatch.getTarget() == null ? null : entitypatch.getTarget().position();
|
||||
};
|
||||
|
||||
/**
|
||||
* Location set by Animation Variable
|
||||
*/
|
||||
public static final DestLocationProvider SYNCHED_DEST_VARIABLE = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
return entitypatch.getAnimator().getVariables().getOrDefault(SynchedAnimationVariableKeys.DESTINATION.get(), self.getRealAnimation());
|
||||
};
|
||||
|
||||
/**
|
||||
* Location of current attack target that is provided by animation variable
|
||||
*/
|
||||
public static final DestLocationProvider SYNCHED_TARGET_ENTITY_LOCATION_VARIABLE = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
Optional<Integer> targetEntityId = entitypatch.getAnimator().getVariables().get(SynchedAnimationVariableKeys.TARGET_ENTITY.get(), self.getRealAnimation());
|
||||
|
||||
if (targetEntityId.isPresent()) {
|
||||
Entity entity = entitypatch.getOriginal().level().getEntity(targetEntityId.get());
|
||||
|
||||
if (entity != null) {
|
||||
return entity.position();
|
||||
}
|
||||
}
|
||||
|
||||
return entitypatch.getOriginal().position();
|
||||
};
|
||||
|
||||
/**
|
||||
* Looking direction from an action beginning location to a destination location
|
||||
*/
|
||||
public static final YRotProvider LOOK_DEST = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
|
||||
|
||||
if (destLocation != null) {
|
||||
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
|
||||
|
||||
if (startInWorld == null) {
|
||||
startInWorld = entitypatch.getOriginal().position();
|
||||
}
|
||||
|
||||
Vec3 toDestWorld = destLocation.subtract(startInWorld);
|
||||
float yRot = (float)Mth.wrapDegrees(MathUtils.getYRotOfVector(toDestWorld));
|
||||
float entityYRot = MathUtils.rotlerp(entitypatch.getYRot(), yRot, entitypatch.getYRotLimit());
|
||||
|
||||
return entityYRot;
|
||||
} else {
|
||||
return entitypatch.getYRot();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotate an entity toward target for attack animations
|
||||
*/
|
||||
public static final YRotProvider MOB_ATTACK_TARGET_LOOK = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
|
||||
if (!entitypatch.isLogicalClient() && entitypatch instanceof MobPatch<?> mobpatch) {
|
||||
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
|
||||
float elapsedTime = player.getElapsedTime();
|
||||
EntityState state = self.getState(entitypatch, elapsedTime);
|
||||
|
||||
if (state.getLevel() == 1 && !state.turningLocked()) {
|
||||
mobpatch.getOriginal().getNavigation().stop();
|
||||
entitypatch.getOriginal().attackAnim = 2;
|
||||
LivingEntity target = entitypatch.getTarget();
|
||||
|
||||
if (target != null) {
|
||||
float currentYRot = Mth.wrapDegrees(entitypatch.getOriginal().getYRot());
|
||||
float clampedYRot = entitypatch.getYRotDeltaTo(target);
|
||||
|
||||
return currentYRot + clampedYRot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entitypatch.getYRot();
|
||||
};
|
||||
|
||||
/******************************************************
|
||||
* MoveCoordSetters
|
||||
* Consider that getAnimationPlayer(self) returns null at the beginning.
|
||||
******************************************************/
|
||||
/**
|
||||
* Sets a raw animation coordinate as action animation's coord
|
||||
* - read by {@link MoveCoordFunctions#MODEL_COORD}
|
||||
*/
|
||||
public static final MoveCoordSetter RAW_COORD = (self, entitypatch, transformSheet) -> {
|
||||
transformSheet.readFrom(self.getCoord().copyAll());
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a raw animation coordinate multiplied by entity's pitch as action animation's coord
|
||||
* - read by {@link MoveCoordFunctions#MODEL_COORD}
|
||||
*/
|
||||
public static final MoveCoordSetter RAW_COORD_WITH_X_ROT = (self, entitypatch, transformSheet) -> {
|
||||
TransformSheet sheet = self.getCoord().copyAll();
|
||||
float xRot = entitypatch.getOriginal().getXRot();
|
||||
|
||||
for (Keyframe kf : sheet.getKeyframes()) {
|
||||
kf.transform().translation().rotate(-xRot, Vec3f.X_AXIS);
|
||||
}
|
||||
|
||||
transformSheet.readFrom(sheet);
|
||||
};
|
||||
|
||||
/**
|
||||
* Trace the origin point(0, 0, 0) in blender coord system as the destination
|
||||
* - specify the {@link ActionAnimationProperty#DEST_LOCATION_PROVIDER} or it will act as {@link MoveCoordFunctions#RAW_COORD}.
|
||||
* - the first keyframe's location is where the entity is in world
|
||||
* - you can specify target frame distance by {@link ActionAnimationProperty#COORD_START_KEYFRAME_INDEX}, {@link ActionAnimationProperty#COORD_DEST_KEYFRAME_INDEX}
|
||||
* - the coord after destination frame will not be scaled or rotated by distance gap between start location and end location in world coord
|
||||
* - entity's x rotation is not affected by this coord function
|
||||
* - entity's y rotation is the direction toward a destination, or you can give specific rotation value by {@link ActionAnimation#ENTITY_Y_ROT AnimationProperty}
|
||||
* - no movements in link animation
|
||||
* - read by {@link MoveCoordFunctions#WORLD_COORD}
|
||||
*/
|
||||
public static final MoveCoordSetter TRACE_ORIGIN_AS_DESTINATION = (self, entitypatch, transformSheet) -> {
|
||||
if (self.isLinkAnimation()) {
|
||||
transformSheet.readFrom(TransformSheet.EMPTY_SHEET_PROVIDER.apply(entitypatch.getOriginal().position()));
|
||||
return;
|
||||
}
|
||||
|
||||
Keyframe[] coordKeyframes = self.getCoord().getKeyframes();
|
||||
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
|
||||
int destFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coordKeyframes.length - 1);
|
||||
Vec3 destInWorld = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
|
||||
|
||||
if (destInWorld == null) {
|
||||
Vec3f beginningPosition = coordKeyframes[0].transform().translation().copy().multiply(1.0F, 1.0F, -1.0F);
|
||||
beginningPosition.rotate(-entitypatch.getYRot(), Vec3f.Y_AXIS);
|
||||
destInWorld = entitypatch.getOriginal().position().add(-beginningPosition.x, -beginningPosition.y, -beginningPosition.z);
|
||||
}
|
||||
|
||||
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
|
||||
|
||||
if (startInWorld == null) {
|
||||
startInWorld = entitypatch.getOriginal().position();
|
||||
}
|
||||
|
||||
Vec3 toTargetInWorld = destInWorld.subtract(startInWorld);
|
||||
float yRot = (float)Mth.wrapDegrees(MathUtils.getYRotOfVector(toTargetInWorld));
|
||||
Optional<YRotProvider> destYRotProvider = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_COORD_YROT_PROVIDER);
|
||||
float destYRot = destYRotProvider.isEmpty() ? yRot : destYRotProvider.get().get(self, entitypatch);
|
||||
|
||||
TransformSheet result = self.getCoord().transformToWorldCoordOriginAsDest(entitypatch, startInWorld, destInWorld, yRot, destYRot, startFrame, destFrame);
|
||||
transformSheet.readFrom(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Trace the target entity's position (use it with MODEL_COORD)
|
||||
* - the location of the last keyfram is basis to limit maximum distance
|
||||
* - rotation is where the entity is looking
|
||||
*/
|
||||
public static final MoveCoordSetter TRACE_TARGET_DISTANCE = (self, entitypatch, transformSheet) -> {
|
||||
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
|
||||
|
||||
if (destLocation != null) {
|
||||
TransformSheet transform = self.getCoord().copyAll();
|
||||
Keyframe[] coord = transform.getKeyframes();
|
||||
Keyframe[] realAnimationCoord = self.getRealAnimation().get().getCoord().getKeyframes();
|
||||
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
|
||||
|
||||
if (startInWorld == null) {
|
||||
startInWorld = entitypatch.getOriginal().position();
|
||||
}
|
||||
|
||||
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
|
||||
int realAnimationEndFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(self.getRealAnimation().get().getCoord().getKeyframes().length - 1);
|
||||
Vec3 toDestWorld = destLocation.subtract(startInWorld);
|
||||
Vec3f toDestAnim = realAnimationCoord[realAnimationEndFrame].transform().translation();
|
||||
LivingEntity attackTarget = entitypatch.getTarget();
|
||||
|
||||
// Calculate Entity-Entity collide radius
|
||||
float entityRadius = 0.0F;
|
||||
|
||||
if (attackTarget != null) {
|
||||
float reach = 0.0F;
|
||||
|
||||
if (self.getRealAnimation().get() instanceof AttackAnimation attackAnimation) {
|
||||
Optional<Float> reachOpt = attackAnimation.getProperty(AttackAnimationProperty.REACH);
|
||||
|
||||
if (reachOpt.isPresent()) {
|
||||
reach = reachOpt.get();
|
||||
} else {
|
||||
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
|
||||
|
||||
if (player != null) {
|
||||
Phase phase = attackAnimation.getPhaseByTime(player.getElapsedTime());
|
||||
reach = entitypatch.getReach(phase.hand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entityRadius = (attackTarget.getBbWidth() + entitypatch.getOriginal().getBbWidth()) * 0.7F + reach;
|
||||
}
|
||||
|
||||
float worldLength = Math.max((float)toDestWorld.length() - entityRadius, 0.0F);
|
||||
float animLength = toDestAnim.length();
|
||||
|
||||
float dot = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.INITIAL_LOOK_VEC_DOT, self.getRealAnimation());
|
||||
float lookLength = Mth.lerp(dot, animLength, worldLength);
|
||||
float scale = Math.min(lookLength / animLength, 1.0F);
|
||||
|
||||
if (self.isLinkAnimation()) {
|
||||
scale *= coord[coord.length - 1].transform().translation().length() / animLength;
|
||||
}
|
||||
|
||||
int endFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coord.length - 1);
|
||||
|
||||
for (int i = startFrame; i <= endFrame; i++) {
|
||||
Vec3f translation = coord[i].transform().translation();
|
||||
translation.x *= scale;
|
||||
|
||||
if (translation.z < 0.0F) {
|
||||
translation.z *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
transformSheet.readFrom(transform);
|
||||
} else {
|
||||
transformSheet.readFrom(self.getCoord().copyAll());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trace the target entity's position (use it MODEL_COORD)
|
||||
* - the location of the last keyframe is a basis to limit maximum distance
|
||||
* - rotation is the direction toward a target entity
|
||||
*/
|
||||
public static final MoveCoordSetter TRACE_TARGET_LOCATION_ROTATION = (self, entitypatch, transformSheet) -> {
|
||||
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
|
||||
|
||||
if (destLocation != null) {
|
||||
TransformSheet transform = self.getCoord().copyAll();
|
||||
Keyframe[] coord = transform.getKeyframes();
|
||||
Keyframe[] realAnimationCoord = self.getRealAnimation().get().getCoord().getKeyframes();
|
||||
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
|
||||
|
||||
if (startInWorld == null) {
|
||||
startInWorld = entitypatch.getOriginal().position();
|
||||
}
|
||||
|
||||
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
|
||||
int endFrame = self.isLinkAnimation() ? coord.length - 1 : self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coord.length - 1);
|
||||
Vec3 toDestWorld = destLocation.subtract(startInWorld);
|
||||
Vec3f toDestAnim = realAnimationCoord[endFrame].transform().translation();
|
||||
LivingEntity attackTarget = entitypatch.getTarget();
|
||||
|
||||
// Calculate Entity-Entity collide radius
|
||||
float entityRadius = 0.0F;
|
||||
|
||||
if (attackTarget != null) {
|
||||
float reach = 0.0F;
|
||||
|
||||
if (self.getRealAnimation().get() instanceof AttackAnimation attackAnimation) {
|
||||
Optional<Float> reachOpt = attackAnimation.getProperty(AttackAnimationProperty.REACH);
|
||||
|
||||
if (reachOpt.isPresent()) {
|
||||
reach = reachOpt.get();
|
||||
} else {
|
||||
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
|
||||
|
||||
if (player != null) {
|
||||
Phase phase = attackAnimation.getPhaseByTime(player.getElapsedTime());
|
||||
reach = entitypatch.getReach(phase.hand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entityRadius = (attackTarget.getBbWidth() + entitypatch.getOriginal().getBbWidth()) * 0.7F + reach;
|
||||
}
|
||||
|
||||
float worldLength = Math.max((float)toDestWorld.length() - entityRadius, 0.0F);
|
||||
float animLength = toDestAnim.length();
|
||||
float scale = Math.min(worldLength / animLength, 1.0F);
|
||||
|
||||
if (self.isLinkAnimation()) {
|
||||
scale *= coord[endFrame].transform().translation().length() / animLength;
|
||||
}
|
||||
|
||||
for (int i = startFrame; i <= endFrame; i++) {
|
||||
Vec3f translation = coord[i].transform().translation();
|
||||
translation.x *= scale;
|
||||
|
||||
if (translation.z < 0.0F) {
|
||||
translation.z *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
transformSheet.readFrom(transform);
|
||||
} else {
|
||||
transformSheet.readFrom(self.getCoord().copyAll());
|
||||
}
|
||||
};
|
||||
|
||||
public static final MoveCoordSetter VEX_TRACE = (self, entitypatch, transformSheet) -> {
|
||||
if (!self.isLinkAnimation()) {
|
||||
TransformSheet transform = self.getCoord().copyAll();
|
||||
|
||||
if (entitypatch.getTarget() != null) {
|
||||
Keyframe[] keyframes = transform.getKeyframes();
|
||||
Vec3 pos = entitypatch.getOriginal().position();
|
||||
Vec3 targetpos = entitypatch.getTarget().getEyePosition();
|
||||
double flyDistance = Math.max(5.0D, targetpos.subtract(pos).length() * 2);
|
||||
|
||||
transform.forEach((index, keyframe) -> {
|
||||
keyframe.transform().translation().scale((float)(flyDistance / Math.abs(keyframes[keyframes.length - 1].transform().translation().z)));
|
||||
});
|
||||
|
||||
Vec3 toTarget = targetpos.subtract(pos);
|
||||
float xRot = (float)-MathUtils.getXRotOfVector(toTarget);
|
||||
float yRot = (float)MathUtils.getYRotOfVector(toTarget);
|
||||
|
||||
entitypatch.setYRot(yRot);
|
||||
|
||||
transform.forEach((index, keyframe) -> {
|
||||
keyframe.transform().translation().rotateDegree(Vec3f.X_AXIS, xRot);
|
||||
keyframe.transform().translation().rotateDegree(Vec3f.Y_AXIS, 180.0F - yRot);
|
||||
keyframe.transform().translation().add(entitypatch.getOriginal().position());
|
||||
});
|
||||
|
||||
transformSheet.readFrom(transform);
|
||||
} else {
|
||||
transform.forEach((index, keyframe) -> {
|
||||
keyframe.transform().translation().rotateDegree(Vec3f.Y_AXIS, 180.0F - entitypatch.getYRot());
|
||||
keyframe.transform().translation().add(entitypatch.getOriginal().position());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
|
||||
/**
|
||||
* RIG stub. Upstream EF : ActionAnimation ajoute un système de COORD
|
||||
* TransformSheet pour les mouvements d'attaque en espace monde + des
|
||||
* packets sync serveur/client via {@code CPSyncPlayerAnimationPosition}.
|
||||
* Strippé pour TiedUp — garde juste l'API {@code addProperty} utilisée par
|
||||
* {@link com.tiedup.remake.rig.asset.JsonAssetLoader} (ligne 621).
|
||||
*/
|
||||
public class ActionAnimation extends MainFrameAnimation {
|
||||
|
||||
// Variables indépendantes propagées à MoveCoordFunctions (position de départ
|
||||
// en coord monde + produit scalaire lookVec initial pour lerp anim→world).
|
||||
public static final IndependentAnimationVariableKey<Vec3> BEGINNING_LOCATION =
|
||||
AnimationVariables.independent((animator) -> animator.getEntityPatch().getOriginal().position(), true);
|
||||
public static final IndependentAnimationVariableKey<Float> INITIAL_LOOK_VEC_DOT =
|
||||
AnimationVariables.independent((animator) -> 1.0F, true);
|
||||
|
||||
public ActionAnimation(float transitionTime, boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat, registryName, armature);
|
||||
}
|
||||
|
||||
public ActionAnimation(boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(isRepeat, registryName, armature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une propriété action-specific (COORD TransformSheet typiquement).
|
||||
* Version stub : no-op, juste pour que les refs JsonAssetLoader compilent.
|
||||
*/
|
||||
public <V> ActionAnimation addProperty(ActionAnimationProperty<V> property, V value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub Phase 0 — LinkAnimation.modifyPose appelle ça pour zero-out X/Z de la
|
||||
* Root joint en espace monde (empêche le sliding visuel pendant la transition
|
||||
* vers une ActionAnimation sans keyframe "Coord"). Voir EF
|
||||
* {@code yesman.epicfight.api.animation.types.ActionAnimation:209-222}.
|
||||
*
|
||||
* <p>Safe Phase 1 (idle/walk sont des StaticAnimation, pas ActionAnimation →
|
||||
* jamais appelée). À re-implémenter Phase 2 dès qu'on introduit de vraies
|
||||
* ActionAnimations bondage — sinon : sliding visible pendant transitionTime
|
||||
* frames à chaque entrée dans l'action anim.</p>
|
||||
*/
|
||||
public void correctRootJoint(
|
||||
com.tiedup.remake.rig.anim.types.DynamicAnimation animation,
|
||||
com.tiedup.remake.rig.anim.Pose pose,
|
||||
com.tiedup.remake.rig.patch.LivingEntityPatch<?> entitypatch,
|
||||
float time,
|
||||
float partialTicks
|
||||
) {
|
||||
if (com.tiedup.remake.rig.TiedUpRigConstants.IS_DEV_ENV) {
|
||||
com.tiedup.remake.rig.TiedUpRigConstants.LOGGER.warn(
|
||||
"correctRootJoint no-op appelé (Phase 0 stub) — si ActionAnimation jouée, "
|
||||
+ "sliding visuel attendu. Voir docs/plans/rig/PHASE0_DEGRADATIONS.md D-07."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
|
||||
/**
|
||||
* RIG stub. Upstream EF : AttackAnimation = système d'attaque combat (570L,
|
||||
* Phase timings, hitboxes, damage source, attack events, colliders). 100%
|
||||
* combat, strippé en stub minimal pour TiedUp.
|
||||
*
|
||||
* <p>On conserve :</p>
|
||||
* <ul>
|
||||
* <li>Le type {@code AttackAnimation} pour les {@code instanceof} dans
|
||||
* {@link com.tiedup.remake.rig.asset.JsonAssetLoader}:584</li>
|
||||
* <li>Le field {@code phases} (liste vide) pour la boucle d'itération
|
||||
* en JsonAssetLoader:591</li>
|
||||
* <li>La classe interne {@link Phase} avec un {@code getColliders()}
|
||||
* no-op pour JsonAssetLoader:592</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class AttackAnimation extends ActionAnimation {
|
||||
|
||||
/**
|
||||
* Liste des phases d'attaque. Toujours vide en TiedUp — on ne joue
|
||||
* jamais d'animation attaque. Mais le field doit exister pour que
|
||||
* JsonAssetLoader.java ligne 591 puisse itérer dessus sans NPE.
|
||||
*
|
||||
* <p>Type {@code Phase[]} (et non {@code List<Phase>}) pour s'aligner
|
||||
* sur la signature upstream EF — facilite le re-port de fixes EF.</p>
|
||||
*/
|
||||
public final Phase[] phases = new Phase[0];
|
||||
|
||||
public AttackAnimation(float transitionTime, boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat, registryName, armature);
|
||||
}
|
||||
|
||||
public AttackAnimation(boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(isRepeat, registryName, armature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub — MoveCoordFunctions appelle ça pour calculer la reach mid-anim.
|
||||
* On retourne toujours une Phase neutre (mainHand, pas de colliders),
|
||||
* donc le reach retombe sur {@code entitypatch.getReach(MAIN_HAND)}.
|
||||
*/
|
||||
public Phase getPhaseByTime(float elapsedTime) {
|
||||
return new Phase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase d'attaque. Stub pour satisfaire {@code phase.getColliders()}
|
||||
* en JsonAssetLoader + {@code phase.hand} en MoveCoordFunctions.
|
||||
*/
|
||||
public static class Phase {
|
||||
public final InteractionHand hand = InteractionHand.MAIN_HAND;
|
||||
|
||||
public JointColliderPair[] getColliders() {
|
||||
return new JointColliderPair[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub — (Joint, Collider) pair pour les hitboxes combat. Non utilisé
|
||||
* en TiedUp, juste un placeholder typé pour que JsonAssetLoader:592 compile.
|
||||
*/
|
||||
public static class JointColliderPair {
|
||||
public com.tiedup.remake.rig.armature.Joint getFirst() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class ConcurrentLinkAnimation extends DynamicAnimation implements AnimationAccessor<ConcurrentLinkAnimation> {
|
||||
protected AssetAccessor<? extends StaticAnimation> nextAnimation;
|
||||
protected AssetAccessor<? extends DynamicAnimation> currentAnimation;
|
||||
protected float startsAt;
|
||||
|
||||
public ConcurrentLinkAnimation() {
|
||||
this.animationClip = new AnimationClip();
|
||||
}
|
||||
|
||||
public void acceptFrom(AssetAccessor<? extends DynamicAnimation> currentAnimation, AssetAccessor<? extends StaticAnimation> nextAnimation, float time) {
|
||||
this.currentAnimation = currentAnimation;
|
||||
this.nextAnimation = nextAnimation;
|
||||
this.startsAt = time;
|
||||
this.setTotalTime(nextAnimation.get().getTransitionTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(LivingEntityPatch<?> entitypatch) {
|
||||
this.nextAnimation.get().linkTick(entitypatch, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
|
||||
if (!isEnd) {
|
||||
this.nextAnimation.get().end(entitypatch, nextAnimation, isEnd);
|
||||
} else {
|
||||
if (this.startsAt > 0.0F) {
|
||||
entitypatch.getAnimator().getPlayer(this).ifPresent(player -> {
|
||||
player.setElapsedTime(this.startsAt);
|
||||
player.markDoNotResetTime();
|
||||
});
|
||||
|
||||
this.startsAt = 0.0F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.nextAnimation.get().getState(entitypatch, 0.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.nextAnimation.get().getState(stateFactor, entitypatch, 0.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
float elapsed = time + this.startsAt;
|
||||
float currentElapsed = elapsed % this.currentAnimation.get().getTotalTime();
|
||||
float nextElapsed = elapsed % this.nextAnimation.get().getTotalTime();
|
||||
Pose currentAnimPose = this.currentAnimation.get().getPoseByTime(entitypatch, currentElapsed, 1.0F);
|
||||
Pose nextAnimPose = this.nextAnimation.get().getPoseByTime(entitypatch, nextElapsed, 1.0F);
|
||||
float interpolate = time / this.getTotalTime();
|
||||
|
||||
Pose interpolatedPose = Pose.interpolatePose(currentAnimPose, nextAnimPose, interpolate);
|
||||
JointMaskEntry maskEntry = this.nextAnimation.get().getJointMaskEntry(entitypatch, true).orElse(null);
|
||||
|
||||
if (maskEntry != null && entitypatch.isLogicalClient()) {
|
||||
interpolatedPose.disableJoint((entry) ->
|
||||
maskEntry.isMasked(
|
||||
this.nextAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ? entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion()
|
||||
, entry.getKey()
|
||||
));
|
||||
}
|
||||
|
||||
return interpolatedPose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
this.nextAnimation.get().modifyPose(this, pose, entitypatch, time, partialTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
|
||||
return this.nextAnimation.get().getPlaySpeed(entitypatch, animation);
|
||||
}
|
||||
|
||||
public void setNextAnimation(AnimationAccessor<? extends StaticAnimation> animation) {
|
||||
this.nextAnimation = animation;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Override
|
||||
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
|
||||
return this.nextAnimation.get().getJointMaskEntry(entitypatch, useCurrentMotion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMainFrameAnimation() {
|
||||
return this.nextAnimation.get().isMainFrameAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReboundAnimation() {
|
||||
return this.nextAnimation.get().isReboundAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
|
||||
return this.nextAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConcurrentLinkAnimation: Mix " + this.currentAnimation + " and " + this.nextAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationClip getAnimationClip() {
|
||||
return this.animationClip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTransformFor(String joint) {
|
||||
return this.nextAnimation.get().hasTransformFor(joint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLinkAnimation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrentLinkAnimation get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationAccessor<? extends DynamicAnimation> getAccessor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
|
||||
public class DirectStaticAnimation extends StaticAnimation implements AnimationAccessor<DirectStaticAnimation> {
|
||||
private ResourceLocation registryName;
|
||||
|
||||
public DirectStaticAnimation() {
|
||||
this.accessor = this;
|
||||
}
|
||||
|
||||
public DirectStaticAnimation(float transitionTime, boolean isRepeat, ResourceLocation registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat, registryName.toString(), armature);
|
||||
|
||||
this.registryName = registryName;
|
||||
this.accessor = this;
|
||||
}
|
||||
|
||||
/* Multilayer, Pov animation Constructor */
|
||||
@ApiStatus.Internal
|
||||
public DirectStaticAnimation(ResourceLocation baseAnimPath, float transitionTime, boolean repeatPlay, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(baseAnimPath, transitionTime, repeatPlay, registryName, armature);
|
||||
|
||||
this.registryName = ResourceLocation.parse(registryName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectStaticAnimation get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <A extends DynamicAnimation> AnimationAccessor<A> getAccessor() {
|
||||
return (AnimationAccessor<A>)this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return this.registryName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.AnimationPlayer;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public abstract class DynamicAnimation {
|
||||
protected final boolean isRepeat;
|
||||
protected final float transitionTime;
|
||||
protected AnimationClip animationClip;
|
||||
|
||||
public DynamicAnimation() {
|
||||
this(TiedUpRigConstants.GENERAL_ANIMATION_TRANSITION_TIME, false);
|
||||
}
|
||||
|
||||
public DynamicAnimation(float transitionTime, boolean isRepeat) {
|
||||
this.isRepeat = isRepeat;
|
||||
this.transitionTime = transitionTime;
|
||||
}
|
||||
|
||||
public final Pose getRawPose(float time) {
|
||||
return this.getAnimationClip().getPoseInTime(time);
|
||||
}
|
||||
|
||||
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
Pose pose = this.getRawPose(time);
|
||||
this.modifyPose(this, pose, entitypatch, time, partialTicks);
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
/** Modify the pose both this and link animation. **/
|
||||
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
}
|
||||
|
||||
public void putOnPlayer(AnimationPlayer animationPlayer, LivingEntityPatch<?> entitypatch) {
|
||||
animationPlayer.setPlayAnimation(this.getAccessor());
|
||||
animationPlayer.tick(entitypatch);
|
||||
animationPlayer.begin(this.getAccessor(), entitypatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the animation put on the {@link AnimationPlayer}
|
||||
* @param entitypatch
|
||||
*/
|
||||
public void begin(LivingEntityPatch<?> entitypatch) {}
|
||||
|
||||
/**
|
||||
* Called each tick when the animation is played
|
||||
* @param entitypatch
|
||||
*/
|
||||
public void tick(LivingEntityPatch<?> entitypatch) {}
|
||||
|
||||
/**
|
||||
* Called when both the animation finished or stopped by other animation.
|
||||
* @param entitypatch
|
||||
* @param nextAnimation the next animation to play after the animation ends
|
||||
* @param isEnd whether the animation completed or not
|
||||
*
|
||||
* if @param isEnd true, nextAnimation is null
|
||||
* if @param isEnd false, nextAnimation is not null
|
||||
*/
|
||||
public void end(LivingEntityPatch<?> entitypatch, @Nullable AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {}
|
||||
public void linkTick(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> linkAnimation) {};
|
||||
|
||||
public boolean hasTransformFor(String joint) {
|
||||
return this.getTransfroms().containsKey(joint);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return EntityState.DEFAULT_STATE;
|
||||
}
|
||||
|
||||
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return new TypeFlexibleHashMap<> (false);
|
||||
}
|
||||
|
||||
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
|
||||
return stateFactor.defaultValue();
|
||||
}
|
||||
|
||||
public AnimationClip getAnimationClip() {
|
||||
return this.animationClip;
|
||||
}
|
||||
|
||||
public Map<String, TransformSheet> getTransfroms() {
|
||||
return this.getAnimationClip().getJointTransforms();
|
||||
}
|
||||
|
||||
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
public TransformSheet getCoord() {
|
||||
return this.getTransfroms().containsKey("Root") ? this.getTransfroms().get("Root") : TransformSheet.EMPTY_SHEET;
|
||||
}
|
||||
|
||||
public void setTotalTime(float totalTime) {
|
||||
this.getAnimationClip().setClipTime(totalTime);
|
||||
}
|
||||
|
||||
public float getTotalTime() {
|
||||
return this.getAnimationClip().getClipTime();
|
||||
}
|
||||
|
||||
public float getTransitionTime() {
|
||||
return this.transitionTime;
|
||||
}
|
||||
|
||||
public boolean isRepeat() {
|
||||
return this.isRepeat;
|
||||
}
|
||||
|
||||
public boolean canBePlayedReverse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public ResourceLocation getRegistryName() {
|
||||
return TiedUpRigConstants.identifier("");
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isBasicAttackAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMainFrameAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isReboundAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMetaAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isClientAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isStaticAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract <A extends DynamicAnimation> AnimationAccessor<? extends DynamicAnimation> getAccessor();
|
||||
public abstract AssetAccessor<? extends StaticAnimation> getRealAnimation();
|
||||
|
||||
public boolean isLinkAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean doesHeadRotFollowEntityHead() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void renderDebugging(PoseStack poseStack, MultiBufferSource buffer, LivingEntityPatch<?> entitypatch, float playTime, float partialTicks) {
|
||||
}
|
||||
}
|
||||
146
src/main/java/com/tiedup/remake/rig/anim/types/EntityState.java
Normal file
146
src/main/java/com/tiedup/remake/rig/anim/types/EntityState.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraftforge.event.entity.ProjectileImpactEvent;
|
||||
import com.tiedup.remake.rig.util.AttackResult;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
|
||||
public class EntityState {
|
||||
public static class StateFactor<T> implements TypeFlexibleHashMap.TypeKey<T> {
|
||||
private final String name;
|
||||
private final T defaultValue;
|
||||
|
||||
public StateFactor(String name, T defaultValue) {
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public T defaultValue() {
|
||||
return this.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static final EntityState DEFAULT_STATE = new EntityState(new TypeFlexibleHashMap<>(true));
|
||||
|
||||
public static final StateFactor<Boolean> TURNING_LOCKED = new StateFactor<>("turningLocked", false);
|
||||
public static final StateFactor<Boolean> MOVEMENT_LOCKED = new StateFactor<>("movementLocked", false);
|
||||
public static final StateFactor<Boolean> ATTACKING = new StateFactor<>("attacking", false);
|
||||
public static final StateFactor<Boolean> CAN_BASIC_ATTACK = new StateFactor<>("canBasicAttack", true);
|
||||
public static final StateFactor<Boolean> CAN_SKILL_EXECUTION = new StateFactor<>("canExecuteSkill", true);
|
||||
public static final StateFactor<Boolean> CAN_USE_ITEM = new StateFactor<>("canUseItem", true);
|
||||
public static final StateFactor<Boolean> CAN_SWITCH_HAND_ITEM = new StateFactor<>("canSwitchHandItem", true);
|
||||
public static final StateFactor<Boolean> INACTION = new StateFactor<>("takingAction", false);
|
||||
public static final StateFactor<Boolean> KNOCKDOWN = new StateFactor<>("knockdown", false);
|
||||
public static final StateFactor<Boolean> LOCKON_ROTATE = new StateFactor<>("lockonRotate", false);
|
||||
public static final StateFactor<Boolean> UPDATE_LIVING_MOTION = new StateFactor<>("updateLivingMotion", true);
|
||||
public static final StateFactor<Integer> HURT_LEVEL = new StateFactor<>("hurtLevel", 0);
|
||||
public static final StateFactor<Integer> PHASE_LEVEL = new StateFactor<>("phaseLevel", 0);
|
||||
public static final StateFactor<Function<DamageSource, AttackResult.ResultType>> ATTACK_RESULT = new StateFactor<>("attackResultModifier", (damagesource) -> AttackResult.ResultType.SUCCESS);
|
||||
public static final StateFactor<Consumer<ProjectileImpactEvent>> PROJECTILE_IMPACT_RESULT = new StateFactor<>("projectileImpactResult", (event) -> {});
|
||||
|
||||
private final TypeFlexibleHashMap<StateFactor<?>> stateMap;
|
||||
|
||||
public EntityState(TypeFlexibleHashMap<StateFactor<?>> states) {
|
||||
this.stateMap = states;
|
||||
}
|
||||
|
||||
public <T> void setState(StateFactor<T> stateFactor, T val) {
|
||||
this.stateMap.put(stateFactor, (Object)val);
|
||||
}
|
||||
|
||||
public <T> T getState(StateFactor<T> stateFactor) {
|
||||
return this.stateMap.getOrDefault(stateFactor);
|
||||
}
|
||||
|
||||
public TypeFlexibleHashMap<StateFactor<?>> getStateMap() {
|
||||
return this.stateMap;
|
||||
}
|
||||
|
||||
public boolean turningLocked() {
|
||||
return this.getState(EntityState.TURNING_LOCKED);
|
||||
}
|
||||
|
||||
public boolean movementLocked() {
|
||||
return this.getState(EntityState.MOVEMENT_LOCKED);
|
||||
}
|
||||
|
||||
public boolean attacking() {
|
||||
return this.getState(EntityState.ATTACKING);
|
||||
}
|
||||
|
||||
public AttackResult.ResultType attackResult(DamageSource damagesource) {
|
||||
return this.getState(EntityState.ATTACK_RESULT).apply(damagesource);
|
||||
}
|
||||
|
||||
public void setProjectileImpactResult(ProjectileImpactEvent event) {
|
||||
this.getState(EntityState.PROJECTILE_IMPACT_RESULT).accept(event);
|
||||
}
|
||||
|
||||
public boolean canBasicAttack() {
|
||||
return this.getState(EntityState.CAN_BASIC_ATTACK);
|
||||
}
|
||||
|
||||
public boolean canUseSkill() {
|
||||
return this.getState(EntityState.CAN_SKILL_EXECUTION);
|
||||
}
|
||||
|
||||
public boolean canUseItem() {
|
||||
return this.canUseSkill() && this.getState(EntityState.CAN_USE_ITEM);
|
||||
}
|
||||
|
||||
public boolean canSwitchHoldingItem() {
|
||||
return !this.inaction() && this.getState(EntityState.CAN_SWITCH_HAND_ITEM);
|
||||
}
|
||||
|
||||
public boolean inaction() {
|
||||
return this.getState(EntityState.INACTION);
|
||||
}
|
||||
|
||||
public boolean updateLivingMotion() {
|
||||
return this.getState(EntityState.UPDATE_LIVING_MOTION);
|
||||
}
|
||||
|
||||
public boolean hurt() {
|
||||
return this.getState(EntityState.HURT_LEVEL) > 0;
|
||||
}
|
||||
|
||||
public int hurtLevel() {
|
||||
return this.getState(EntityState.HURT_LEVEL);
|
||||
}
|
||||
|
||||
public boolean knockDown() {
|
||||
return this.getState(EntityState.KNOCKDOWN);
|
||||
}
|
||||
|
||||
public boolean lockonRotate() {
|
||||
return this.getState(EntityState.LOCKON_ROTATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1: anticipation
|
||||
* 2: attacking
|
||||
* 3: recovery
|
||||
* @return level
|
||||
*/
|
||||
public int getLevel() {
|
||||
return this.getState(EntityState.PHASE_LEVEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.stateMap.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.Layer.Priority;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class LayerOffAnimation extends DynamicAnimation implements AnimationAccessor<LayerOffAnimation> {
|
||||
private AssetAccessor<? extends DynamicAnimation> lastAnimation;
|
||||
private Pose lastPose;
|
||||
private final Priority layerPriority;
|
||||
|
||||
public LayerOffAnimation(Priority layerPriority) {
|
||||
this.layerPriority = layerPriority;
|
||||
this.animationClip = new AnimationClip();
|
||||
}
|
||||
|
||||
public void setLastPose(Pose pose) {
|
||||
this.lastPose = pose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
|
||||
if (entitypatch.isLogicalClient() && isEnd) {
|
||||
entitypatch.getClientAnimator().baseLayer.disableLayer(this.layerPriority);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
Pose lowerLayerPose = entitypatch.getClientAnimator().getComposedLayerPoseBelow(this.layerPriority, Minecraft.getInstance().getFrameTime());
|
||||
Pose interpolatedPose = Pose.interpolatePose(this.lastPose, lowerLayerPose, time / this.getTotalTime());
|
||||
interpolatedPose.disableJoint((joint) -> !this.lastPose.hasTransform(joint.getKey()));
|
||||
|
||||
return interpolatedPose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
|
||||
return this.lastAnimation.get().getJointMaskEntry(entitypatch, useCurrentMotion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
|
||||
return this.lastAnimation.get().getProperty(propertyType);
|
||||
}
|
||||
|
||||
public void setLastAnimation(AssetAccessor<? extends DynamicAnimation> animation) {
|
||||
this.lastAnimation = animation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesHeadRotFollowEntityHead() {
|
||||
return this.lastAnimation.get().doesHeadRotFollowEntityHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
|
||||
return TiedUpRigRegistry.EMPTY_ANIMATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationClip getAnimationClip() {
|
||||
return this.animationClip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTransformFor(String joint) {
|
||||
return this.lastPose.hasTransform(joint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLinkAnimation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayerOffAnimation get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationAccessor<? extends LayerOffAnimation> getAccessor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Keyframe;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class LinkAnimation extends DynamicAnimation implements AnimationAccessor<LinkAnimation> {
|
||||
protected TransformSheet coord;
|
||||
protected AssetAccessor<? extends DynamicAnimation> fromAnimation;
|
||||
protected AssetAccessor<? extends StaticAnimation> toAnimation;
|
||||
protected float nextStartTime;
|
||||
|
||||
public LinkAnimation() {
|
||||
this.animationClip = new AnimationClip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(LivingEntityPatch<?> entitypatch) {
|
||||
this.toAnimation.get().linkTick(entitypatch, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
|
||||
if (!isEnd) {
|
||||
this.toAnimation.get().end(entitypatch, nextAnimation, isEnd);
|
||||
} else {
|
||||
if (this.nextStartTime > 0.0F) {
|
||||
entitypatch.getAnimator().getPlayer(this).ifPresent(player -> {
|
||||
player.setElapsedTime(this.nextStartTime);
|
||||
player.markDoNotResetTime();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
|
||||
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
|
||||
TypeFlexibleHashMap<StateFactor<?>> map = this.toAnimation.get().getStatesMap(entitypatch, timeInRealAnimation);
|
||||
|
||||
for (Map.Entry<StateFactor<?>, Object> entry : map.entrySet()) {
|
||||
Object val = this.toAnimation.get().getModifiedLinkState(entry.getKey(), entry.getValue(), entitypatch, time);
|
||||
map.put(entry.getKey(), val);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
|
||||
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
|
||||
|
||||
EntityState state = this.toAnimation.get().getState(entitypatch, timeInRealAnimation);
|
||||
TypeFlexibleHashMap<StateFactor<?>> map = state.getStateMap();
|
||||
|
||||
for (Map.Entry<StateFactor<?>, Object> entry : map.entrySet()) {
|
||||
Object val = this.toAnimation.get().getModifiedLinkState(entry.getKey(), entry.getValue(), entitypatch, time);
|
||||
map.put(entry.getKey(), val);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
|
||||
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
|
||||
T state = this.toAnimation.get().getState(stateFactor, entitypatch, timeInRealAnimation);
|
||||
|
||||
return (T)this.toAnimation.get().getModifiedLinkState(stateFactor, state, entitypatch, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
Pose nextStartingPose = this.toAnimation.get().getPoseByTime(entitypatch, this.nextStartTime, partialTicks);
|
||||
|
||||
/**
|
||||
* Update dest pose
|
||||
*/
|
||||
for (Map.Entry<String, JointTransform> entry : nextStartingPose.getJointTransformData().entrySet()) {
|
||||
if (this.animationClip.hasJointTransform(entry.getKey())) {
|
||||
Keyframe[] keyframe = this.animationClip.getJointTransform(entry.getKey()).getKeyframes();
|
||||
JointTransform jt = keyframe[keyframe.length - 1].transform();
|
||||
JointTransform newJt = nextStartingPose.getJointTransformData().get(entry.getKey());
|
||||
newJt.translation().set(jt.translation());
|
||||
jt.copyFrom(newJt);
|
||||
}
|
||||
}
|
||||
|
||||
return super.getPoseByTime(entitypatch, time, partialTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
// Bad implementation: Add root joint as coord in loading animation
|
||||
if (this.toAnimation.get() instanceof ActionAnimation actionAnimation) {
|
||||
if (!this.getTransfroms().containsKey("Coord")) {
|
||||
actionAnimation.correctRootJoint(this, pose, entitypatch, time, partialTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
|
||||
return this.toAnimation.get().getPlaySpeed(entitypatch, animation);
|
||||
}
|
||||
|
||||
public void setConnectedAnimations(AssetAccessor<? extends DynamicAnimation> from, AssetAccessor<? extends StaticAnimation> to) {
|
||||
this.fromAnimation = from.get().getRealAnimation();
|
||||
this.toAnimation = to;
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends StaticAnimation> getNextAnimation() {
|
||||
return this.toAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformSheet getCoord() {
|
||||
if (this.coord != null) {
|
||||
return this.coord;
|
||||
} else if (this.getTransfroms().containsKey("Root")) {
|
||||
return this.getTransfroms().get("Root");
|
||||
}
|
||||
|
||||
return TransformSheet.EMPTY_SHEET;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
|
||||
return useCurrentMotion ? this.toAnimation.get().getJointMaskEntry(entitypatch, true) : this.fromAnimation.get().getJointMaskEntry(entitypatch, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMainFrameAnimation() {
|
||||
return this.toAnimation.get().isMainFrameAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReboundAnimation() {
|
||||
return this.toAnimation.get().isReboundAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesHeadRotFollowEntityHead() {
|
||||
return this.fromAnimation.get().doesHeadRotFollowEntityHead() && this.toAnimation.get().doesHeadRotFollowEntityHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
|
||||
return this.toAnimation;
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends DynamicAnimation> getFromAnimation() {
|
||||
return this.fromAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationAccessor<? extends DynamicAnimation> getAccessor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void copyTo(LinkAnimation dest) {
|
||||
dest.setConnectedAnimations(this.fromAnimation, this.toAnimation);
|
||||
dest.setTotalTime(this.getTotalTime());
|
||||
dest.getAnimationClip().reset();
|
||||
this.getTransfroms().forEach((jointName, transformSheet) -> dest.getAnimationClip().addJointTransform(jointName, transformSheet.copyAll()));
|
||||
}
|
||||
|
||||
public void loadCoord(TransformSheet coord) {
|
||||
this.coord = coord;
|
||||
}
|
||||
|
||||
public float getNextStartTime() {
|
||||
return this.nextStartTime;
|
||||
}
|
||||
|
||||
public void setNextStartTime(float nextStartTime) {
|
||||
this.nextStartTime = nextStartTime;
|
||||
}
|
||||
|
||||
public void resetNextStartTime() {
|
||||
this.nextStartTime = 0.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLinkAnimation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "From " + this.fromAnimation + " to " + this.toAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationClip getAnimationClip() {
|
||||
return this.animationClip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkAnimation get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation registryName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
|
||||
/**
|
||||
* RIG stub. Upstream EF : MainFrameAnimation ajoute du scheduling combat
|
||||
* via {@code ActionEvent}/{@code PlayerEventListener} (combat tick hooks).
|
||||
* Strippé pour TiedUp — garde juste le type pour satisfaire les
|
||||
* instanceof checks de {@link com.tiedup.remake.rig.asset.JsonAssetLoader}
|
||||
* et la hiérarchie {@link ActionAnimation} / {@link AttackAnimation}.
|
||||
*/
|
||||
public class MainFrameAnimation extends StaticAnimation {
|
||||
|
||||
public MainFrameAnimation(float transitionTime, boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat, registryName, armature);
|
||||
}
|
||||
|
||||
public MainFrameAnimation(boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(isRepeat, registryName, armature);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class StateSpectrum {
|
||||
private final Set<StatesInTime> timePairs = Sets.newHashSet();
|
||||
|
||||
void readFrom(StateSpectrum.Blueprint blueprint) {
|
||||
this.timePairs.clear();
|
||||
this.timePairs.addAll(blueprint.timePairs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSingleState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
|
||||
for (StatesInTime state : this.timePairs) {
|
||||
if (state.isIn(entitypatch, time)) {
|
||||
for (Map.Entry<StateFactor<?>, ?> timeEntry : state.getStates(entitypatch)) {
|
||||
if (timeEntry.getKey() == stateFactor) {
|
||||
return (T) timeEntry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stateFactor.defaultValue();
|
||||
}
|
||||
|
||||
public TypeFlexibleHashMap<StateFactor<?>> getStateMap(LivingEntityPatch<?> entitypatch, float time) {
|
||||
TypeFlexibleHashMap<StateFactor<?>> stateMap = new TypeFlexibleHashMap<>(true);
|
||||
|
||||
for (StatesInTime state : this.timePairs) {
|
||||
if (state.isIn(entitypatch, time)) {
|
||||
for (Map.Entry<StateFactor<?>, ?> timeEntry : state.getStates(entitypatch)) {
|
||||
stateMap.put(timeEntry.getKey(), timeEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stateMap;
|
||||
}
|
||||
|
||||
abstract static class StatesInTime {
|
||||
public abstract Set<Map.Entry<StateFactor<?>, Object>> getStates(LivingEntityPatch<?> entitypatch);
|
||||
|
||||
public abstract void removeState(StateFactor<?> state);
|
||||
|
||||
public abstract boolean hasState(StateFactor<?> state);
|
||||
|
||||
public abstract boolean isIn(LivingEntityPatch<?> entitypatch, float time);
|
||||
}
|
||||
|
||||
static class SimpleStatesInTime extends StatesInTime {
|
||||
float start;
|
||||
float end;
|
||||
Map<StateFactor<?>, Object> states = Maps.newHashMap();
|
||||
|
||||
public SimpleStatesInTime(float start, float end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIn(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.start <= time && this.end > time;
|
||||
}
|
||||
|
||||
public <T> StatesInTime addState(StateFactor<T> factor, T val) {
|
||||
this.states.put(factor, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<StateFactor<?>, Object>> getStates(LivingEntityPatch<?> entitypatch) {
|
||||
return this.states.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasState(StateFactor<?> state) {
|
||||
return this.states.containsKey(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeState(StateFactor<?> state) {
|
||||
this.states.remove(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Time: %.2f ~ %.2f, States: %s", this.start, this.end, this.states);
|
||||
}
|
||||
}
|
||||
|
||||
static class ConditionalStatesInTime extends StatesInTime {
|
||||
float start;
|
||||
float end;
|
||||
Int2ObjectMap<Map<StateFactor<?>, Object>> conditionalStates = new Int2ObjectOpenHashMap<>();
|
||||
Function<LivingEntityPatch<?>, Integer> condition;
|
||||
|
||||
public ConditionalStatesInTime(Function<LivingEntityPatch<?>, Integer> condition, float start, float end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public <T> StatesInTime addConditionalState(int metadata, StateFactor<T> factor, T val) {
|
||||
Map<StateFactor<?>, Object> states = this.conditionalStates.computeIfAbsent(metadata, (key) -> Maps.newHashMap());
|
||||
states.put(factor, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Set<Map.Entry<StateFactor<?>, Object>> getStates(LivingEntityPatch<?> entitypatch) {
|
||||
return this.conditionalStates.get(this.condition.apply(entitypatch)).entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIn(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.start <= time && this.end > time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasState(StateFactor<?> state) {
|
||||
boolean hasState = false;
|
||||
for (Map<StateFactor<?>, Object> states : this.conditionalStates.values()) {
|
||||
hasState |= states.containsKey(state);
|
||||
}
|
||||
return hasState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeState(StateFactor<?> state) {
|
||||
for (Map<StateFactor<?>, Object> states : this.conditionalStates.values()) {
|
||||
states.remove(state);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("Time: %.2f ~ %.2f, ", this.start, this.end));
|
||||
int entryCnt = 0;
|
||||
for (Map.Entry<Integer, Map<StateFactor<?>, Object>> entry : this.conditionalStates.entrySet()) {
|
||||
sb.append(String.format("States %d: %s", entry.getKey(), entry.getValue()));
|
||||
entryCnt++;
|
||||
if (entryCnt < this.conditionalStates.size()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class VariableStatesInTime extends StatesInTime {
|
||||
Function<LivingEntityPatch<?>, Float> variableStart;
|
||||
Function<LivingEntityPatch<?>, Float> variableEnd;
|
||||
Map<StateFactor<?>, Object> states = Maps.newHashMap();
|
||||
|
||||
public VariableStatesInTime(Function<LivingEntityPatch<?>, Float> variableStart, Function<LivingEntityPatch<?>, Float> variableEnd) {
|
||||
this.variableStart = variableStart;
|
||||
this.variableEnd = variableEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIn(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.variableStart.apply(entitypatch) <= time && this.variableEnd.apply(entitypatch) > time;
|
||||
}
|
||||
|
||||
public <T> StatesInTime addState(StateFactor<T> factor, T val) {
|
||||
this.states.put(factor, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<StateFactor<?>, Object>> getStates(LivingEntityPatch<?> entitypatch) {
|
||||
return this.states.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasState(StateFactor<?> state) {
|
||||
return this.states.containsKey(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeState(StateFactor<?> state) {
|
||||
this.states.remove(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("States: %s", this.states);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Blueprint {
|
||||
StatesInTime currentState;
|
||||
Set<StatesInTime> timePairs = Sets.newHashSet();
|
||||
|
||||
public Blueprint newTimePair(float start, float end) {
|
||||
this.currentState = new SimpleStatesInTime(start, end);
|
||||
this.timePairs.add(this.currentState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Blueprint newConditionalTimePair(Function<LivingEntityPatch<?>, Integer> condition, float start, float end) {
|
||||
this.currentState = new ConditionalStatesInTime(condition, start, end);
|
||||
this.timePairs.add(this.currentState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Blueprint newVariableTimePair(Function<LivingEntityPatch<?>, Float> variableStart, Function<LivingEntityPatch<?>, Float> variableEnd) {
|
||||
this.currentState = new VariableStatesInTime(variableStart, variableEnd);
|
||||
this.timePairs.add(this.currentState);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Blueprint addState(StateFactor<T> factor, T val) {
|
||||
if (this.currentState instanceof SimpleStatesInTime simpleState) {
|
||||
simpleState.addState(factor, val);
|
||||
}
|
||||
if (this.currentState instanceof VariableStatesInTime variableState) {
|
||||
variableState.addState(factor, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Blueprint addConditionalState(int metadata, StateFactor<T> factor, T val) {
|
||||
if (this.currentState instanceof ConditionalStatesInTime conditionalState) {
|
||||
conditionalState.addConditionalState(metadata, factor, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Blueprint removeState(StateFactor<T> factor) {
|
||||
for (StatesInTime timePair : this.timePairs) {
|
||||
timePair.removeState(factor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Blueprint addStateRemoveOld(StateFactor<T> factor, T val) {
|
||||
this.removeState(factor);
|
||||
return this.addState(factor, val);
|
||||
}
|
||||
|
||||
public <T> Blueprint addStateIfNotExist(StateFactor<T> factor, T val) {
|
||||
for (StatesInTime timePair : this.timePairs) {
|
||||
if (timePair.hasState(factor)) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
return this.addState(factor, val);
|
||||
}
|
||||
|
||||
public Blueprint clear() {
|
||||
this.currentState = null;
|
||||
this.timePairs.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StatesInTime state : this.timePairs) {
|
||||
sb.append(state + "\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim.types;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables;
|
||||
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Keyframe;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationEvent;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationEvent.SimpleEvent;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationParameters;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackSpeedModifier;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.anim.client.Layer.LayerType;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
|
||||
import com.tiedup.remake.rig.anim.client.property.TrailInfo;
|
||||
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsProvider;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulatable;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulator;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulator.BakedInverseKinematicsDefinition;
|
||||
import com.tiedup.remake.rig.physics.ik.InverseKinematicsSimulator.InverseKinematicsObject;
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
import com.tiedup.remake.rig.TiedUpRigRegistry;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import com.tiedup.remake.rig.patch.PlayerPatch;
|
||||
|
||||
public class StaticAnimation extends DynamicAnimation implements InverseKinematicsProvider {
|
||||
public static final IndependentAnimationVariableKey<Boolean> HAD_NO_PHYSICS = AnimationVariables.independent((animator) -> false, true);
|
||||
|
||||
public static String getFileHash(ResourceLocation rl) {
|
||||
String fileHash;
|
||||
|
||||
try {
|
||||
JsonAssetLoader jsonfile = new JsonAssetLoader(AnimationManager.getAnimationResourceManager(), rl);
|
||||
fileHash = jsonfile.getFileHash();
|
||||
} catch (AssetLoadingException e) {
|
||||
fileHash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
return fileHash;
|
||||
}
|
||||
|
||||
protected final Map<AnimationProperty<?>, Object> properties = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* States will bind into animation on {@link AnimationManager#apply}
|
||||
*/
|
||||
protected final StateSpectrum.Blueprint stateSpectrumBlueprint = new StateSpectrum.Blueprint();
|
||||
protected final StateSpectrum stateSpectrum = new StateSpectrum();
|
||||
protected final AssetAccessor<? extends Armature> armature;
|
||||
protected ResourceLocation resourceLocation;
|
||||
protected AnimationAccessor<? extends StaticAnimation> accessor;
|
||||
private final String filehash;
|
||||
|
||||
public StaticAnimation() {
|
||||
super(0.0F, true);
|
||||
|
||||
this.resourceLocation = TiedUpRigConstants.identifier("emtpy");
|
||||
this.armature = null;
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
public StaticAnimation(boolean isRepeat, AnimationAccessor<? extends StaticAnimation> accessor, AssetAccessor<? extends Armature> armature) {
|
||||
this(TiedUpRigConstants.GENERAL_ANIMATION_TRANSITION_TIME, isRepeat, accessor, armature);
|
||||
}
|
||||
|
||||
public StaticAnimation(float transitionTime, boolean isRepeat, AnimationAccessor<? extends StaticAnimation> accessor, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat);
|
||||
|
||||
this.resourceLocation = ResourceLocation.fromNamespaceAndPath(accessor.registryName().getNamespace(), "animmodels/animations/" + accessor.registryName().getPath() + ".json");
|
||||
|
||||
this.armature = armature;
|
||||
this.accessor = accessor;
|
||||
this.filehash = getFileHash(this.resourceLocation);
|
||||
}
|
||||
|
||||
/* Resourcepack animations — transitionTime par défaut */
|
||||
public StaticAnimation(boolean isRepeat, String path, AssetAccessor<? extends Armature> armature) {
|
||||
this(TiedUpRigConstants.GENERAL_ANIMATION_TRANSITION_TIME, isRepeat, path, armature);
|
||||
}
|
||||
|
||||
/* Resourcepack animations */
|
||||
public StaticAnimation(float transitionTime, boolean isRepeat, String path, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat);
|
||||
|
||||
ResourceLocation registryName = ResourceLocation.parse(path);
|
||||
this.resourceLocation = ResourceLocation.fromNamespaceAndPath(registryName.getNamespace(), "animmodels/animations/" + registryName.getPath() + ".json");
|
||||
this.armature = armature;
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
/* Multilayer Constructor */
|
||||
public StaticAnimation(ResourceLocation fileLocation, float transitionTime, boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
|
||||
super(transitionTime, isRepeat);
|
||||
|
||||
this.resourceLocation = fileLocation;
|
||||
this.armature = armature;
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
public void loadAnimation() {
|
||||
if (!this.isMetaAnimation()) {
|
||||
if (this.properties.containsKey(StaticAnimationProperty.IK_DEFINITION)) {
|
||||
this.animationClip = AnimationManager.getInstance().loadAnimationClip(this, JsonAssetLoader::loadAllJointsClipForAnimation);
|
||||
|
||||
this.getProperty(StaticAnimationProperty.IK_DEFINITION).ifPresent(ikDefinitions -> {
|
||||
boolean correctY = this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false);
|
||||
boolean correctZ = this.isMainFrameAnimation();
|
||||
|
||||
List<BakedInverseKinematicsDefinition> bakedIKDefinitionList = ikDefinitions.stream().map(ikDefinition -> ikDefinition.bake(this.armature, this.animationClip.getJointTransforms(), correctY, correctZ)).toList();
|
||||
this.addProperty(StaticAnimationProperty.BAKED_IK_DEFINITION, bakedIKDefinitionList);
|
||||
|
||||
// Remove the unbaked data
|
||||
this.properties.remove(StaticAnimationProperty.IK_DEFINITION);
|
||||
});
|
||||
} else {
|
||||
this.animationClip = AnimationManager.getInstance().loadAnimationClip(this, JsonAssetLoader::loadClipForAnimation);
|
||||
}
|
||||
|
||||
this.animationClip.bakeKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
public void postInit() {
|
||||
this.stateSpectrum.readFrom(this.stateSpectrumBlueprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationClip getAnimationClip() {
|
||||
if (this.animationClip == null) {
|
||||
this.loadAnimation();
|
||||
}
|
||||
|
||||
return this.animationClip;
|
||||
}
|
||||
|
||||
public void setLinkAnimation(final AssetAccessor<? extends DynamicAnimation> fromAnimation, Pose startPose, boolean isOnSameLayer, float transitionTimeModifier, LivingEntityPatch<?> entitypatch, LinkAnimation dest) {
|
||||
if (!entitypatch.isLogicalClient()) {
|
||||
startPose = TiedUpRigRegistry.EMPTY_ANIMATION.getPoseByTime(entitypatch, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
dest.resetNextStartTime();
|
||||
|
||||
float playTime = this.getPlaySpeed(entitypatch, dest);
|
||||
PlaybackSpeedModifier playSpeedModifier = this.getRealAnimation().get().getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).orElse(null);
|
||||
|
||||
if (playSpeedModifier != null) {
|
||||
playTime = playSpeedModifier.modify(dest, entitypatch, playTime, 0.0F, playTime);
|
||||
}
|
||||
|
||||
playTime = Math.abs(playTime);
|
||||
playTime *= TiedUpRigConstants.A_TICK;
|
||||
|
||||
float linkTime = transitionTimeModifier > 0.0F ? transitionTimeModifier + this.transitionTime : this.transitionTime;
|
||||
float totalTime = playTime * (int)Math.ceil(linkTime / playTime);
|
||||
float nextStartTime = Math.max(0.0F, -transitionTimeModifier);
|
||||
nextStartTime += totalTime - linkTime;
|
||||
|
||||
dest.setNextStartTime(nextStartTime);
|
||||
dest.getAnimationClip().reset();
|
||||
dest.setTotalTime(totalTime);
|
||||
dest.setConnectedAnimations(fromAnimation, this.getAccessor());
|
||||
|
||||
Map<String, JointTransform> data1 = startPose.getJointTransformData();
|
||||
Map<String, JointTransform> data2 = this.getPoseByTime(entitypatch, nextStartTime, 0.0F).getJointTransformData();
|
||||
Set<String> joint1 = new HashSet<> (isOnSameLayer ? data1.keySet() : Set.of());
|
||||
Set<String> joint2 = new HashSet<> (data2.keySet());
|
||||
|
||||
if (entitypatch.isLogicalClient()) {
|
||||
JointMaskEntry entry = fromAnimation.get().getJointMaskEntry(entitypatch, false).orElse(null);
|
||||
JointMaskEntry entry2 = this.getJointMaskEntry(entitypatch, true).orElse(null);
|
||||
|
||||
if (entry != null) {
|
||||
joint1.removeIf((jointName) -> entry.isMasked(fromAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
|
||||
entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion(), jointName));
|
||||
}
|
||||
|
||||
if (entry2 != null) {
|
||||
joint2.removeIf((jointName) -> entry2.isMasked(this.getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
|
||||
entitypatch.getCurrentLivingMotion() : entitypatch.currentCompositeMotion, jointName));
|
||||
}
|
||||
}
|
||||
|
||||
joint1.addAll(joint2);
|
||||
|
||||
if (linkTime != totalTime) {
|
||||
Map<String, JointTransform> firstPose = this.getPoseByTime(entitypatch, 0.0F, 0.0F).getJointTransformData();
|
||||
|
||||
for (String jointName : joint1) {
|
||||
Keyframe[] keyframes = new Keyframe[3];
|
||||
keyframes[0] = new Keyframe(0.0F, data1.get(jointName));
|
||||
keyframes[1] = new Keyframe(linkTime, firstPose.get(jointName));
|
||||
keyframes[2] = new Keyframe(totalTime, data2.get(jointName));
|
||||
TransformSheet sheet = new TransformSheet(keyframes);
|
||||
dest.getAnimationClip().addJointTransform(jointName, sheet);
|
||||
}
|
||||
} else {
|
||||
for (String jointName : joint1) {
|
||||
Keyframe[] keyframes = new Keyframe[2];
|
||||
keyframes[0] = new Keyframe(0.0F, data1.get(jointName));
|
||||
keyframes[1] = new Keyframe(totalTime, data2.get(jointName));
|
||||
TransformSheet sheet = new TransformSheet(keyframes);
|
||||
dest.getAnimationClip().addJointTransform(jointName, sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(LivingEntityPatch<?> entitypatch) {
|
||||
// Load if null
|
||||
this.getAnimationClip();
|
||||
|
||||
// Please fix this implementation when minecraft supports any mixinable method that returns noPhysics variable
|
||||
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent(val -> {
|
||||
if (val) {
|
||||
entitypatch.getAnimator().getVariables().put(HAD_NO_PHYSICS, this.getAccessor(), entitypatch.getOriginal().noPhysics);
|
||||
entitypatch.getOriginal().noPhysics = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (entitypatch.isLogicalClient()) {
|
||||
this.getProperty(ClientAnimationProperties.TRAIL_EFFECT).ifPresent(trailInfos -> {
|
||||
int idx = 0;
|
||||
|
||||
for (TrailInfo trailInfo : trailInfos) {
|
||||
double eid = Double.longBitsToDouble((long)entitypatch.getOriginal().getId());
|
||||
double animid = Double.longBitsToDouble((long)this.getId());
|
||||
double jointId = Double.longBitsToDouble((long)this.armature.get().searchJointByName(trailInfo.joint()).getId());
|
||||
double index = Double.longBitsToDouble((long)idx++);
|
||||
|
||||
// RIG : RenderItemBase (combat weapon item render) strippé —
|
||||
// TiedUp n'a pas d'items "actifs" porteurs de trails comme
|
||||
// les weapons EF. Le trailInfo reste tel quel de la définition.
|
||||
|
||||
if (!trailInfo.playable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entitypatch.getOriginal().level().addParticle(trailInfo.particle(), eid, 0, animid, jointId, index, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.getProperty(StaticAnimationProperty.ON_BEGIN_EVENTS).ifPresent(events -> {
|
||||
for (SimpleEvent<?> event : events) {
|
||||
event.execute(entitypatch, this.getAccessor(), 0.0F, 0.0F);
|
||||
}
|
||||
});
|
||||
|
||||
// RIG : PlayerEventListener (combat animation events) strippé.
|
||||
// Les ON_BEGIN_EVENTS SimpleEvent continuent de fonctionner ci-dessus.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
|
||||
// RIG : ANIMATION_END_EVENT fire strippé (PlayerEventListener combat).
|
||||
|
||||
this.getProperty(StaticAnimationProperty.ON_END_EVENTS).ifPresent((events) -> {
|
||||
for (SimpleEvent<?> event : events) {
|
||||
event.executeWithNewParams(entitypatch, this.getAccessor(), this.getTotalTime(), this.getTotalTime(), event.getParameters() == null ? AnimationParameters.of(isEnd) : AnimationParameters.addParameter(event.getParameters(), isEnd));
|
||||
}
|
||||
});
|
||||
|
||||
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent((val) -> {
|
||||
if (val) {
|
||||
entitypatch.getOriginal().noPhysics = entitypatch.getAnimator().getVariables().getOrDefault(HAD_NO_PHYSICS, this.getAccessor());
|
||||
}
|
||||
});
|
||||
|
||||
entitypatch.getAnimator().getVariables().removeAll(this.getAccessor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(LivingEntityPatch<?> entitypatch) {
|
||||
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent((val) -> {
|
||||
if (val) {
|
||||
entitypatch.getOriginal().noPhysics = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.getProperty(StaticAnimationProperty.TICK_EVENTS).ifPresent((events) -> {
|
||||
entitypatch.getAnimator().getPlayer(this.getAccessor()).ifPresent(player -> {
|
||||
for (AnimationEvent<?, ?> event : events) {
|
||||
float prevElapsed = player.getPrevElapsedTime();
|
||||
float elapsed = player.getElapsedTime();
|
||||
|
||||
event.execute(entitypatch, this.getAccessor(), prevElapsed, elapsed);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return new EntityState(this.getStatesMap(entitypatch, time));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.stateSpectrum.getStateMap(entitypatch, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
|
||||
return this.stateSpectrum.getSingleState(stateFactor, entitypatch, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
|
||||
return this.getProperty(ClientAnimationProperties.JOINT_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
|
||||
entitypatch.poseTick(animation, pose, time, partialTicks);
|
||||
|
||||
this.getProperty(StaticAnimationProperty.POSE_MODIFIER).ifPresent((poseModifier) -> {
|
||||
poseModifier.modify(animation, pose, entitypatch, time, partialTicks);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticAnimation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesHeadRotFollowEntityHead() {
|
||||
return !this.getProperty(StaticAnimationProperty.FIXED_HEAD_ROTATION).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return this.accessor.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof StaticAnimation staticAnimation) {
|
||||
if (this.accessor != null && staticAnimation.accessor != null) {
|
||||
return this.getId() == staticAnimation.getId();
|
||||
}
|
||||
}
|
||||
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
public ResourceLocation getLocation() {
|
||||
return this.resourceLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getRegistryName() {
|
||||
return this.accessor.registryName();
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends Armature> getArmature() {
|
||||
return this.armature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformSheet getCoord() {
|
||||
return this.getProperty(ActionAnimationProperty.COORD).orElse(super.getCoord());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String classPath = this.getClass().toString();
|
||||
return classPath.substring(classPath.lastIndexOf(".") + 1) + " " + this.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal use only
|
||||
*/
|
||||
@Deprecated
|
||||
public StaticAnimation addPropertyUnsafe(AnimationProperty<?> propertyType, Object value) {
|
||||
this.properties.put(propertyType, value);
|
||||
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addPropertyUnsafe(propertyType, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A extends StaticAnimation, V> A addProperty(StaticAnimationProperty<V> propertyType, V value) {
|
||||
this.properties.put(propertyType, value);
|
||||
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addProperty(propertyType, value));
|
||||
return (A)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
|
||||
return (Optional<V>) Optional.ofNullable(this.properties.get(propertyType));
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Layer.Priority getPriority() {
|
||||
return this.getProperty(ClientAnimationProperties.PRIORITY).orElse(Layer.Priority.LOWEST);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Layer.LayerType getLayerType() {
|
||||
return this.getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(LayerType.BASE_LAYER);
|
||||
}
|
||||
|
||||
public Object getModifiedLinkState(StateFactor<?> factor, Object val, LivingEntityPatch<?> entitypatch, float elapsedTime) {
|
||||
return val;
|
||||
}
|
||||
|
||||
public List<AssetAccessor<? extends StaticAnimation>> getSubAnimations() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationAccessor<? extends StaticAnimation> getRealAnimation() {
|
||||
return this.getAccessor();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <A extends DynamicAnimation> AnimationAccessor<A> getAccessor() {
|
||||
return (AnimationAccessor<A>)this.accessor;
|
||||
}
|
||||
|
||||
public void setAccessor(AnimationAccessor<? extends StaticAnimation> accessor) {
|
||||
this.accessor = accessor;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void renderDebugging(PoseStack poseStack, MultiBufferSource buffer, LivingEntityPatch<?> entitypatch, float playTime, float partialTicks) {
|
||||
// RIG : debug render des targets IK (RenderingTool.drawQuad) strippé.
|
||||
// Pas d'IK en TiedUp. Reactivable Phase 2+ avec un helper drawQuad
|
||||
// simple si on veut debug les joints.
|
||||
}
|
||||
|
||||
@Override
|
||||
public InverseKinematicsObject createSimulationData(InverseKinematicsProvider provider, InverseKinematicsSimulatable simOwner, InverseKinematicsSimulator.InverseKinematicsBuilder simBuilder) {
|
||||
return new InverseKinematicsObject(simBuilder);
|
||||
}
|
||||
}
|
||||
269
src/main/java/com/tiedup/remake/rig/armature/Armature.java
Normal file
269
src/main/java/com/tiedup/remake/rig/armature/Armature.java
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class Armature {
|
||||
private final String name;
|
||||
private final Int2ObjectMap<Joint> jointById;
|
||||
private final Map<String, Joint> jointByName;
|
||||
private final Map<String, Joint.HierarchicalJointAccessor> pathIndexMap;
|
||||
private final int jointCount;
|
||||
private final OpenMatrix4f[] poseMatrices;
|
||||
public final Joint rootJoint;
|
||||
|
||||
public Armature(String name, int jointNumber, Joint rootJoint, Map<String, Joint> jointMap) {
|
||||
this.name = name;
|
||||
this.jointCount = jointNumber;
|
||||
this.rootJoint = rootJoint;
|
||||
this.jointByName = jointMap;
|
||||
this.jointById = new Int2ObjectOpenHashMap<>();
|
||||
this.pathIndexMap = Maps.newHashMap();
|
||||
|
||||
this.jointByName.values().forEach((joint) -> {
|
||||
this.jointById.put(joint.getId(), joint);
|
||||
});
|
||||
|
||||
this.poseMatrices = OpenMatrix4f.allocateMatrixArray(this.jointCount);
|
||||
}
|
||||
|
||||
protected Joint getOrLogException(Map<String, Joint> jointMap, String name) {
|
||||
if (!jointMap.containsKey(name)) {
|
||||
if (TiedUpRigConstants.IS_DEV_ENV) {
|
||||
TiedUpRigConstants.LOGGER.debug("Cannot find the joint named " + name + " in " + this.getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
return Joint.EMPTY;
|
||||
}
|
||||
|
||||
return jointMap.get(name);
|
||||
}
|
||||
|
||||
public void setPose(Pose pose) {
|
||||
this.getPoseTransform(this.rootJoint, new OpenMatrix4f(), pose, this.poseMatrices, false);
|
||||
}
|
||||
|
||||
public void bakeOriginMatrices() {
|
||||
this.rootJoint.initOriginTransform(new OpenMatrix4f());
|
||||
}
|
||||
|
||||
public OpenMatrix4f[] getPoseMatrices() {
|
||||
return this.poseMatrices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param applyOriginTransform if you need a final pose of the animations, give it false.
|
||||
*/
|
||||
public OpenMatrix4f[] getPoseAsTransformMatrix(Pose pose, boolean applyOriginTransform) {
|
||||
OpenMatrix4f[] jointMatrices = new OpenMatrix4f[this.jointCount];
|
||||
this.getPoseTransform(this.rootJoint, new OpenMatrix4f(), pose, jointMatrices, applyOriginTransform);
|
||||
return jointMatrices;
|
||||
}
|
||||
|
||||
private void getPoseTransform(Joint joint, OpenMatrix4f parentTransform, Pose pose, OpenMatrix4f[] jointMatrices, boolean applyOriginTransform) {
|
||||
OpenMatrix4f result = pose.orElseEmpty(joint.getName()).getAnimationBoundMatrix(joint, parentTransform);
|
||||
jointMatrices[joint.getId()] = result;
|
||||
|
||||
for (Joint joints : joint.getSubJoints()) {
|
||||
this.getPoseTransform(joints, result, pose, jointMatrices, applyOriginTransform);
|
||||
}
|
||||
|
||||
if (applyOriginTransform) {
|
||||
result.mulBack(joint.getToOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inapposite past perfect
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public OpenMatrix4f getBindedTransformFor(Pose pose, Joint joint) {
|
||||
return this.getBoundTransformByJointIndex(pose, this.searchPathIndex(joint.getName()).createAccessTicket(this.rootJoint));
|
||||
}
|
||||
|
||||
public OpenMatrix4f getBoundTransformFor(Pose pose, Joint joint) {
|
||||
return this.getBoundTransformByJointIndex(pose, this.searchPathIndex(joint.getName()).createAccessTicket(this.rootJoint));
|
||||
}
|
||||
|
||||
public OpenMatrix4f getBoundTransformByJointIndex(Pose pose, Joint.AccessTicket pathIndices) {
|
||||
return this.getBoundJointTransformRecursively(pose, this.rootJoint, new OpenMatrix4f(), pathIndices);
|
||||
}
|
||||
|
||||
private OpenMatrix4f getBoundJointTransformRecursively(Pose pose, Joint joint, OpenMatrix4f parentTransform, Joint.AccessTicket pathIndices) {
|
||||
JointTransform jt = pose.orElseEmpty(joint.getName());
|
||||
OpenMatrix4f result = jt.getAnimationBoundMatrix(joint, parentTransform);
|
||||
|
||||
return pathIndices.hasNext() ? this.getBoundJointTransformRecursively(pose, pathIndices.next(), result, pathIndices) : result;
|
||||
}
|
||||
|
||||
public boolean hasJoint(String name) {
|
||||
return this.jointByName.containsKey(name);
|
||||
}
|
||||
|
||||
public Joint searchJointById(int id) {
|
||||
return this.jointById.get(id);
|
||||
}
|
||||
|
||||
public Joint searchJointByName(String name) {
|
||||
return this.jointByName.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and record joint path from root to terminal
|
||||
*
|
||||
* @param terminalJointName
|
||||
* @return
|
||||
*/
|
||||
public Joint.HierarchicalJointAccessor searchPathIndex(String terminalJointName) {
|
||||
return this.searchPathIndex(this.rootJoint, terminalJointName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and record joint path to terminal
|
||||
*
|
||||
* @param start
|
||||
* @param terminalJointName
|
||||
* @return
|
||||
*/
|
||||
public Joint.HierarchicalJointAccessor searchPathIndex(Joint start, String terminalJointName) {
|
||||
String signature = start.getName() + "-" + terminalJointName;
|
||||
|
||||
if (this.pathIndexMap.containsKey(signature)) {
|
||||
return this.pathIndexMap.get(signature);
|
||||
} else {
|
||||
Joint.HierarchicalJointAccessor.Builder pathBuilder = start.searchPath(Joint.HierarchicalJointAccessor.builder(), terminalJointName);
|
||||
Joint.HierarchicalJointAccessor accessor;
|
||||
|
||||
if (pathBuilder == null) {
|
||||
throw new IllegalArgumentException("Failed to get joint path index for " + terminalJointName);
|
||||
} else {
|
||||
accessor = pathBuilder.build();
|
||||
this.pathIndexMap.put(signature, accessor);
|
||||
}
|
||||
|
||||
return accessor;
|
||||
}
|
||||
}
|
||||
|
||||
public void gatherAllJointsInPathToTerminal(String terminalJointName, Collection<String> jointsInPath) {
|
||||
if (!this.jointByName.containsKey(terminalJointName)) {
|
||||
throw new NoSuchElementException("No " + terminalJointName + " joint in this armature!");
|
||||
}
|
||||
|
||||
Joint.HierarchicalJointAccessor pathIndices = this.searchPathIndex(terminalJointName);
|
||||
Joint.AccessTicket accessTicket = pathIndices.createAccessTicket(this.rootJoint);
|
||||
|
||||
Joint joint = this.rootJoint;
|
||||
jointsInPath.add(joint.getName());
|
||||
|
||||
while (accessTicket.hasNext()) {
|
||||
jointsInPath.add(accessTicket.next().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public int getJointNumber() {
|
||||
return this.jointCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Armature deepCopy() {
|
||||
Map<String, Joint> oldToNewJoint = Maps.newHashMap();
|
||||
oldToNewJoint.put("empty", Joint.EMPTY);
|
||||
|
||||
Joint newRoot = this.copyHierarchy(this.rootJoint, oldToNewJoint);
|
||||
newRoot.initOriginTransform(new OpenMatrix4f());
|
||||
Armature newArmature = null;
|
||||
|
||||
// Uses reflection to keep the type of copied armature
|
||||
try {
|
||||
Constructor<? extends Armature> constructor = this.getClass().getConstructor(String.class, int.class, Joint.class, Map.class);
|
||||
newArmature = constructor.newInstance(this.name, this.jointCount, newRoot, oldToNewJoint);
|
||||
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new IllegalStateException("Armature copy failed! " + e);
|
||||
}
|
||||
|
||||
return newArmature;
|
||||
}
|
||||
|
||||
private Joint copyHierarchy(Joint joint, Map<String, Joint> oldToNewJoint) {
|
||||
if (joint == Joint.EMPTY) {
|
||||
return Joint.EMPTY;
|
||||
}
|
||||
|
||||
Joint newJoint = new Joint(joint.getName(), joint.getId(), joint.getLocalTransform());
|
||||
oldToNewJoint.put(joint.getName(), newJoint);
|
||||
|
||||
for (Joint subJoint : joint.getSubJoints()) {
|
||||
newJoint.addSubJoints(this.copyHierarchy(subJoint, oldToNewJoint));
|
||||
}
|
||||
|
||||
return newJoint;
|
||||
}
|
||||
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject root = new JsonObject();
|
||||
JsonObject armature = new JsonObject();
|
||||
|
||||
JsonArray jointNamesArray = new JsonArray();
|
||||
JsonArray jointHierarchy = new JsonArray();
|
||||
|
||||
this.jointById.int2ObjectEntrySet().stream().sorted((entry1, entry2) -> Integer.compare(entry1.getIntKey(), entry2.getIntKey())).forEach((entry) -> jointNamesArray.add(entry.getValue().getName()));
|
||||
armature.add("joints", jointNamesArray);
|
||||
armature.add("hierarchy", jointHierarchy);
|
||||
|
||||
exportJoint(jointHierarchy, this.rootJoint, true);
|
||||
|
||||
root.add("armature", armature);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static void exportJoint(JsonArray parent, Joint joint, boolean root) {
|
||||
JsonObject jointJson = new JsonObject();
|
||||
jointJson.addProperty("name", joint.getName());
|
||||
|
||||
JsonArray transformMatrix = new JsonArray();
|
||||
OpenMatrix4f localMatrixInBlender = new OpenMatrix4f(joint.getLocalTransform());
|
||||
|
||||
if (root) {
|
||||
localMatrixInBlender.mulFront(OpenMatrix4f.invert(JsonAssetLoader.BLENDER_TO_MINECRAFT_COORD, null));
|
||||
}
|
||||
|
||||
localMatrixInBlender.transpose();
|
||||
localMatrixInBlender.toList().forEach(transformMatrix::add);
|
||||
jointJson.add("transform", transformMatrix);
|
||||
parent.add(jointJson);
|
||||
|
||||
if (!joint.getSubJoints().isEmpty()) {
|
||||
JsonArray children = new JsonArray();
|
||||
jointJson.add("children", children);
|
||||
joint.getSubJoints().forEach((joint$2) -> exportJoint(children, joint$2, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.armature.types.HumanLikeArmature;
|
||||
|
||||
public class HumanoidArmature extends Armature implements HumanLikeArmature {
|
||||
public final Joint thighR;
|
||||
public final Joint legR;
|
||||
public final Joint kneeR;
|
||||
public final Joint thighL;
|
||||
public final Joint legL;
|
||||
public final Joint kneeL;
|
||||
public final Joint torso;
|
||||
public final Joint chest;
|
||||
public final Joint head;
|
||||
public final Joint shoulderR;
|
||||
public final Joint armR;
|
||||
public final Joint handR;
|
||||
public final Joint toolR;
|
||||
public final Joint elbowR;
|
||||
public final Joint shoulderL;
|
||||
public final Joint armL;
|
||||
public final Joint handL;
|
||||
public final Joint toolL;
|
||||
public final Joint elbowL;
|
||||
|
||||
public HumanoidArmature(String name, int jointNumber, Joint rootJoint, Map<String, Joint> jointMap) {
|
||||
super(name, jointNumber, rootJoint, jointMap);
|
||||
|
||||
this.thighR = this.getOrLogException(jointMap, "Thigh_R");
|
||||
this.legR = this.getOrLogException(jointMap, "Leg_R");
|
||||
this.kneeR = this.getOrLogException(jointMap, "Knee_R");
|
||||
this.thighL = this.getOrLogException(jointMap, "Thigh_L");
|
||||
this.legL = this.getOrLogException(jointMap, "Leg_L");
|
||||
this.kneeL = this.getOrLogException(jointMap, "Knee_L");
|
||||
this.torso = this.getOrLogException(jointMap, "Torso");
|
||||
this.chest = this.getOrLogException(jointMap, "Chest");
|
||||
this.head = this.getOrLogException(jointMap, "Head");
|
||||
this.shoulderR = this.getOrLogException(jointMap, "Shoulder_R");
|
||||
this.armR = this.getOrLogException(jointMap, "Arm_R");
|
||||
this.handR = this.getOrLogException(jointMap, "Hand_R");
|
||||
this.toolR = this.getOrLogException(jointMap, "Tool_R");
|
||||
this.elbowR = this.getOrLogException(jointMap, "Elbow_R");
|
||||
this.shoulderL = this.getOrLogException(jointMap, "Shoulder_L");
|
||||
this.armL = this.getOrLogException(jointMap, "Arm_L");
|
||||
this.handL = this.getOrLogException(jointMap, "Hand_L");
|
||||
this.toolL = this.getOrLogException(jointMap, "Tool_L");
|
||||
this.elbowL = this.getOrLogException(jointMap, "Elbow_L");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftToolJoint() {
|
||||
return this.toolL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightToolJoint() {
|
||||
return this.toolR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint backToolJoint() {
|
||||
return this.chest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftHandJoint() {
|
||||
return this.handL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightHandJoint() {
|
||||
return this.handR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftArmJoint() {
|
||||
return this.armL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightArmJoint() {
|
||||
return this.armR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftLegJoint() {
|
||||
return this.legL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightLegJoint() {
|
||||
return this.legR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftThighJoint() {
|
||||
return this.thighL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightThighJoint() {
|
||||
return this.thighR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint headJoint() {
|
||||
return this.head;
|
||||
}
|
||||
}
|
||||
278
src/main/java/com/tiedup/remake/rig/armature/Joint.java
Normal file
278
src/main/java/com/tiedup/remake/rig/armature/Joint.java
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public class Joint {
|
||||
public static final Joint EMPTY = new Joint("empty", -1, new OpenMatrix4f());
|
||||
|
||||
private final List<Joint> subJoints = Lists.newArrayList();
|
||||
private final int jointId;
|
||||
private final String jointName;
|
||||
private final OpenMatrix4f localTransform;
|
||||
private final OpenMatrix4f toOrigin = new OpenMatrix4f();
|
||||
|
||||
public Joint(String name, int jointId, OpenMatrix4f localTransform) {
|
||||
this.jointId = jointId;
|
||||
this.jointName = name;
|
||||
this.localTransform = localTransform.unmodifiable();
|
||||
}
|
||||
|
||||
public void addSubJoints(Joint... joints) {
|
||||
for (Joint joint : joints) {
|
||||
if (!this.subJoints.contains(joint)) {
|
||||
this.subJoints.add(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSubJoints(Joint... joints) {
|
||||
for (Joint joint : joints) {
|
||||
this.subJoints.remove(joint);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Joint> getAllJoints() {
|
||||
List<Joint> list = Lists.newArrayList();
|
||||
this.getSubJoints(list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void iterSubJoints(Consumer<Joint> iterTask) {
|
||||
iterTask.accept(this);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.iterSubJoints(iterTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void getSubJoints(List<Joint> list) {
|
||||
list.add(this);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.getSubJoints(list);
|
||||
}
|
||||
}
|
||||
|
||||
public void initOriginTransform(OpenMatrix4f parentTransform) {
|
||||
OpenMatrix4f modelTransform = OpenMatrix4f.mul(parentTransform, this.localTransform, null);
|
||||
OpenMatrix4f.invert(modelTransform, this.toOrigin);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.initOriginTransform(modelTransform);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f getLocalTransform() {
|
||||
return this.localTransform;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getToOrigin() {
|
||||
return this.toOrigin;
|
||||
}
|
||||
|
||||
public List<Joint> getSubJoints() {
|
||||
return this.subJoints;
|
||||
}
|
||||
|
||||
// Null if index out of range
|
||||
@Nullable
|
||||
public Joint getSubJoint(int index) {
|
||||
if (index < 0 || this.subJoints.size() <= index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.subJoints.get(index);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.jointName;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.jointId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Joint joint) {
|
||||
return this.jointName.equals(joint.jointName) && this.jointId == joint.jointId;
|
||||
} else {
|
||||
return super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.jointName.hashCode() ^ this.jointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the method that memorize path search results. {@link Armature#searchPathIndex(Joint, String)}
|
||||
*
|
||||
* @param builder
|
||||
* @param jointName
|
||||
* @return
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public HierarchicalJointAccessor.Builder searchPath(HierarchicalJointAccessor.Builder builder, String jointName) {
|
||||
if (jointName.equals(this.getName())) {
|
||||
return builder;
|
||||
} else {
|
||||
int i = 0;
|
||||
|
||||
for (Joint subJoint : this.subJoints) {
|
||||
HierarchicalJointAccessor.Builder nextBuilder = subJoint.searchPath(builder.append(i), jointName);
|
||||
i++;
|
||||
|
||||
if (nextBuilder != null) {
|
||||
return nextBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nid: " + this.jointId);
|
||||
sb.append("\nname: " + this.jointName);
|
||||
sb.append("\nlocal transform: " + this.localTransform);
|
||||
sb.append("\nto origin: " + this.toOrigin);
|
||||
sb.append("\nchildren: [");
|
||||
|
||||
int idx = 0;
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
idx++;
|
||||
sb.append(joint.jointName);
|
||||
|
||||
if (idx != this.subJoints.size()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("]\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String printIncludingChildren() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.toString());
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
sb.append(joint.printIncludingChildren());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class HierarchicalJointAccessor {
|
||||
private Queue<Integer> indicesToTerminal;
|
||||
private final String signature;
|
||||
|
||||
private HierarchicalJointAccessor(Builder builder) {
|
||||
this.indicesToTerminal = builder.indicesToTerminal;
|
||||
this.signature = builder.signature;
|
||||
}
|
||||
|
||||
public AccessTicket createAccessTicket(Joint rootJoint) {
|
||||
return new AccessTicket(this.indicesToTerminal, rootJoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof HierarchicalJointAccessor accessor) {
|
||||
this.signature.equals(accessor.signature);
|
||||
}
|
||||
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.signature.hashCode();
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder(new LinkedList<> (), "");
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Queue<Integer> indicesToTerminal;
|
||||
private String signature;
|
||||
|
||||
private Builder(Queue<Integer> indicesToTerminal, String signature) {
|
||||
this.indicesToTerminal = indicesToTerminal;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public Builder append(int index) {
|
||||
String signatureNext;
|
||||
|
||||
if (this.indicesToTerminal.isEmpty()) {
|
||||
signatureNext = this.signature + String.valueOf(index);
|
||||
} else {
|
||||
signatureNext = this.signature + "-" + String.valueOf(index);
|
||||
}
|
||||
|
||||
Queue<Integer> nextQueue = new LinkedList<> (this.indicesToTerminal);
|
||||
nextQueue.add(index);
|
||||
|
||||
return new Builder(nextQueue, signatureNext);
|
||||
}
|
||||
|
||||
public HierarchicalJointAccessor build() {
|
||||
return new HierarchicalJointAccessor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccessTicket implements Iterator<Joint> {
|
||||
Queue<Integer> accecssStack;
|
||||
Joint joint;
|
||||
|
||||
private AccessTicket(Queue<Integer> indicesToTerminal, Joint rootJoint) {
|
||||
this.accecssStack = new LinkedList<> (indicesToTerminal);
|
||||
this.joint = rootJoint;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return !this.accecssStack.isEmpty();
|
||||
}
|
||||
|
||||
public Joint next() {
|
||||
if (this.hasNext()) {
|
||||
int nextIndex = this.accecssStack.poll();
|
||||
this.joint = this.joint.subJoints.get(nextIndex);
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return this.joint;
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/main/java/com/tiedup/remake/rig/armature/JointTransform.java
Normal file
215
src/main/java/com/tiedup/remake/rig/armature/JointTransform.java
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import com.tiedup.remake.rig.math.AnimationTransformEntry;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.MatrixOperation;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
public class JointTransform {
|
||||
public static final String ANIMATION_TRANSFORM = "animation_transform";
|
||||
public static final String JOINT_LOCAL_TRANSFORM = "joint_local_transform";
|
||||
public static final String PARENT = "parent";
|
||||
public static final String RESULT1 = "front_result";
|
||||
public static final String RESULT2 = "overwrite_rotation";
|
||||
|
||||
public static class TransformEntry {
|
||||
public final MatrixOperation multiplyFunction;
|
||||
public final JointTransform transform;
|
||||
|
||||
public TransformEntry(MatrixOperation multiplyFunction, JointTransform transform) {
|
||||
this.multiplyFunction = multiplyFunction;
|
||||
this.transform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, TransformEntry> entries = Maps.newHashMap();
|
||||
private final Vec3f translation;
|
||||
private final Vec3f scale;
|
||||
private final Quaternionf rotation;
|
||||
|
||||
public JointTransform(Vec3f translation, Quaternionf rotation, Vec3f scale) {
|
||||
this.translation = translation;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public Vec3f translation() {
|
||||
return this.translation;
|
||||
}
|
||||
|
||||
public Quaternionf rotation() {
|
||||
return this.rotation;
|
||||
}
|
||||
|
||||
public Vec3f scale() {
|
||||
return this.scale;
|
||||
}
|
||||
|
||||
public void clearTransform() {
|
||||
this.translation.set(0.0F, 0.0F, 0.0F);
|
||||
this.rotation.set(0.0F, 0.0F, 0.0F, 1.0F);
|
||||
this.scale.set(1.0F, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
public JointTransform copy() {
|
||||
return JointTransform.empty().copyFrom(this);
|
||||
}
|
||||
|
||||
public JointTransform copyFrom(JointTransform jt) {
|
||||
Vec3f newV = jt.translation();
|
||||
Quaternionf newQ = jt.rotation();
|
||||
Vec3f newS = jt.scale;
|
||||
this.translation.set(newV);
|
||||
this.rotation.set(newQ);
|
||||
this.scale.set(newS);
|
||||
this.entries.putAll(jt.entries);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void jointLocal(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(JOINT_LOCAL_TRANSFORM, new TransformEntry(multiplyFunction, this.mergeIfExist(JOINT_LOCAL_TRANSFORM, transform)));
|
||||
}
|
||||
|
||||
public void parent(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(PARENT, new TransformEntry(multiplyFunction, this.mergeIfExist(PARENT, transform)));
|
||||
}
|
||||
|
||||
public void animationTransform(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(ANIMATION_TRANSFORM, new TransformEntry(multiplyFunction, this.mergeIfExist(ANIMATION_TRANSFORM, transform)));
|
||||
}
|
||||
|
||||
public void frontResult(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(RESULT1, new TransformEntry(multiplyFunction, this.mergeIfExist(RESULT1, transform)));
|
||||
}
|
||||
|
||||
public void overwriteRotation(JointTransform transform) {
|
||||
this.entries.put(RESULT2, new TransformEntry(OpenMatrix4f::mul, this.mergeIfExist(RESULT2, transform)));
|
||||
}
|
||||
|
||||
public JointTransform mergeIfExist(String entryName, JointTransform transform) {
|
||||
if (this.entries.containsKey(entryName)) {
|
||||
TransformEntry transformEntry = this.entries.get(entryName);
|
||||
return JointTransform.mul(transform, transformEntry.transform, transformEntry.multiplyFunction);
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getAnimationBoundMatrix(Joint joint, OpenMatrix4f parentTransform) {
|
||||
AnimationTransformEntry animationTransformEntry = new AnimationTransformEntry();
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : this.entries.entrySet()) {
|
||||
animationTransformEntry.put(entry.getKey(), entry.getValue().transform.toMatrix(), entry.getValue().multiplyFunction);
|
||||
}
|
||||
|
||||
animationTransformEntry.put(ANIMATION_TRANSFORM, this.toMatrix(), OpenMatrix4f::mul);
|
||||
animationTransformEntry.put(JOINT_LOCAL_TRANSFORM, joint.getLocalTransform());
|
||||
animationTransformEntry.put(PARENT, parentTransform);
|
||||
|
||||
return animationTransformEntry.getResult();
|
||||
}
|
||||
|
||||
public OpenMatrix4f toMatrix() {
|
||||
return new OpenMatrix4f().translate(this.translation).mulBack(OpenMatrix4f.fromQuaternion(this.rotation)).scale(this.scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("translation:%s, rotation:%s, scale:%s %d entries ", this.translation, this.rotation, this.scale, this.entries.size());
|
||||
}
|
||||
|
||||
public static JointTransform interpolateTransform(JointTransform prev, JointTransform next, float progression, JointTransform dest) {
|
||||
if (dest == null) {
|
||||
dest = JointTransform.empty();
|
||||
}
|
||||
|
||||
MathUtils.lerpVector(prev.translation, next.translation, progression, dest.translation);
|
||||
MathUtils.lerpQuaternion(prev.rotation, next.rotation, progression, dest.rotation);
|
||||
MathUtils.lerpVector(prev.scale, next.scale, progression, dest.scale);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static JointTransform interpolate(JointTransform prev, JointTransform next, float progression) {
|
||||
return interpolate(prev, next, progression, null);
|
||||
}
|
||||
|
||||
public static JointTransform interpolate(JointTransform prev, JointTransform next, float progression, JointTransform dest) {
|
||||
if (dest == null) {
|
||||
dest = JointTransform.empty();
|
||||
}
|
||||
|
||||
if (prev == null || next == null) {
|
||||
dest.clearTransform();
|
||||
return dest;
|
||||
}
|
||||
|
||||
progression = Mth.clamp(progression, 0.0F, 1.0F);
|
||||
interpolateTransform(prev, next, progression, dest);
|
||||
dest.entries.clear();
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : prev.entries.entrySet()) {
|
||||
JointTransform transform = next.entries.containsKey(entry.getKey()) ? next.entries.get(entry.getKey()).transform : JointTransform.empty();
|
||||
dest.entries.put(entry.getKey(), new TransformEntry(entry.getValue().multiplyFunction, interpolateTransform(entry.getValue().transform, transform, progression, null)));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : next.entries.entrySet()) {
|
||||
if (!dest.entries.containsKey(entry.getKey())) {
|
||||
dest.entries.put(entry.getKey(), new TransformEntry(entry.getValue().multiplyFunction, interpolateTransform(JointTransform.empty(), entry.getValue().transform, progression, null)));
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static JointTransform fromMatrixWithoutScale(OpenMatrix4f matrix) {
|
||||
return new JointTransform(matrix.toTranslationVector(), matrix.toQuaternion(), new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform translation(Vec3f vec) {
|
||||
return JointTransform.translationRotation(vec, new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform rotation(Quaternionf quat) {
|
||||
return JointTransform.translationRotation(new Vec3f(0.0F, 0.0F, 0.0F), quat);
|
||||
}
|
||||
|
||||
public static JointTransform scale(Vec3f vec) {
|
||||
return new JointTransform(new Vec3f(0.0F, 0.0F, 0.0F), new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F), vec);
|
||||
}
|
||||
|
||||
public static JointTransform fromMatrix(OpenMatrix4f matrix) {
|
||||
return new JointTransform(matrix.toTranslationVector(), matrix.toQuaternion(), matrix.toScaleVector());
|
||||
}
|
||||
|
||||
public static JointTransform translationRotation(Vec3f vec, Quaternionf quat) {
|
||||
return new JointTransform(vec, quat, new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform mul(JointTransform left, JointTransform right, MatrixOperation operation) {
|
||||
return JointTransform.fromMatrix(operation.mul(left.toMatrix(), right.toMatrix(), null));
|
||||
}
|
||||
|
||||
public static JointTransform fromPrimitives(float locX, float locY, float locZ, float quatX, float quatY, float quatZ, float quatW, float scaX, float scaY, float scaZ) {
|
||||
return new JointTransform(new Vec3f(locX, locY, locZ), new Quaternionf(quatX, quatY, quatZ, quatW), new Vec3f(scaX, scaY, scaZ));
|
||||
}
|
||||
|
||||
public static JointTransform empty() {
|
||||
return new JointTransform(new Vec3f(0.0F, 0.0F, 0.0F), new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F), new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature.types;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
|
||||
/**
|
||||
* This class is not being used by Epic Fight, but is left to meet various purposes of developers
|
||||
* Also presents developers which joints are necessary when an armature would be Human-like
|
||||
*/
|
||||
public interface HumanLikeArmature extends ToolHolderArmature {
|
||||
public Joint leftHandJoint();
|
||||
public Joint rightHandJoint();
|
||||
public Joint leftArmJoint();
|
||||
public Joint rightArmJoint();
|
||||
public Joint leftLegJoint();
|
||||
public Joint rightLegJoint();
|
||||
public Joint leftThighJoint();
|
||||
public Joint rightThighJoint();
|
||||
public Joint headJoint();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.armature.types;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
|
||||
/**
|
||||
* Interface pour armatures portant un outil (main gauche/droite + dos).
|
||||
* TiedUp gardera cette convention pour maintenir la compat avec les JSON EF
|
||||
* (rig équivalent : Tool_R, Tool_L, Tool_Back). Dans les faits, dans un
|
||||
* contexte bondage, "outil" = menottes, laisse, cage, etc.
|
||||
*/
|
||||
public interface ToolHolderArmature {
|
||||
Joint leftToolJoint();
|
||||
Joint rightToolJoint();
|
||||
Joint backToolJoint();
|
||||
}
|
||||
69
src/main/java/com/tiedup/remake/rig/asset/AssetAccessor.java
Normal file
69
src/main/java/com/tiedup/remake/rig/asset/AssetAccessor.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.asset;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* An accessor class
|
||||
* @param <O> {@link Object} can be any object
|
||||
*/
|
||||
public interface AssetAccessor<O> extends Supplier<O> {
|
||||
O get();
|
||||
|
||||
ResourceLocation registryName();
|
||||
|
||||
default boolean isPresent() {
|
||||
return this.get() != null;
|
||||
}
|
||||
|
||||
default boolean isEmpty() {
|
||||
return !this.isPresent();
|
||||
}
|
||||
|
||||
boolean inRegistry();
|
||||
|
||||
default boolean checkType(Class<?> cls) {
|
||||
return cls.isAssignableFrom(this.get().getClass());
|
||||
}
|
||||
|
||||
default O orElse(O whenNull) {
|
||||
return this.isPresent() ? this.get() : whenNull;
|
||||
}
|
||||
|
||||
default void ifPresent(Consumer<O> action) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
}
|
||||
}
|
||||
|
||||
default void ifPresentOrElse(Consumer<O> action, Runnable whenNull) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
} else {
|
||||
whenNull.run();
|
||||
}
|
||||
}
|
||||
|
||||
default void doOrThrow(Consumer<O> action) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
} else {
|
||||
throw new NoSuchElementException("No asset " + this.registryName());
|
||||
}
|
||||
}
|
||||
|
||||
default void checkNotNull() {
|
||||
if (!this.isPresent()) {
|
||||
throw new NoSuchElementException("No asset " + this.registryName());
|
||||
}
|
||||
}
|
||||
}
|
||||
718
src/main/java/com/tiedup/remake/rig/asset/JsonAssetLoader.java
Normal file
718
src/main/java/com/tiedup/remake/rig/asset/JsonAssetLoader.java
Normal file
@@ -0,0 +1,718 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.asset;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Keyframe;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.AttackAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.AttackAnimation.Phase;
|
||||
import com.tiedup.remake.rig.anim.types.MainFrameAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.mesh.ClassicMesh;
|
||||
import com.tiedup.remake.rig.mesh.CompositeMesh;
|
||||
import com.tiedup.remake.rig.mesh.Mesh;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.Meshes;
|
||||
import com.tiedup.remake.rig.mesh.Meshes.MeshContructor;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.SoftBodyTranslatable;
|
||||
import com.tiedup.remake.rig.mesh.StaticMesh;
|
||||
import com.tiedup.remake.rig.mesh.VertexBuilder;
|
||||
import com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer.VanillaMeshPartDefinition;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject.ClothPart.ConstraintType;
|
||||
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.math.Vec4f;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class JsonAssetLoader {
|
||||
public static final OpenMatrix4f BLENDER_TO_MINECRAFT_COORD = OpenMatrix4f.createRotatorDeg(-90.0F, Vec3f.X_AXIS);
|
||||
public static final OpenMatrix4f MINECRAFT_TO_BLENDER_COORD = OpenMatrix4f.invert(BLENDER_TO_MINECRAFT_COORD, null);
|
||||
public static final String UNGROUPED_NAME = "noGroups";
|
||||
public static final String COORD_BONE = "Coord";
|
||||
public static final String ROOT_BONE = "Root";
|
||||
|
||||
private JsonObject rootJson;
|
||||
|
||||
// Used for deciding armature name, other resources are nullable
|
||||
@Nullable
|
||||
private ResourceLocation resourceLocation;
|
||||
private String filehash;
|
||||
|
||||
public JsonAssetLoader(ResourceManager resourceManager, ResourceLocation resourceLocation) throws AssetLoadingException {
|
||||
JsonReader jsonReader = null;
|
||||
this.resourceLocation = resourceLocation;
|
||||
|
||||
try {
|
||||
try {
|
||||
if (resourceManager == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
Resource resource = resourceManager.getResource(resourceLocation).orElseThrow();
|
||||
InputStream inputStream = resource.open();
|
||||
InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
jsonReader = new JsonReader(isr);
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
} catch (NoSuchElementException e) {
|
||||
// In this case, reads the animation data from mod.jar (Especially in a server)
|
||||
Class<?> modClass = ModList.get().getModObjectById(resourceLocation.getNamespace()).orElseThrow(() -> new AssetLoadingException("No modid " + resourceLocation)).getClass();
|
||||
InputStream inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
||||
|
||||
if (inputStream == null) {
|
||||
modClass = ModList.get().getModObjectById(TiedUpRigConstants.MODID).get().getClass();
|
||||
inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
||||
}
|
||||
|
||||
//Still null, throws exception.
|
||||
if (inputStream == null) {
|
||||
throw new AssetLoadingException("Can't find resource file: " + resourceLocation);
|
||||
}
|
||||
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
InputStreamReader reader = new InputStreamReader(bufferedInputStream, StandardCharsets.UTF_8);
|
||||
|
||||
jsonReader = new JsonReader(reader);
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + " because of " + e);
|
||||
} finally {
|
||||
if (jsonReader != null) {
|
||||
try {
|
||||
jsonReader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.filehash = ParseUtil.getBytesSHA256Hash(this.rootJson.toString().getBytes());
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public JsonAssetLoader(InputStream inputstream, ResourceLocation resourceLocation) throws AssetLoadingException {
|
||||
JsonReader jsonReader = null;
|
||||
this.resourceLocation = resourceLocation;
|
||||
|
||||
jsonReader = new JsonReader(new InputStreamReader(inputstream, StandardCharsets.UTF_8));
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
|
||||
try {
|
||||
jsonReader.close();
|
||||
} catch (IOException e) {
|
||||
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + ": " + e);
|
||||
}
|
||||
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public JsonAssetLoader(JsonObject rootJson, ResourceLocation rl) {
|
||||
this.rootJson = rootJson;
|
||||
this.resourceLocation = rl;
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Mesh.RenderProperties getRenderProperties(JsonObject json) {
|
||||
if (!json.has("render_properties")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonObject properties = json.getAsJsonObject("render_properties");
|
||||
Mesh.RenderProperties.Builder renderProperties = Mesh.RenderProperties.Builder.create();
|
||||
|
||||
if (properties.has("transparent")) {
|
||||
renderProperties.transparency(properties.get("transparent").getAsBoolean());
|
||||
}
|
||||
|
||||
if (properties.has("texture_path")) {
|
||||
renderProperties.customTexturePath(properties.get("texture_path").getAsString());
|
||||
}
|
||||
|
||||
if (properties.has("color")) {
|
||||
JsonArray jsonarray = properties.getAsJsonArray("color");
|
||||
renderProperties.customColor(jsonarray.get(0).getAsFloat(), jsonarray.get(1).getAsFloat(), jsonarray.get(2).getAsFloat());
|
||||
}
|
||||
|
||||
return renderProperties.build();
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public ResourceLocation getParent() {
|
||||
return this.rootJson.has("parent") ? ResourceLocation.parse(this.rootJson.get("parent").getAsString()) : null;
|
||||
}
|
||||
|
||||
private static final float DEFAULT_PARTICLE_MASS = 0.16F;
|
||||
private static final float DEFAULT_SELF_COLLISON = 0.05F;
|
||||
|
||||
@Nullable
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Map<String, SoftBodyTranslatable.ClothSimulationInfo> loadClothInformation(Float[] positionArray) {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject clothInfoObj = obj.getAsJsonObject("cloth_info");
|
||||
|
||||
if (clothInfoObj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, SoftBodyTranslatable.ClothSimulationInfo> clothInfo = Maps.newHashMap();
|
||||
|
||||
for (Map.Entry<String, JsonElement> e : clothInfoObj.entrySet()) {
|
||||
JsonObject clothObject = e.getValue().getAsJsonObject();
|
||||
int[] particlesArray = ParseUtil.toIntArrayPrimitive(clothObject.get("particles").getAsJsonObject().get("array").getAsJsonArray());
|
||||
float[] weightsArray = ParseUtil.toFloatArrayPrimitive(clothObject.get("weights").getAsJsonObject().get("array").getAsJsonArray());
|
||||
float particleMass = clothObject.has("particle_mass") ? clothObject.get("particle_mass").getAsFloat() : DEFAULT_PARTICLE_MASS;
|
||||
float selfCollision = clothObject.has("self_collision") ? clothObject.get("self_collision").getAsFloat() : DEFAULT_SELF_COLLISON;
|
||||
|
||||
JsonArray constraintsArray = clothObject.get("constraints").getAsJsonArray();
|
||||
List<int[]> constraintsList = new ArrayList<> (constraintsArray.size());
|
||||
float[] compliances = new float[constraintsArray.size()];
|
||||
ConstraintType[] constraintType = new ConstraintType[constraintsArray.size()];
|
||||
float[] rootDistances = new float[particlesArray.length / 2];
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (JsonElement element : constraintsArray) {
|
||||
JsonObject asJsonObject = element.getAsJsonObject();
|
||||
|
||||
if (asJsonObject.has("unused") && GsonHelper.getAsBoolean(asJsonObject, "unused")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
constraintType[i] = ConstraintType.valueOf(GsonHelper.getAsString(asJsonObject, "type").toUpperCase(Locale.ROOT));
|
||||
compliances[i] = GsonHelper.getAsFloat(asJsonObject, "compliance");
|
||||
constraintsList.add(ParseUtil.toIntArrayPrimitive(asJsonObject.get("array").getAsJsonArray()));
|
||||
element.getAsJsonObject().get("compliance");
|
||||
i++;
|
||||
}
|
||||
|
||||
List<Vec3> rootParticles = Lists.newArrayList();
|
||||
|
||||
for (int j = 0; j < particlesArray.length / 2; j++) {
|
||||
int weightIndex = particlesArray[j * 2 + 1];
|
||||
float weight = weightsArray[weightIndex];
|
||||
|
||||
if (weight == 0.0F) {
|
||||
int posId = particlesArray[j * 2];
|
||||
rootParticles.add(new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < particlesArray.length / 2; j++) {
|
||||
int posId = particlesArray[j * 2];
|
||||
Vec3 position = new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]);
|
||||
Vec3 nearest = MathUtils.getNearestVector(position, rootParticles);
|
||||
rootDistances[j] = (float)position.distanceTo(nearest);
|
||||
}
|
||||
|
||||
int[] normalOffsetMappingArray = null;
|
||||
|
||||
if (clothObject.has("normal_offsets")) {
|
||||
normalOffsetMappingArray = ParseUtil.toIntArrayPrimitive(clothObject.get("normal_offsets").getAsJsonObject().get("array").getAsJsonArray());
|
||||
}
|
||||
|
||||
SoftBodyTranslatable.ClothSimulationInfo clothSimulInfo = new SoftBodyTranslatable.ClothSimulationInfo(particleMass, selfCollision, constraintsList, constraintType, compliances, particlesArray, weightsArray, rootDistances, normalOffsetMappingArray);
|
||||
clothInfo.put(e.getKey(), clothSimulInfo);
|
||||
}
|
||||
|
||||
return clothInfo;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public <T extends ClassicMesh> T loadClassicMesh(MeshContructor<ClassicMesh.ClassicMeshPart, VertexBuilder, T> constructor) {
|
||||
ResourceLocation parent = this.getParent();
|
||||
|
||||
if (parent != null) {
|
||||
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadClassicMesh(constructor)).get();
|
||||
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
||||
} else {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject positions = obj.getAsJsonObject("positions");
|
||||
JsonObject normals = obj.getAsJsonObject("normals");
|
||||
JsonObject uvs = obj.getAsJsonObject("uvs");
|
||||
JsonObject parts = obj.getAsJsonObject("parts");
|
||||
JsonObject indices = obj.getAsJsonObject("indices");
|
||||
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < positionArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
||||
positionArray[k] = posVector.x;
|
||||
positionArray[k+1] = posVector.y;
|
||||
positionArray[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < normalArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
||||
normalArray[k] = normVector.x;
|
||||
normalArray[k+1] = normVector.y;
|
||||
normalArray[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
||||
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
||||
|
||||
arrayMap.put("positions", positionArray);
|
||||
arrayMap.put("normals", normalArray);
|
||||
arrayMap.put("uvs", uvArray);
|
||||
|
||||
if (parts != null) {
|
||||
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
||||
}
|
||||
}
|
||||
|
||||
if (indices != null) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
||||
}
|
||||
|
||||
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
||||
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public <T extends SkinnedMesh> T loadSkinnedMesh(MeshContructor<SkinnedMesh.SkinnedMeshPart, VertexBuilder, T> constructor) {
|
||||
ResourceLocation parent = this.getParent();
|
||||
|
||||
if (parent != null) {
|
||||
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadSkinnedMesh(constructor)).get();
|
||||
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
||||
} else {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject positions = obj.getAsJsonObject("positions");
|
||||
JsonObject normals = obj.getAsJsonObject("normals");
|
||||
JsonObject uvs = obj.getAsJsonObject("uvs");
|
||||
JsonObject vdincies = obj.getAsJsonObject("vindices");
|
||||
JsonObject weights = obj.getAsJsonObject("weights");
|
||||
JsonObject vcounts = obj.getAsJsonObject("vcounts");
|
||||
JsonObject parts = obj.getAsJsonObject("parts");
|
||||
JsonObject indices = obj.getAsJsonObject("indices");
|
||||
|
||||
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < positionArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
||||
positionArray[k] = posVector.x;
|
||||
positionArray[k+1] = posVector.y;
|
||||
positionArray[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < normalArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
||||
normalArray[k] = normVector.x;
|
||||
normalArray[k+1] = normVector.y;
|
||||
normalArray[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
||||
Float[] weightArray = ParseUtil.toFloatArray(weights.get("array").getAsJsonArray());
|
||||
Integer[] affectingJointCounts = ParseUtil.toIntArray(vcounts.get("array").getAsJsonArray());
|
||||
Integer[] affectingJointIndices = ParseUtil.toIntArray(vdincies.get("array").getAsJsonArray());
|
||||
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
||||
arrayMap.put("positions", positionArray);
|
||||
arrayMap.put("normals", normalArray);
|
||||
arrayMap.put("uvs", uvArray);
|
||||
arrayMap.put("weights", weightArray);
|
||||
arrayMap.put("vcounts", affectingJointCounts);
|
||||
arrayMap.put("vindices", affectingJointIndices);
|
||||
|
||||
if (parts != null) {
|
||||
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
||||
}
|
||||
}
|
||||
|
||||
if (indices != null) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
||||
}
|
||||
|
||||
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
||||
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public CompositeMesh loadCompositeMesh() throws AssetLoadingException {
|
||||
if (!this.rootJson.has("meshes")) {
|
||||
throw new AssetLoadingException("Composite mesh loading exception: lower meshes undefined");
|
||||
}
|
||||
|
||||
JsonAssetLoader clothLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("cloth").getAsJsonObject(), null);
|
||||
JsonAssetLoader staticLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("static").getAsJsonObject(), null);
|
||||
SoftBodyTranslatable softBodyMesh = (SoftBodyTranslatable)clothLoader.loadMesh(false);
|
||||
StaticMesh<?> staticMesh = (StaticMesh<?>)staticLoader.loadMesh(false);
|
||||
|
||||
if (!softBodyMesh.canStartSoftBodySimulation()) {
|
||||
throw new AssetLoadingException("Composite mesh loading exception: soft mesh doesn't have cloth info");
|
||||
}
|
||||
|
||||
return new CompositeMesh(staticMesh, softBodyMesh);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Mesh loadMesh() throws AssetLoadingException {
|
||||
return this.loadMesh(true);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private Mesh loadMesh(boolean allowCompositeMesh) throws AssetLoadingException {
|
||||
if (!this.rootJson.has("mesh_loader")) {
|
||||
throw new AssetLoadingException("Mesh loading exception: No mesh loader provided!");
|
||||
}
|
||||
|
||||
String loader = this.rootJson.get("mesh_loader").getAsString();
|
||||
|
||||
switch (loader) {
|
||||
case "classic_mesh" -> {
|
||||
return this.loadClassicMesh(ClassicMesh::new);
|
||||
}
|
||||
case "skinned_mesh" -> {
|
||||
return this.loadSkinnedMesh(SkinnedMesh::new);
|
||||
}
|
||||
case "composite_mesh" -> {
|
||||
if (!allowCompositeMesh) {
|
||||
throw new AssetLoadingException("Can't have a composite mesh inside another composite mesh");
|
||||
}
|
||||
|
||||
return this.loadCompositeMesh();
|
||||
}
|
||||
default -> {
|
||||
throw new AssetLoadingException("Mesh loading exception: Unsupported mesh loader: " + loader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationClip loadClipForAnimation(StaticAnimation animation) {
|
||||
if (this.rootJson == null) {
|
||||
throw new AssetLoadingException("Can't find animation in path: " + animation);
|
||||
}
|
||||
|
||||
if (animation.getArmature() == null) {
|
||||
TiedUpRigConstants.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
||||
}
|
||||
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
boolean action = animation instanceof MainFrameAnimation;
|
||||
boolean attack = animation instanceof AttackAnimation;
|
||||
boolean noTransformData = !action && !attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER;
|
||||
boolean root = true;
|
||||
Armature armature = animation.getArmature().get();
|
||||
Set<String> allowedJoints = Sets.newLinkedHashSet();
|
||||
|
||||
if (attack) {
|
||||
for (Phase phase : ((AttackAnimation)animation).phases) {
|
||||
for (AttackAnimation.JointColliderPair colliderInfo : phase.getColliders()) {
|
||||
armature.gatherAllJointsInPathToTerminal(colliderInfo.getFirst().getName(), allowedJoints);
|
||||
}
|
||||
}
|
||||
} else if (action) {
|
||||
allowedJoints.add(ROOT_BONE);
|
||||
}
|
||||
|
||||
AnimationClip clip = new AnimationClip();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
|
||||
if (attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER && !allowedJoints.contains(name)) {
|
||||
if (name.equals(COORD_BONE)) {
|
||||
root = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
if (name.equals(COORD_BONE)) {
|
||||
TransformSheet sheet = getTransformSheet(jObject, new OpenMatrix4f(), true, format);
|
||||
|
||||
if (action) {
|
||||
((ActionAnimation)animation).addProperty(ActionAnimationProperty.COORD, sheet);
|
||||
}
|
||||
|
||||
root = false;
|
||||
continue;
|
||||
} else {
|
||||
TiedUpRigConstants.LOGGER.debug("[EpicFightMod] No joint named " + name + " in " + animation);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
|
||||
if (!noTransformData) {
|
||||
clip.addJointTransform(name, sheet);
|
||||
}
|
||||
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
public AnimationClip loadAllJointsClipForAnimation(StaticAnimation animation) {
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
boolean root = true;
|
||||
|
||||
if (animation.getArmature() == null) {
|
||||
TiedUpRigConstants.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
||||
}
|
||||
|
||||
Armature armature = animation.getArmature().get();
|
||||
AnimationClip clip = new AnimationClip();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
if (TiedUpRigConstants.IS_DEV_ENV) {
|
||||
TiedUpRigConstants.LOGGER.debug(animation.getRegistryName() + ": No joint named " + name + " in armature");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
clip.addJointTransform(name, sheet);
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
public JsonObject getRootJson() {
|
||||
return this.rootJson;
|
||||
}
|
||||
|
||||
public String getFileHash() {
|
||||
return this.filehash;
|
||||
}
|
||||
|
||||
public static TransformFormat getAsTransformFormatOrDefault(JsonObject jsonObject, String propertyName) {
|
||||
return jsonObject.has(propertyName) ? ParseUtil.enumValueOfOrNull(TransformFormat.class, GsonHelper.getAsString(jsonObject, propertyName)) : TransformFormat.MATRIX;
|
||||
}
|
||||
|
||||
public AnimationClip loadAnimationClip(Armature armature) {
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
AnimationClip clip = new AnimationClip();
|
||||
boolean root = true;
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(element.getAsJsonObject(), OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
clip.addJointTransform(name, sheet);
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jObject
|
||||
* @param invLocalTransform nullable if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
||||
* @param rootCorrection no matter what the value is if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
||||
* @param transformFormat
|
||||
* @return
|
||||
*/
|
||||
public static TransformSheet getTransformSheet(JsonObject jObject, @Nullable OpenMatrix4f invLocalTransform, boolean rootCorrection, TransformFormat transformFormat) throws AssetLoadingException, JsonParseException {
|
||||
JsonArray timeArray = jObject.getAsJsonArray("time");
|
||||
JsonArray transformArray = jObject.getAsJsonArray("transform");
|
||||
|
||||
if (timeArray.size() != transformArray.size()) {
|
||||
throw new AssetLoadingException(
|
||||
"Can't read transform sheet: the size of timestamp and transform array is different."
|
||||
+ "timestamp array size: " + timeArray.size() + ", transform array size: " + transformArray.size()
|
||||
);
|
||||
}
|
||||
|
||||
int timesCount = timeArray.size();
|
||||
List<Keyframe> keyframeList = Lists.newArrayList();
|
||||
|
||||
for (int i = 0; i < timesCount; i++) {
|
||||
float timeStamp = timeArray.get(i).getAsFloat();
|
||||
|
||||
if (timeStamp < 0.0F) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// WORKAROUND: The case when transform format is wrongly specified!
|
||||
if (transformFormat == TransformFormat.ATTRIBUTES && transformArray.get(i).isJsonArray()) {
|
||||
transformFormat = TransformFormat.MATRIX;
|
||||
} else if (transformFormat == TransformFormat.MATRIX && transformArray.get(i).isJsonObject()) {
|
||||
transformFormat = TransformFormat.ATTRIBUTES;
|
||||
}
|
||||
|
||||
switch (transformFormat) {
|
||||
case MATRIX -> {
|
||||
JsonArray matrixArray = transformArray.get(i).getAsJsonArray();
|
||||
float[] matrixElements = new float[16];
|
||||
|
||||
for (int j = 0; j < 16; j++) {
|
||||
matrixElements[j] = matrixArray.get(j).getAsFloat();
|
||||
}
|
||||
|
||||
OpenMatrix4f matrix = OpenMatrix4f.load(null, matrixElements);
|
||||
matrix.transpose();
|
||||
|
||||
if (rootCorrection) {
|
||||
matrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
||||
}
|
||||
|
||||
matrix.mulFront(invLocalTransform);
|
||||
|
||||
JointTransform transform = JointTransform.fromMatrix(matrix);
|
||||
transform.rotation().normalize();
|
||||
keyframeList.add(new Keyframe(timeStamp, transform));
|
||||
}
|
||||
case ATTRIBUTES -> {
|
||||
JsonObject transformObject = transformArray.get(i).getAsJsonObject();
|
||||
JsonArray locArray = transformObject.get("loc").getAsJsonArray();
|
||||
JsonArray rotArray = transformObject.get("rot").getAsJsonArray();
|
||||
JsonArray scaArray = transformObject.get("sca").getAsJsonArray();
|
||||
JointTransform transform
|
||||
= JointTransform.fromPrimitives(
|
||||
locArray.get(0).getAsFloat()
|
||||
, locArray.get(1).getAsFloat()
|
||||
, locArray.get(2).getAsFloat()
|
||||
, -rotArray.get(1).getAsFloat()
|
||||
, -rotArray.get(2).getAsFloat()
|
||||
, -rotArray.get(3).getAsFloat()
|
||||
, rotArray.get(0).getAsFloat()
|
||||
, scaArray.get(0).getAsFloat()
|
||||
, scaArray.get(1).getAsFloat()
|
||||
, scaArray.get(2).getAsFloat()
|
||||
);
|
||||
|
||||
keyframeList.add(new Keyframe(timeStamp, transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransformSheet sheet = new TransformSheet(keyframeList);
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the transform is expressed in json
|
||||
*
|
||||
* {@link TransformFormat#MATRIX} be like,
|
||||
* [0, 1, 2, ..., 15]
|
||||
*
|
||||
* {@link TransformFormat#ATTRIBUTES} be like,
|
||||
* {
|
||||
* "loc": [0, 0, 0],
|
||||
* "rot": [0, 0, 0, 1],
|
||||
* "sca": [1, 1, 1],
|
||||
* }
|
||||
*/
|
||||
public enum TransformFormat {
|
||||
MATRIX, ATTRIBUTES
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.bridge;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* Table d'alias runtime pour mapper les noms de joints des GLB legacy TiedUp
|
||||
* (riggés via PlayerAnimator / bendy-lib avec noms type {@code leftUpperArm})
|
||||
* vers le skeleton biped Epic Fight utilisé par RIG ({@code Arm_L}, etc.).
|
||||
*
|
||||
* <p>Voir {@code docs/plans/rig/ARCHITECTURE.md §6.3} pour la source de vérité
|
||||
* du mapping. Tout joint inconnu après lookup doit être loggé WARN par le
|
||||
* caller et fallback sur {@code Root}.</p>
|
||||
*
|
||||
* <p><b>Cas spécial "body/torso"</b> — le GLB legacy a souvent un unique joint
|
||||
* couvrant l'ensemble du torse. On le mappe sur {@code Chest} par défaut
|
||||
* (meilleur fit pour les items bondage majoritairement attachés au haut du
|
||||
* corps : harnais, menottes de poitrine, collier). Si un item a besoin
|
||||
* d'attachement à {@code Torso} (ceinture), le modeler devra renommer son
|
||||
* joint en {@code waist} explicitement.</p>
|
||||
*/
|
||||
public final class GlbJointAliasTable {
|
||||
|
||||
/**
|
||||
* Mapping direct PlayerAnimator → biped EF. Les clés sont la forme
|
||||
* lowercase EXACTE des noms exportés par les GLB legacy.
|
||||
*/
|
||||
private static final Map<String, String> ALIAS = ImmutableMap.<String, String>builder()
|
||||
// Torso region
|
||||
.put("body", "Chest")
|
||||
.put("torso", "Chest")
|
||||
.put("chest", "Chest")
|
||||
.put("waist", "Torso")
|
||||
.put("hip", "Torso")
|
||||
|
||||
// Head
|
||||
.put("head", "Head")
|
||||
|
||||
// Arms left
|
||||
.put("leftshoulder", "Shoulder_L")
|
||||
.put("leftupperarm", "Arm_L")
|
||||
.put("leftarm", "Arm_L")
|
||||
.put("leftlowerarm", "Elbow_L")
|
||||
.put("leftforearm", "Elbow_L")
|
||||
.put("leftelbow", "Elbow_L")
|
||||
.put("lefthand", "Hand_L")
|
||||
|
||||
// Arms right
|
||||
.put("rightshoulder", "Shoulder_R")
|
||||
.put("rightupperarm", "Arm_R")
|
||||
.put("rightarm", "Arm_R")
|
||||
.put("rightlowerarm", "Elbow_R")
|
||||
.put("rightforearm", "Elbow_R")
|
||||
.put("rightelbow", "Elbow_R")
|
||||
.put("righthand", "Hand_R")
|
||||
|
||||
// Legs left
|
||||
.put("leftupperleg", "Thigh_L")
|
||||
.put("leftleg", "Thigh_L")
|
||||
.put("leftlowerleg", "Knee_L")
|
||||
.put("leftknee", "Knee_L")
|
||||
.put("leftfoot", "Leg_L")
|
||||
|
||||
// Legs right
|
||||
.put("rightupperleg", "Thigh_R")
|
||||
.put("rightleg", "Thigh_R")
|
||||
.put("rightlowerleg", "Knee_R")
|
||||
.put("rightknee", "Knee_R")
|
||||
.put("rightfoot", "Leg_R")
|
||||
|
||||
// Root fallback (déjà nommé Root dans GLB modernes)
|
||||
.put("root", "Root")
|
||||
.put("armature", "Root")
|
||||
.build();
|
||||
|
||||
private GlbJointAliasTable() {}
|
||||
|
||||
/**
|
||||
* Traduit un nom de joint GLB legacy vers le nom biped EF équivalent.
|
||||
* Case-insensitive. Les noms déjà au format biped EF (ex: {@code Arm_L}) sont
|
||||
* retournés tels quels après vérification.
|
||||
*
|
||||
* @param gltfJointName nom tel qu'exporté dans le GLB (jointNames[])
|
||||
* @return nom biped EF (ex: {@code Arm_L}), ou null si inconnu
|
||||
*/
|
||||
@Nullable
|
||||
public static String mapGltfJointName(String gltfJointName) {
|
||||
if (gltfJointName == null || gltfJointName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Direct hit sur le biped EF (GLB moderne déjà bien rigged).
|
||||
if (isBipedJointName(gltfJointName)) {
|
||||
return gltfJointName;
|
||||
}
|
||||
|
||||
return ALIAS.get(gltfJointName.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un nom est déjà au format biped EF. Utilisé pour court-circuiter
|
||||
* l'alias lookup sur les GLB modernes.
|
||||
*/
|
||||
public static boolean isBipedJointName(String name) {
|
||||
// Heuristique : les noms biped EF sont en PascalCase avec suffixe _R/_L,
|
||||
// ou parmi {Root, Torso, Chest, Head}.
|
||||
return switch (name) {
|
||||
case "Root", "Torso", "Chest", "Head" -> true;
|
||||
default -> name.endsWith("_R") || name.endsWith("_L");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.bridge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
import com.tiedup.remake.client.gltf.GltfData;
|
||||
import com.tiedup.remake.client.gltf.GltfData.Primitive;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.math.Vec2f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer.VanillaMeshPartDefinition;
|
||||
|
||||
/**
|
||||
* Pont Phase 1 : convertit un {@link GltfData} (format GLB legacy TiedUp
|
||||
* riggé 11-joints PlayerAnimator) en {@link SkinnedMesh} Epic Fight
|
||||
* (biped ~20 joints).
|
||||
*
|
||||
* <p>Algorithme (voir {@code docs/plans/rig/MIGRATION.md §1.2.1}) :</p>
|
||||
* <ol>
|
||||
* <li>Pré-calculer le mapping {@code gltfJointIdx → bipedJointId} via
|
||||
* {@link GlbJointAliasTable} + {@link Armature#searchJointByName}.</li>
|
||||
* <li>Pour chaque vertex :
|
||||
* <ul>
|
||||
* <li>Position / normal / UV depuis {@link GltfData}</li>
|
||||
* <li>Retenir les 3 joints de plus fort poids parmi les 4 glTF</li>
|
||||
* <li>Renormaliser les poids retenus pour sommer à 1.0</li>
|
||||
* <li>Construire le {@link SingleGroupVertexBuilder}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Grouper les indices par {@link Primitive} en autant de
|
||||
* {@link MeshPartDefinition}.</li>
|
||||
* <li>{@link SingleGroupVertexBuilder#loadVertexInformation(List, Map)}
|
||||
* construit le {@link SkinnedMesh}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Les animations éventuellement embarquées dans le GLB sont <b>ignorées</b> —
|
||||
* les animations passent par JSON EF natif via {@code JsonAssetLoader}.</p>
|
||||
*/
|
||||
public final class GltfToSkinnedMesh {
|
||||
|
||||
private static final float WEIGHT_EPSILON = 1.0e-4F;
|
||||
|
||||
private GltfToSkinnedMesh() {}
|
||||
|
||||
/**
|
||||
* Convertit un GLB parsé en {@link SkinnedMesh} utilisable par le pipeline
|
||||
* de rendu RIG.
|
||||
*
|
||||
* @param data données GLB parsées par {@code GlbParser.parse(...)}
|
||||
* @param armature armature biped EF cible (doit être déjà chargée)
|
||||
* @return SkinnedMesh prêt à être rendu
|
||||
* @throws IllegalStateException si {@code armature} est null
|
||||
*/
|
||||
public static SkinnedMesh convert(GltfData data, AssetAccessor<? extends Armature> armature) {
|
||||
if (armature == null || armature.get() == null) {
|
||||
throw new IllegalStateException(
|
||||
"Armature not loaded — GltfToSkinnedMesh.convert() called before resource reload completed"
|
||||
);
|
||||
}
|
||||
|
||||
Armature arm = armature.get();
|
||||
int[] jointIdMap = buildJointIdMap(data.jointNames(), arm);
|
||||
|
||||
int vertexCount = data.vertexCount();
|
||||
float[] positions = data.positions();
|
||||
float[] normals = data.normals();
|
||||
float[] texCoords = data.texCoords();
|
||||
int[] joints = data.joints();
|
||||
float[] weights = data.weights();
|
||||
|
||||
List<SingleGroupVertexBuilder> vertices = new ArrayList<>(vertexCount);
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
vertices.add(buildVertex(i, positions, normals, texCoords, joints, weights, jointIdMap));
|
||||
}
|
||||
|
||||
Map<MeshPartDefinition, IntList> partIndices = buildPartIndices(data.primitives());
|
||||
|
||||
return SingleGroupVertexBuilder.loadVertexInformation(vertices, partIndices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit le mapping {@code gltfJointIdx → bipedJointId} une seule fois
|
||||
* avant la boucle vertex. Les noms inconnus retombent sur la racine
|
||||
* {@code Root} (id 0) avec un log WARN.
|
||||
*/
|
||||
private static int[] buildJointIdMap(String[] gltfJointNames, Armature arm) {
|
||||
int[] map = new int[gltfJointNames.length];
|
||||
int unknownCount = 0;
|
||||
int aliasedCount = 0;
|
||||
int rootId = arm.rootJoint != null ? arm.rootJoint.getId() : 0;
|
||||
|
||||
for (int i = 0; i < gltfJointNames.length; i++) {
|
||||
String gltfName = gltfJointNames[i];
|
||||
String bipedName = GlbJointAliasTable.mapGltfJointName(gltfName);
|
||||
|
||||
if (bipedName == null) {
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"GltfToSkinnedMesh: unknown joint '{}' — fallback to Root",
|
||||
gltfName
|
||||
);
|
||||
map[i] = rootId;
|
||||
unknownCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Joint joint = arm.searchJointByName(bipedName);
|
||||
if (joint == null) {
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"GltfToSkinnedMesh: biped joint '{}' (aliased from '{}') not found in armature — fallback to Root",
|
||||
bipedName, gltfName
|
||||
);
|
||||
map[i] = rootId;
|
||||
unknownCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
map[i] = joint.getId();
|
||||
if (!gltfName.equals(bipedName)) {
|
||||
aliasedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
TiedUpRigConstants.LOGGER.info(
|
||||
"GltfToSkinnedMesh: {} joints mapped ({} via alias, {} unknown→Root)",
|
||||
gltfJointNames.length, aliasedCount, unknownCount
|
||||
);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit un vertex individuel : position/normal/UV depuis les arrays
|
||||
* flattened, puis sélection des 3 plus forts poids (drop du 4e) + renormalisation.
|
||||
*/
|
||||
private static SingleGroupVertexBuilder buildVertex(
|
||||
int i,
|
||||
float[] positions, float[] normals, float[] texCoords,
|
||||
int[] joints, float[] weights,
|
||||
int[] jointIdMap) {
|
||||
|
||||
SingleGroupVertexBuilder vb = new SingleGroupVertexBuilder();
|
||||
vb.setPosition(new Vec3f(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]));
|
||||
vb.setNormal(new Vec3f(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]));
|
||||
vb.setTextureCoordinate(new Vec2f(texCoords[i * 2], texCoords[i * 2 + 1]));
|
||||
|
||||
// Récupère les 4 joints/poids glTF, sélectionne les 3 plus forts.
|
||||
int[] rawJoints = new int[4];
|
||||
float[] rawWeights = new float[4];
|
||||
for (int k = 0; k < 4; k++) {
|
||||
rawJoints[k] = joints[i * 4 + k];
|
||||
rawWeights[k] = weights[i * 4 + k];
|
||||
}
|
||||
|
||||
// Trouve l'index du plus faible poids (à drop).
|
||||
int minIdx = 0;
|
||||
for (int k = 1; k < 4; k++) {
|
||||
if (rawWeights[k] < rawWeights[minIdx]) {
|
||||
minIdx = k;
|
||||
}
|
||||
}
|
||||
|
||||
// Build les 3 retenus + compte effective.
|
||||
float w0 = 0, w1 = 0, w2 = 0;
|
||||
int id0 = 0, id1 = 0, id2 = 0;
|
||||
int effectiveCount = 0;
|
||||
int slot = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
if (k == minIdx) continue;
|
||||
float w = rawWeights[k];
|
||||
int id = jointIdMap[rawJoints[k]];
|
||||
switch (slot) {
|
||||
case 0 -> { w0 = w; id0 = id; }
|
||||
case 1 -> { w1 = w; id1 = id; }
|
||||
case 2 -> { w2 = w; id2 = id; }
|
||||
}
|
||||
if (w > WEIGHT_EPSILON) effectiveCount++;
|
||||
slot++;
|
||||
}
|
||||
|
||||
// Renormalise les 3 poids pour qu'ils somment à 1.0.
|
||||
float sum = w0 + w1 + w2;
|
||||
if (sum > WEIGHT_EPSILON) {
|
||||
float inv = 1.0F / sum;
|
||||
w0 *= inv; w1 *= inv; w2 *= inv;
|
||||
} else {
|
||||
// Vertex sans skinning (tout-zéro ou bugué) — attache au Root avec poids 1.
|
||||
w0 = 1.0F; w1 = 0; w2 = 0;
|
||||
id0 = 0; id1 = 0; id2 = 0;
|
||||
effectiveCount = 1;
|
||||
}
|
||||
|
||||
vb.setEffectiveJointIDs(new Vec3f(id0, id1, id2));
|
||||
vb.setEffectiveJointWeights(new Vec3f(w0, w1, w2));
|
||||
vb.setEffectiveJointNumber(Math.max(1, effectiveCount));
|
||||
return vb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groupe les indices par primitive (= material dans Blender) → une
|
||||
* {@link VanillaMeshPartDefinition} par primitive. Le partName est pris sur
|
||||
* le {@code materialName} si défini, sinon un nom synthétique
|
||||
* {@code "part_N"}.
|
||||
*/
|
||||
private static Map<MeshPartDefinition, IntList> buildPartIndices(List<Primitive> primitives) {
|
||||
Map<MeshPartDefinition, IntList> partIndices = new HashMap<>();
|
||||
int fallbackCounter = 0;
|
||||
|
||||
for (Primitive prim : primitives) {
|
||||
String partName = prim.materialName();
|
||||
if (partName == null || partName.isEmpty()) {
|
||||
partName = "part_" + fallbackCounter++;
|
||||
}
|
||||
|
||||
MeshPartDefinition partDef = VanillaMeshPartDefinition.of(partName);
|
||||
IntList indexList = new IntArrayList(prim.indices().length);
|
||||
for (int idx : prim.indices()) {
|
||||
indexList.add(idx);
|
||||
}
|
||||
partIndices.put(partDef, indexList);
|
||||
}
|
||||
|
||||
return partIndices;
|
||||
}
|
||||
}
|
||||
135
src/main/java/com/tiedup/remake/rig/cloth/AbstractSimulator.java
Normal file
135
src/main/java/com/tiedup/remake/rig/cloth/AbstractSimulator.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.cloth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.tiedup.remake.rig.physics.PhysicsSimulator;
|
||||
import com.tiedup.remake.rig.physics.SimulationObject;
|
||||
import com.tiedup.remake.rig.physics.SimulationObject.SimulationObjectBuilder;
|
||||
import com.tiedup.remake.rig.physics.SimulationProvider;
|
||||
|
||||
public abstract class AbstractSimulator<KEY, B extends SimulationObjectBuilder, PV extends SimulationProvider<O, SO, B, PV>, O, SO extends SimulationObject<B, PV, O>> implements PhysicsSimulator<KEY, B, PV, O, SO> {
|
||||
protected Map<KEY, ObjectWrapper> simulationObjects = Maps.newHashMap();
|
||||
|
||||
@Override
|
||||
public void tick(O simObject) {
|
||||
this.simulationObjects.values().removeIf((keyWrapper) -> {
|
||||
if (keyWrapper.isRunning()) {
|
||||
if (!keyWrapper.runWhen.getAsBoolean()) {
|
||||
keyWrapper.stopRunning();
|
||||
|
||||
if (!keyWrapper.permanent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (keyWrapper.runWhen.getAsBoolean()) {
|
||||
keyWrapper.startRunning(simObject);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simulation object and run. Remove when @Param until returns false
|
||||
*/
|
||||
@Override
|
||||
public void runUntil(KEY key, PV provider, B builder, BooleanSupplier until) {
|
||||
this.simulationObjects.put(key, new ObjectWrapper(provider, until, false, builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an undeleted simulation object. Run simulation when @Param when returns true
|
||||
*/
|
||||
@Override
|
||||
public void runWhen(KEY key, PV provider, B builder, BooleanSupplier when) {
|
||||
this.simulationObjects.put(key, new ObjectWrapper(provider, when, true, builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop simulation
|
||||
*/
|
||||
@Override
|
||||
public void stop(KEY key) {
|
||||
this.simulationObjects.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart with the same condition but with another provider
|
||||
*/
|
||||
@Override
|
||||
public void restart(KEY key) {
|
||||
ObjectWrapper kwrap = this.simulationObjects.get(key);
|
||||
|
||||
if (kwrap != null) {
|
||||
this.stop(key);
|
||||
this.simulationObjects.put(key, new ObjectWrapper(kwrap.provider, kwrap.runWhen, kwrap.permanent, kwrap.builder));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning(KEY key) {
|
||||
return this.simulationObjects.containsKey(key) ? this.simulationObjects.get(key).isRunning() : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SO> getRunningObject(KEY key) {
|
||||
if (!this.simulationObjects.containsKey(key)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(this.simulationObjects.get(key).simulationObject);
|
||||
}
|
||||
|
||||
public List<Pair<KEY, SO>> getAllRunningObjects() {
|
||||
return this.simulationObjects.entrySet().stream().filter((entry) -> entry.getValue().isRunning()).map((entry) -> Pair.of(entry.getKey(), entry.getValue().simulationObject)).toList();
|
||||
}
|
||||
|
||||
protected class ObjectWrapper {
|
||||
final PV provider;
|
||||
final B builder;
|
||||
final BooleanSupplier runWhen;
|
||||
final boolean permanent;
|
||||
|
||||
SO simulationObject;
|
||||
boolean isRunning;
|
||||
|
||||
ObjectWrapper(PV key, BooleanSupplier runWhen, boolean permanent, B builder) {
|
||||
this.provider = key;
|
||||
this.runWhen = runWhen;
|
||||
this.permanent = permanent;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public void startRunning(O simObject) {
|
||||
this.simulationObject = this.provider.createSimulationData(this.provider, simObject, this.builder);
|
||||
|
||||
if (this.simulationObject != null) {
|
||||
this.isRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRunning() {
|
||||
this.isRunning = false;
|
||||
this.simulationObject = null;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.isRunning;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.cloth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
/**
|
||||
* 0: Root,
|
||||
* 1: Thigh_R,
|
||||
* 2: "Leg_R",
|
||||
* 3: "Knee_R",
|
||||
* 4: "Thigh_L",
|
||||
* 5: "Leg_L",
|
||||
* 6: "Knee_L",
|
||||
* 7: "Torso",
|
||||
* 8: "Chest",
|
||||
* 9: "Head",
|
||||
* 10: "Shoulder_R",
|
||||
* 11: "Arm_R",
|
||||
* 12: "Hand_R",
|
||||
* 13: "Tool_R",
|
||||
* 14: "Elbow_R",
|
||||
* 15: "Shoulder_L",
|
||||
* 16: "Arm_L",
|
||||
* 17: "Hand_L",
|
||||
* 18: "Tool_L",
|
||||
* 19: "Elbow_L"
|
||||
**/
|
||||
|
||||
public class ClothColliderPresets {
|
||||
public static final List<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>> BIPED_SLIM = ImmutableList.<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>>builder()
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[1], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[2], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[4], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[5], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[7], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.125D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[8], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.3D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[9], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.25D, 0.0D, 0.2D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[11], new ClothSimulator.ClothOBBCollider(0.12D, 0.24D, 0.125D, -0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[12], new ClothSimulator.ClothOBBCollider(0.12D, 0.1875D, 0.125D, -0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[16], new ClothSimulator.ClothOBBCollider(0.12D, 0.24D, 0.125D, 0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[17], new ClothSimulator.ClothOBBCollider(0.12D, 0.1875D, 0.125D, 0.05D, 0.14D, 0.0D)))
|
||||
.build();
|
||||
|
||||
public static final List<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>> BIPED = ImmutableList.<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>>builder()
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[1], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[2], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[4], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[5], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[7], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.125D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[8], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.3D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[9], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.25D, 0.0D, 0.2D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[11], new ClothSimulator.ClothOBBCollider(0.13D, 0.24D, 0.13D, -0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[12], new ClothSimulator.ClothOBBCollider(0.13D, 0.1875D, 0.13D, -0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[16], new ClothSimulator.ClothOBBCollider(0.13D, 0.24D, 0.13D, 0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[17], new ClothSimulator.ClothOBBCollider(0.13D, 0.1875D, 0.13D, 0.0D, 0.14D, 0.0D)))
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.cloth;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.physics.SimulatableObject;
|
||||
|
||||
public interface ClothSimulatable extends SimulatableObject {
|
||||
@Nullable
|
||||
Armature getArmature();
|
||||
|
||||
@Nullable
|
||||
Animator getSimulatableAnimator();
|
||||
|
||||
boolean invalid();
|
||||
public Vec3 getObjectVelocity();
|
||||
public float getYRot();
|
||||
public float getYRotO();
|
||||
|
||||
// Cloth object requires providing location info for 2 steps before for accurate continuous collide detection.
|
||||
public Vec3 getAccurateCloakLocation(float partialFrame);
|
||||
public Vec3 getAccuratePartialLocation(float partialFrame);
|
||||
public float getAccurateYRot(float partialFrame);
|
||||
public float getYRotDelta(float partialFrame);
|
||||
public float getScale();
|
||||
public float getGravity();
|
||||
|
||||
ClothSimulator getClothSimulator();
|
||||
}
|
||||
1931
src/main/java/com/tiedup/remake/rig/cloth/ClothSimulator.java
Normal file
1931
src/main/java/com/tiedup/remake/rig/cloth/ClothSimulator.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.collider;
|
||||
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
/**
|
||||
* OBB géométrique forké depuis EF, strippé de la logique combat
|
||||
* (isCollide(Entity), drawInternal, updateAndSelectCollideEntity).
|
||||
* Ne garde que les données + transform() utilisés par ClothOBBCollider.
|
||||
*/
|
||||
public class OBBCollider {
|
||||
protected final Vec3 modelCenter;
|
||||
protected final AABB outerAABB;
|
||||
protected Vec3 worldCenter;
|
||||
protected final Vec3[] modelVertices;
|
||||
protected final Vec3[] modelNormals;
|
||||
protected Vec3[] rotatedVertices;
|
||||
protected Vec3[] rotatedNormals;
|
||||
protected Vec3f scale = new Vec3f(1.0F, 1.0F, 1.0F);
|
||||
|
||||
public OBBCollider(double vertexX, double vertexY, double vertexZ, double centerX, double centerY, double centerZ) {
|
||||
this(getInitialAABB(vertexX, vertexY, vertexZ, centerX, centerY, centerZ), vertexX, vertexY, vertexZ, centerX, centerY, centerZ);
|
||||
}
|
||||
|
||||
protected OBBCollider(AABB outerAABB, double vertexX, double vertexY, double vertexZ, double centerX, double centerY, double centerZ) {
|
||||
this.modelCenter = new Vec3(centerX, centerY, centerZ);
|
||||
this.outerAABB = outerAABB;
|
||||
this.worldCenter = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
|
||||
this.modelVertices = new Vec3[4];
|
||||
this.modelNormals = new Vec3[3];
|
||||
this.rotatedVertices = new Vec3[4];
|
||||
this.rotatedNormals = new Vec3[3];
|
||||
this.modelVertices[0] = new Vec3(vertexX, vertexY, -vertexZ);
|
||||
this.modelVertices[1] = new Vec3(vertexX, vertexY, vertexZ);
|
||||
this.modelVertices[2] = new Vec3(-vertexX, vertexY, vertexZ);
|
||||
this.modelVertices[3] = new Vec3(-vertexX, vertexY, -vertexZ);
|
||||
this.modelNormals[0] = new Vec3(1, 0, 0);
|
||||
this.modelNormals[1] = new Vec3(0, 1, 0);
|
||||
this.modelNormals[2] = new Vec3(0, 0, 1);
|
||||
this.rotatedVertices[0] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedVertices[1] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedVertices[2] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedVertices[3] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedNormals[0] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedNormals[1] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
this.rotatedNormals[2] = new Vec3(0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
|
||||
static AABB getInitialAABB(double posX, double posY, double posZ, double center_x, double center_y, double center_z) {
|
||||
double xLength = Math.abs(posX) + Math.abs(center_x);
|
||||
double yLength = Math.abs(posY) + Math.abs(center_y);
|
||||
double zLength = Math.abs(posZ) + Math.abs(center_z);
|
||||
double maxLength = Math.max(xLength, Math.max(yLength, zLength));
|
||||
return new AABB(maxLength, maxLength, maxLength, -maxLength, -maxLength, -maxLength);
|
||||
}
|
||||
|
||||
public void transform(OpenMatrix4f modelMatrix) {
|
||||
OpenMatrix4f noTranslation = modelMatrix.removeTranslation();
|
||||
|
||||
for (int i = 0; i < this.modelVertices.length; i++) {
|
||||
this.rotatedVertices[i] = OpenMatrix4f.transform(noTranslation, this.modelVertices[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.modelNormals.length; i++) {
|
||||
this.rotatedNormals[i] = OpenMatrix4f.transform(noTranslation, this.modelNormals[i]);
|
||||
}
|
||||
|
||||
this.scale = noTranslation.toScaleVector();
|
||||
this.worldCenter = OpenMatrix4f.transform(modelMatrix, this.modelCenter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.event;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class InitAnimatorEvent extends Event {
|
||||
private final LivingEntityPatch<?> entitypatch;
|
||||
private final Animator animator;
|
||||
|
||||
public InitAnimatorEvent(LivingEntityPatch<?> entitypatch, Animator animator) {
|
||||
this.entitypatch = entitypatch;
|
||||
this.animator = animator;
|
||||
}
|
||||
|
||||
public LivingEntityPatch<?> getEntityPatch() {
|
||||
return this.entitypatch;
|
||||
}
|
||||
|
||||
public Animator getAnimator() {
|
||||
return this.animator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.event;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import com.tiedup.remake.rig.render.PatchedEntityRenderer;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class PatchedRenderersEvent extends Event implements IModBusEvent {
|
||||
public static class Add extends PatchedRenderersEvent {
|
||||
private final Map<EntityType<?>, Function<EntityType<?>, PatchedEntityRenderer>> entityRendererProvider;
|
||||
private final EntityRendererProvider.Context context;
|
||||
|
||||
public Add(Map<EntityType<?>, Function<EntityType<?>, PatchedEntityRenderer>> entityRendererProvider, EntityRendererProvider.Context context) {
|
||||
this.entityRendererProvider = entityRendererProvider;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void addPatchedEntityRenderer(EntityType<?> entityType, Function<EntityType<?>, PatchedEntityRenderer> provider) {
|
||||
this.entityRendererProvider.put(entityType, provider);
|
||||
}
|
||||
|
||||
public EntityRendererProvider.Context getContext() {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.event;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.render.PatchedEntityRenderer;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class PrepareModelEvent extends Event {
|
||||
private final SkinnedMesh mesh;
|
||||
private final LivingEntityPatch<?> entitypatch;
|
||||
private final MultiBufferSource buffer;
|
||||
private final PoseStack poseStack;
|
||||
private final int packedLight;
|
||||
private final float partialTicks;
|
||||
|
||||
private final PatchedEntityRenderer<?, ?, ?, ?> renderer;
|
||||
|
||||
public PrepareModelEvent(PatchedEntityRenderer<?, ?, ?, ?> renderer, SkinnedMesh mesh, LivingEntityPatch<?> entitypatch, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
|
||||
this.renderer = renderer;
|
||||
this.mesh = mesh;
|
||||
this.entitypatch = entitypatch;
|
||||
this.buffer = buffer;
|
||||
this.poseStack = poseStack;
|
||||
this.packedLight = packedLight;
|
||||
this.partialTicks = partialTicks;
|
||||
}
|
||||
|
||||
public SkinnedMesh getMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
|
||||
public LivingEntityPatch<?> getEntityPatch() {
|
||||
return this.entitypatch;
|
||||
}
|
||||
|
||||
public MultiBufferSource getBuffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public PoseStack getPoseStack() {
|
||||
return this.poseStack;
|
||||
}
|
||||
|
||||
public int getPackedLight() {
|
||||
return this.packedLight;
|
||||
}
|
||||
|
||||
public float getPartialTicks() {
|
||||
return this.partialTicks;
|
||||
}
|
||||
|
||||
public PatchedEntityRenderer<?, ?, ?, ?> getRenderer() {
|
||||
return this.renderer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.exception;
|
||||
|
||||
public class AnimationInvokeException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AnimationInvokeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AnimationInvokeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AnimationInvokeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.exception;
|
||||
|
||||
public class AssetLoadingException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AssetLoadingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AssetLoadingException(String message, Throwable ex) {
|
||||
super(message, ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class AnimationTransformEntry {
|
||||
private static final String[] BINDING_PRIORITY = {JointTransform.PARENT, JointTransform.JOINT_LOCAL_TRANSFORM, JointTransform.ANIMATION_TRANSFORM, JointTransform.RESULT1, JointTransform.RESULT2};
|
||||
private final Map<String, Pair<OpenMatrix4f, MatrixOperation>> matrices = Maps.newHashMap();
|
||||
|
||||
public void put(String entryPosition, OpenMatrix4f matrix) {
|
||||
this.put(entryPosition, matrix, OpenMatrix4f::mul);
|
||||
}
|
||||
|
||||
public void put(String entryPosition, OpenMatrix4f matrix, MatrixOperation operation) {
|
||||
if (this.matrices.containsKey(entryPosition)) {
|
||||
Pair<OpenMatrix4f, MatrixOperation> appliedTransform = this.matrices.get(entryPosition);
|
||||
OpenMatrix4f result = appliedTransform.getSecond().mul(appliedTransform.getFirst(), matrix, null);
|
||||
this.matrices.put(entryPosition, Pair.of(result, operation));
|
||||
} else {
|
||||
this.matrices.put(entryPosition, Pair.of(new OpenMatrix4f(matrix), operation));
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f getResult() {
|
||||
OpenMatrix4f result = new OpenMatrix4f();
|
||||
|
||||
for (String entryName : BINDING_PRIORITY) {
|
||||
if (this.matrices.containsKey(entryName)) {
|
||||
Pair<OpenMatrix4f, MatrixOperation> pair = this.matrices.get(entryName);
|
||||
pair.getSecond().mul(result, pair.getFirst(), result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
357
src/main/java/com/tiedup/remake/rig/math/MathUtils.java
Normal file
357
src/main/java/com/tiedup/remake/rig/math/MathUtils.java
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class MathUtils {
|
||||
public static final Vec3 XP = new Vec3(1.0D, 0.0D, 0.0D);
|
||||
public static final Vec3 XN = new Vec3(-1.0D, 0.0D, 0.0D);
|
||||
public static final Vec3 YP = new Vec3(0.0D, 1.0D, 0.0D);
|
||||
public static final Vec3 YN = new Vec3(0.0D, -1.0D, 0.0D);
|
||||
public static final Vec3 ZP = new Vec3(0.0D, 0.0D, 1.0D);
|
||||
public static final Vec3 ZN = new Vec3(0.0D, 0.0D, -1.0D);
|
||||
|
||||
public static OpenMatrix4f getModelMatrixIntegral(float xPosO, float xPos, float yPosO, float yPos, float zPosO, float zPos, float xRotO, float xRot, float yRotO, float yRot, float partialTick, float scaleX, float scaleY, float scaleZ) {
|
||||
OpenMatrix4f modelMatrix = new OpenMatrix4f();
|
||||
Vec3f translation = new Vec3f(-(xPosO + (xPos - xPosO) * partialTick), ((yPosO + (yPos - yPosO) * partialTick)), -(zPosO + (zPos - zPosO) * partialTick));
|
||||
float partialXRot = Mth.rotLerp(partialTick, xRotO, xRot);
|
||||
float partialYRot = Mth.rotLerp(partialTick, yRotO, yRot);
|
||||
modelMatrix.translate(translation).rotateDeg(-partialYRot, Vec3f.Y_AXIS).rotateDeg(-partialXRot, Vec3f.X_AXIS).scale(scaleX, scaleY, scaleZ);
|
||||
|
||||
return modelMatrix;
|
||||
}
|
||||
|
||||
public static int getSign(double value) {
|
||||
return value > 0.0D ? 1 : -1;
|
||||
}
|
||||
|
||||
public static Vec3 getVectorForRotation(float pitch, float yaw) {
|
||||
float f = pitch * (float) Math.PI / 180F;
|
||||
float f1 = -yaw * (float) Math.PI / 180F;
|
||||
float f2 = Mth.cos(f1);
|
||||
float f3 = Mth.sin(f1);
|
||||
float f4 = Mth.cos(f);
|
||||
float f5 = Mth.sin(f);
|
||||
|
||||
return new Vec3(f3 * f4, -f5, f2 * f4);
|
||||
}
|
||||
|
||||
public static float lerpBetween(float f1, float f2, float zero2one) {
|
||||
float f = 0;
|
||||
|
||||
for (f = f2 - f1; f < -180.0F; f += 360.0F) {
|
||||
}
|
||||
|
||||
while (f >= 180.0F) {
|
||||
f -= 360.0F;
|
||||
}
|
||||
|
||||
return f1 + zero2one * f;
|
||||
}
|
||||
|
||||
public static float rotlerp(float from, float to, float limit) {
|
||||
float f = Mth.wrapDegrees(to - from);
|
||||
|
||||
if (f > limit) {
|
||||
f = limit;
|
||||
}
|
||||
|
||||
if (f < -limit) {
|
||||
f = -limit;
|
||||
}
|
||||
|
||||
float f1 = from + f;
|
||||
|
||||
while (f1 >= 180.0F) {
|
||||
f1 -= 360.0F;
|
||||
}
|
||||
|
||||
while (f1 <= -180.0F) {
|
||||
f1 += 360.0F;
|
||||
}
|
||||
|
||||
return f1;
|
||||
}
|
||||
|
||||
public static float wrapRadian(float pValue) {
|
||||
float maxRot = (float)Math.PI * 2.0F;
|
||||
float f = pValue % maxRot;
|
||||
|
||||
if (f >= Math.PI) {
|
||||
f -= maxRot;
|
||||
}
|
||||
|
||||
if (f < -Math.PI) {
|
||||
f += maxRot;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float findNearestRotation(float src, float rotation) {
|
||||
float diff = Math.abs(src - rotation);
|
||||
float idealRotation = rotation;
|
||||
int sign = Mth.sign(src - rotation);
|
||||
|
||||
if (sign == 0) {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
float next = idealRotation + sign * 360.0F;
|
||||
|
||||
if (Math.abs(src - next) > diff) {
|
||||
return idealRotation;
|
||||
}
|
||||
|
||||
idealRotation = next;
|
||||
diff = Math.abs(src - next);
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3 getNearestVector(Vec3 from, Vec3... vectors) {
|
||||
double minLength = 1000000.0D;
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double distSqr = from.distanceToSqr(vectors[i]);
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return vectors[index];
|
||||
}
|
||||
|
||||
public static Vec3 getNearestVector(Vec3 from, List<Vec3> vectors) {
|
||||
return getNearestVector(from, vectors.toArray(new Vec3[0]));
|
||||
}
|
||||
|
||||
private static final Matrix4f MATRIX4F = new Matrix4f();
|
||||
private static final Matrix3f MATRIX3F = new Matrix3f();
|
||||
|
||||
public static void mulStack(PoseStack poseStack, OpenMatrix4f mat) {
|
||||
OpenMatrix4f.exportToMojangMatrix(mat, MATRIX4F);
|
||||
MATRIX3F.set(MATRIX4F);
|
||||
poseStack.mulPoseMatrix(MATRIX4F);
|
||||
poseStack.last().normal().mul(MATRIX3F);
|
||||
}
|
||||
|
||||
public static double getAngleBetween(Vec3f a, Vec3f b) {
|
||||
Vec3f normA = Vec3f.normalize(a, null);
|
||||
Vec3f normB = Vec3f.normalize(b, null);
|
||||
|
||||
double cos = (normA.x * normB.x + normA.y * normB.y + normA.z * normB.z);
|
||||
return Math.toDegrees(Math.acos(cos));
|
||||
}
|
||||
|
||||
public static double getAngleBetween(Vec3 a, Vec3 b) {
|
||||
Vec3 normA = a.normalize();
|
||||
Vec3 normB = b.normalize();
|
||||
|
||||
double cos = (normA.x * normB.x + normA.y * normB.y + normA.z * normB.z);
|
||||
return Math.toDegrees(Math.safeAcos(cos));
|
||||
}
|
||||
|
||||
public static float getAngleBetween(Quaternionf a, Quaternionf b) {
|
||||
float dot = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
return 2.0F * (Math.safeAcos(MathUtils.getSign(dot) * b.w) - Math.safeAcos(a.w));
|
||||
}
|
||||
|
||||
public static double getXRotOfVector(Vec3 vec) {
|
||||
Vec3 normalized = vec.normalize();
|
||||
return -(Math.atan2(normalized.y, (float)Math.sqrt(normalized.x * normalized.x + normalized.z * normalized.z)) * (180D / Math.PI));
|
||||
}
|
||||
|
||||
public static double getYRotOfVector(Vec3 vec) {
|
||||
Vec3 normalized = vec.normalize();
|
||||
return Math.atan2(normalized.z, normalized.x) * (180D / Math.PI) - 90.0F;
|
||||
}
|
||||
|
||||
private static Quaternionf getQuaternionFromMatrix(OpenMatrix4f mat) {
|
||||
Quaternionf quat = new Quaternionf(0, 0, 0, 1);
|
||||
quat.setFromUnnormalized(OpenMatrix4f.exportToMojangMatrix(mat.transpose(null)));
|
||||
return quat;
|
||||
}
|
||||
|
||||
public static Vec3f lerpVector(Vec3f start, Vec3f end, float delta) {
|
||||
return lerpVector(start, end, delta, new Vec3f());
|
||||
}
|
||||
|
||||
public static Vec3f lerpVector(Vec3f start, Vec3f end, float delta, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = start.x + (end.x - start.x) * delta;
|
||||
dest.y = start.y + (end.y - start.y) * delta;
|
||||
dest.z = start.z + (end.z - start.z) * delta;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3 lerpVector(Vec3 start, Vec3 end, float delta) {
|
||||
return new Vec3(start.x + (end.x - start.x) * delta, start.y + (end.y - start.y) * delta, start.z + (end.z - start.z) * delta);
|
||||
}
|
||||
|
||||
public static Vec3 projectVector(Vec3 from, Vec3 to) {
|
||||
double dot = to.dot(from);
|
||||
double normalScale = 1.0D / ((to.x * to.x) + (to.y * to.y) + (to.z * to.z));
|
||||
|
||||
return new Vec3(dot * to.x * normalScale, dot * to.y * normalScale, dot * to.z * normalScale);
|
||||
}
|
||||
|
||||
public static Vec3f projectVector(Vec3f from, Vec3f to, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
float dot = Vec3f.dot(to, from);
|
||||
float normalScale = 1.0F / ((to.x * to.x) + (to.y * to.y) + (to.z * to.z));
|
||||
|
||||
dest.x = dot * to.x * normalScale;
|
||||
dest.y = dot * to.y * normalScale;
|
||||
dest.z = dot * to.z * normalScale;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Quaternionf lerpQuaternion(Quaternionf from, Quaternionf to, float delta) {
|
||||
return lerpQuaternion(from, to, delta, null);
|
||||
}
|
||||
|
||||
public static Quaternionf lerpQuaternion(Quaternionf from, Quaternionf to, float delta, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
float fromX = from.x();
|
||||
float fromY = from.y();
|
||||
float fromZ = from.z();
|
||||
float fromW = from.w();
|
||||
float toX = to.x();
|
||||
float toY = to.y();
|
||||
float toZ = to.z();
|
||||
float toW = to.w();
|
||||
float resultX;
|
||||
float resultY;
|
||||
float resultZ;
|
||||
float resultW;
|
||||
float dot = fromW * toW + fromX * toX + fromY * toY + fromZ * toZ;
|
||||
float blendI = 1.0F - delta;
|
||||
|
||||
if (dot < 0.0F) {
|
||||
resultW = blendI * fromW + delta * -toW;
|
||||
resultX = blendI * fromX + delta * -toX;
|
||||
resultY = blendI * fromY + delta * -toY;
|
||||
resultZ = blendI * fromZ + delta * -toZ;
|
||||
} else {
|
||||
resultW = blendI * fromW + delta * toW;
|
||||
resultX = blendI * fromX + delta * toX;
|
||||
resultY = blendI * fromY + delta * toY;
|
||||
resultZ = blendI * fromZ + delta * toZ;
|
||||
}
|
||||
|
||||
dest.set(resultX, resultY, resultZ, resultW);
|
||||
dest.normalize();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static Vector3f getScaleVectorFromMatrix(OpenMatrix4f mat) {
|
||||
Vec3f a = new Vec3f(mat.m00, mat.m10, mat.m20);
|
||||
Vec3f b = new Vec3f(mat.m01, mat.m11, mat.m21);
|
||||
Vec3f c = new Vec3f(mat.m02, mat.m12, mat.m22);
|
||||
return new Vector3f(a.length(), b.length(), c.length());
|
||||
}
|
||||
|
||||
public static <T> Set<Set<T>> getSubset(Collection<T> collection) {
|
||||
Set<Set<T>> subsets = new HashSet<> ();
|
||||
List<T> asList = new ArrayList<> (collection);
|
||||
createSubset(0, asList, new HashSet<> (), subsets);
|
||||
|
||||
return subsets;
|
||||
}
|
||||
|
||||
private static <T> void createSubset(int idx, List<T> elements, Set<T> parent, Set<Set<T>> subsets) {
|
||||
for (int i = idx; i < elements.size(); i++) {
|
||||
Set<T> subset = new HashSet<> (parent);
|
||||
subset.add(elements.get(i));
|
||||
subsets.add(subset);
|
||||
|
||||
createSubset(i + 1, elements, subset, subsets);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLeastAngleVectorIdx(Vec3f src, Vec3f... candidates) {
|
||||
int leastVectorIdx = -1;
|
||||
int current = 0;
|
||||
float maxDot = -10000.0F;
|
||||
|
||||
for (Vec3f normzlizedVec : Stream.of(candidates).map((vec) -> vec.normalize()).collect(Collectors.toList())) {
|
||||
float dot = Vec3f.dot(src, normzlizedVec);
|
||||
|
||||
if (maxDot < dot) {
|
||||
maxDot = dot;
|
||||
leastVectorIdx = current;
|
||||
}
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
return leastVectorIdx;
|
||||
}
|
||||
|
||||
public static boolean canBeSeen(Entity target, Entity watcher, double maxDistance) {
|
||||
if (target.level() != watcher.level()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double sqr = maxDistance * maxDistance;
|
||||
Level level = target.level();
|
||||
Vec3 vec1 = watcher.getEyePosition();
|
||||
|
||||
double height = target.getBoundingBox().maxY - target.getBoundingBox().minY;
|
||||
Vec3 vec2 = target.position().add(0.0D, height * 0.15D, 0.0D);
|
||||
Vec3 vec3 = target.position().add(0.0D, height * 0.5D, 0.0D);
|
||||
Vec3 vec4 = target.position().add(0.0D, height * 0.95D, 0.0D);
|
||||
|
||||
return vec1.distanceToSqr(vec2) < sqr && level.clip(new ClipContext(vec1, vec2, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS ||
|
||||
vec1.distanceToSqr(vec3) < sqr && level.clip(new ClipContext(vec1, vec3, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS ||
|
||||
vec1.distanceToSqr(vec4) < sqr && level.clip(new ClipContext(vec1, vec4, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
private MathUtils() {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MatrixOperation {
|
||||
OpenMatrix4f mul(OpenMatrix4f left, OpenMatrix4f right, OpenMatrix4f dest);
|
||||
}
|
||||
903
src/main/java/com/tiedup/remake/rig/math/OpenMatrix4f.java
Normal file
903
src/main/java/com/tiedup/remake/rig/math/OpenMatrix4f.java
Normal file
@@ -0,0 +1,903 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class OpenMatrix4f {
|
||||
private static final FloatBuffer MATRIX_TRANSFORMER = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
private static final Vec3f VECTOR_STORAGE = new Vec3f();
|
||||
private static final Vec4f VEC4_STORAGE = new Vec4f();
|
||||
|
||||
public static final OpenMatrix4f IDENTITY = new OpenMatrix4f();
|
||||
|
||||
/*
|
||||
* m00 m01 m02 m03
|
||||
* m10 m11 m12 m13
|
||||
* m20 m21 m22 m23
|
||||
* m30 m31 m32 m33
|
||||
*/
|
||||
public float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33;
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
public OpenMatrix4f() {
|
||||
this.setIdentity();
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final OpenMatrix4f src) {
|
||||
this(src, false);
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final OpenMatrix4f src, boolean immutable) {
|
||||
load(src);
|
||||
this.immutable = immutable;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final JointTransform jointTransform) {
|
||||
load(OpenMatrix4f.fromQuaternion(jointTransform.rotation()).translate(jointTransform.translation()).scale(jointTransform.scale()));
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(
|
||||
float m00, float m01, float m02, float m03
|
||||
, float m10, float m11, float m12, float m13
|
||||
, float m20, float m21, float m22, float m23
|
||||
, float m30, float m31, float m32, float m33
|
||||
) {
|
||||
this.m00 = m00;
|
||||
this.m01 = m01;
|
||||
this.m02 = m02;
|
||||
this.m03 = m03;
|
||||
this.m10 = m10;
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
this.m13 = m13;
|
||||
this.m20 = m20;
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
this.m23 = m23;
|
||||
this.m30 = m30;
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
this.m33 = m33;
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f setIdentity() {
|
||||
return setIdentity(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given matrix to be the identity matrix.
|
||||
* @param m The matrix to set to the identity
|
||||
* @return m
|
||||
*/
|
||||
public static OpenMatrix4f setIdentity(OpenMatrix4f m) {
|
||||
if (m.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
m.m00 = 1.0f;
|
||||
m.m01 = 0.0f;
|
||||
m.m02 = 0.0f;
|
||||
m.m03 = 0.0f;
|
||||
m.m10 = 0.0f;
|
||||
m.m11 = 1.0f;
|
||||
m.m12 = 0.0f;
|
||||
m.m13 = 0.0f;
|
||||
m.m20 = 0.0f;
|
||||
m.m21 = 0.0f;
|
||||
m.m22 = 1.0f;
|
||||
m.m23 = 0.0f;
|
||||
m.m30 = 0.0f;
|
||||
m.m31 = 0.0f;
|
||||
m.m32 = 0.0f;
|
||||
m.m33 = 1.0f;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public OpenMatrix4f load(OpenMatrix4f src) {
|
||||
return load(src, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = src.m00;
|
||||
dest.m01 = src.m01;
|
||||
dest.m02 = src.m02;
|
||||
dest.m03 = src.m03;
|
||||
dest.m10 = src.m10;
|
||||
dest.m11 = src.m11;
|
||||
dest.m12 = src.m12;
|
||||
dest.m13 = src.m13;
|
||||
dest.m20 = src.m20;
|
||||
dest.m21 = src.m21;
|
||||
dest.m22 = src.m22;
|
||||
dest.m23 = src.m23;
|
||||
dest.m30 = src.m30;
|
||||
dest.m31 = src.m31;
|
||||
dest.m32 = src.m32;
|
||||
dest.m33 = src.m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(@Nullable OpenMatrix4f dest, float[] elements) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = elements[0];
|
||||
dest.m01 = elements[1];
|
||||
dest.m02 = elements[2];
|
||||
dest.m03 = elements[3];
|
||||
dest.m10 = elements[4];
|
||||
dest.m11 = elements[5];
|
||||
dest.m12 = elements[6];
|
||||
dest.m13 = elements[7];
|
||||
dest.m20 = elements[8];
|
||||
dest.m21 = elements[9];
|
||||
dest.m22 = elements[10];
|
||||
dest.m23 = elements[11];
|
||||
dest.m30 = elements[12];
|
||||
dest.m31 = elements[13];
|
||||
dest.m32 = elements[14];
|
||||
dest.m33 = elements[15];
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(@Nullable OpenMatrix4f dest, FloatBuffer buf) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
buf.position(0);
|
||||
|
||||
dest.m00 = buf.get();
|
||||
dest.m01 = buf.get();
|
||||
dest.m02 = buf.get();
|
||||
dest.m03 = buf.get();
|
||||
dest.m10 = buf.get();
|
||||
dest.m11 = buf.get();
|
||||
dest.m12 = buf.get();
|
||||
dest.m13 = buf.get();
|
||||
dest.m20 = buf.get();
|
||||
dest.m21 = buf.get();
|
||||
dest.m22 = buf.get();
|
||||
dest.m23 = buf.get();
|
||||
dest.m30 = buf.get();
|
||||
dest.m31 = buf.get();
|
||||
dest.m32 = buf.get();
|
||||
dest.m33 = buf.get();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f load(FloatBuffer buf) {
|
||||
return OpenMatrix4f.load(this, buf);
|
||||
}
|
||||
|
||||
public OpenMatrix4f store(FloatBuffer buf) {
|
||||
buf.put(m00);
|
||||
buf.put(m01);
|
||||
buf.put(m02);
|
||||
buf.put(m03);
|
||||
buf.put(m10);
|
||||
buf.put(m11);
|
||||
buf.put(m12);
|
||||
buf.put(m13);
|
||||
buf.put(m20);
|
||||
buf.put(m21);
|
||||
buf.put(m22);
|
||||
buf.put(m23);
|
||||
buf.put(m30);
|
||||
buf.put(m31);
|
||||
buf.put(m32);
|
||||
buf.put(m33);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Float> toList() {
|
||||
List<Float> elements = Lists.newArrayList();
|
||||
|
||||
elements.add(0, m00);
|
||||
elements.add(1, m01);
|
||||
elements.add(2, m02);
|
||||
elements.add(3, m03);
|
||||
elements.add(4, m10);
|
||||
elements.add(5, m11);
|
||||
elements.add(6, m12);
|
||||
elements.add(7, m13);
|
||||
elements.add(8, m20);
|
||||
elements.add(9, m21);
|
||||
elements.add(10, m22);
|
||||
elements.add(11, m23);
|
||||
elements.add(12, m30);
|
||||
elements.add(13, m31);
|
||||
elements.add(14, m32);
|
||||
elements.add(15, m33);
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public OpenMatrix4f unmodifiable() {
|
||||
return new OpenMatrix4f(this, true);
|
||||
}
|
||||
|
||||
public OpenMatrix4f mulFront(OpenMatrix4f mulTransform) {
|
||||
return OpenMatrix4f.mul(mulTransform, this, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f mulBack(OpenMatrix4f mulTransform) {
|
||||
return OpenMatrix4f.mul(this, mulTransform, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mul(OpenMatrix4f left, OpenMatrix4f right, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float m00 = left.m00 * right.m00 + left.m10 * right.m01 + left.m20 * right.m02 + left.m30 * right.m03;
|
||||
float m01 = left.m01 * right.m00 + left.m11 * right.m01 + left.m21 * right.m02 + left.m31 * right.m03;
|
||||
float m02 = left.m02 * right.m00 + left.m12 * right.m01 + left.m22 * right.m02 + left.m32 * right.m03;
|
||||
float m03 = left.m03 * right.m00 + left.m13 * right.m01 + left.m23 * right.m02 + left.m33 * right.m03;
|
||||
float m10 = left.m00 * right.m10 + left.m10 * right.m11 + left.m20 * right.m12 + left.m30 * right.m13;
|
||||
float m11 = left.m01 * right.m10 + left.m11 * right.m11 + left.m21 * right.m12 + left.m31 * right.m13;
|
||||
float m12 = left.m02 * right.m10 + left.m12 * right.m11 + left.m22 * right.m12 + left.m32 * right.m13;
|
||||
float m13 = left.m03 * right.m10 + left.m13 * right.m11 + left.m23 * right.m12 + left.m33 * right.m13;
|
||||
float m20 = left.m00 * right.m20 + left.m10 * right.m21 + left.m20 * right.m22 + left.m30 * right.m23;
|
||||
float m21 = left.m01 * right.m20 + left.m11 * right.m21 + left.m21 * right.m22 + left.m31 * right.m23;
|
||||
float m22 = left.m02 * right.m20 + left.m12 * right.m21 + left.m22 * right.m22 + left.m32 * right.m23;
|
||||
float m23 = left.m03 * right.m20 + left.m13 * right.m21 + left.m23 * right.m22 + left.m33 * right.m23;
|
||||
float m30 = left.m00 * right.m30 + left.m10 * right.m31 + left.m20 * right.m32 + left.m30 * right.m33;
|
||||
float m31 = left.m01 * right.m30 + left.m11 * right.m31 + left.m21 * right.m32 + left.m31 * right.m33;
|
||||
float m32 = left.m02 * right.m30 + left.m12 * right.m31 + left.m22 * right.m32 + left.m32 * right.m33;
|
||||
float m33 = left.m03 * right.m30 + left.m13 * right.m31 + left.m23 * right.m32 + left.m33 * right.m33;
|
||||
|
||||
dest.m00 = m00;
|
||||
dest.m01 = m01;
|
||||
dest.m02 = m02;
|
||||
dest.m03 = m03;
|
||||
dest.m10 = m10;
|
||||
dest.m11 = m11;
|
||||
dest.m12 = m12;
|
||||
dest.m13 = m13;
|
||||
dest.m20 = m20;
|
||||
dest.m21 = m21;
|
||||
dest.m22 = m22;
|
||||
dest.m23 = m23;
|
||||
dest.m30 = m30;
|
||||
dest.m31 = m31;
|
||||
dest.m32 = m32;
|
||||
dest.m33 = m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mulMatrices(OpenMatrix4f... srcs) {
|
||||
OpenMatrix4f result = new OpenMatrix4f();
|
||||
|
||||
for (OpenMatrix4f src : srcs) {
|
||||
result.mulBack(src);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mulAsOriginInverse(OpenMatrix4f left, OpenMatrix4f right, OpenMatrix4f dest) {
|
||||
float x = left.m30;
|
||||
float y = left.m31;
|
||||
float z = left.m32;
|
||||
|
||||
OpenMatrix4f result = mul(right, left, dest);
|
||||
result.m30 = x;
|
||||
result.m31 = y;
|
||||
result.m32 = z;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Vec4f transform(OpenMatrix4f matrix, Vec4f src, @Nullable Vec4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec4f();
|
||||
}
|
||||
|
||||
float x = matrix.m00 * src.x + matrix.m10 * src.y + matrix.m20 * src.z + matrix.m30 * src.w;
|
||||
float y = matrix.m01 * src.x + matrix.m11 * src.y + matrix.m21 * src.z + matrix.m31 * src.w;
|
||||
float z = matrix.m02 * src.x + matrix.m12 * src.y + matrix.m22 * src.z + matrix.m32 * src.w;
|
||||
float w = matrix.m03 * src.x + matrix.m13 * src.y + matrix.m23 * src.z + matrix.m33 * src.w;
|
||||
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.z = z;
|
||||
dest.w = w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3 transform(OpenMatrix4f matrix, Vec3 src) {
|
||||
double x = matrix.m00 * src.x + matrix.m10 * src.y + matrix.m20 * src.z + matrix.m30;
|
||||
double y = matrix.m01 * src.x + matrix.m11 * src.y + matrix.m21 * src.z + matrix.m31;
|
||||
double z = matrix.m02 * src.x + matrix.m12 * src.y + matrix.m22 * src.z + matrix.m32;
|
||||
|
||||
return new Vec3(x, y ,z);
|
||||
}
|
||||
|
||||
public static Vec3f transform3v(OpenMatrix4f matrix, Vec3f src, @Nullable Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
VEC4_STORAGE.set(src.x, src.y, src.z, 1.0F);
|
||||
|
||||
Vec4f result = transform(matrix, VEC4_STORAGE, null);
|
||||
dest.x = result.x;
|
||||
dest.y = result.y;
|
||||
dest.z = result.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f transpose() {
|
||||
return transpose(this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f transpose(OpenMatrix4f dest) {
|
||||
return transpose(this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f transpose(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float m00 = src.m00;
|
||||
float m01 = src.m10;
|
||||
float m02 = src.m20;
|
||||
float m03 = src.m30;
|
||||
float m10 = src.m01;
|
||||
float m11 = src.m11;
|
||||
float m12 = src.m21;
|
||||
float m13 = src.m31;
|
||||
float m20 = src.m02;
|
||||
float m21 = src.m12;
|
||||
float m22 = src.m22;
|
||||
float m23 = src.m32;
|
||||
float m30 = src.m03;
|
||||
float m31 = src.m13;
|
||||
float m32 = src.m23;
|
||||
float m33 = src.m33;
|
||||
|
||||
dest.m00 = m00;
|
||||
dest.m01 = m01;
|
||||
dest.m02 = m02;
|
||||
dest.m03 = m03;
|
||||
dest.m10 = m10;
|
||||
dest.m11 = m11;
|
||||
dest.m12 = m12;
|
||||
dest.m13 = m13;
|
||||
dest.m20 = m20;
|
||||
dest.m21 = m21;
|
||||
dest.m22 = m22;
|
||||
dest.m23 = m23;
|
||||
dest.m30 = m30;
|
||||
dest.m31 = m31;
|
||||
dest.m32 = m32;
|
||||
dest.m33 = m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public float determinant() {
|
||||
float f = m00 * ((m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32) - m13 * m22 * m31 - m11 * m23 * m32 - m12 * m21 * m33);
|
||||
f -= m01 * ((m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32) - m13 * m22 * m30 - m10 * m23 * m32 - m12 * m20 * m33);
|
||||
f += m02 * ((m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31) - m13 * m21 * m30 - m10 * m23 * m31 - m11 * m20 * m33);
|
||||
f -= m03 * ((m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31) - m12 * m21 * m30 - m10 * m22 * m31 - m11 * m20 * m32);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
private static float determinant3x3(float t00, float t01, float t02, float t10, float t11, float t12, float t20, float t21, float t22) {
|
||||
return t00 * (t11 * t22 - t12 * t21) + t01 * (t12 * t20 - t10 * t22) + t02 * (t10 * t21 - t11 * t20);
|
||||
}
|
||||
|
||||
public OpenMatrix4f invert() {
|
||||
return OpenMatrix4f.invert(this, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f invert(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
float determinant = src.determinant();
|
||||
|
||||
if (determinant != 0.0F) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float determinant_inv = 1.0F / determinant;
|
||||
|
||||
float t00 = determinant3x3(src.m11, src.m12, src.m13, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33);
|
||||
float t01 = -determinant3x3(src.m10, src.m12, src.m13, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33);
|
||||
float t02 = determinant3x3(src.m10, src.m11, src.m13, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33);
|
||||
float t03 = -determinant3x3(src.m10, src.m11, src.m12, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32);
|
||||
float t10 = -determinant3x3(src.m01, src.m02, src.m03, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33);
|
||||
float t11 = determinant3x3(src.m00, src.m02, src.m03, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33);
|
||||
float t12 = -determinant3x3(src.m00, src.m01, src.m03, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33);
|
||||
float t13 = determinant3x3(src.m00, src.m01, src.m02, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32);
|
||||
float t20 = determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m31, src.m32, src.m33);
|
||||
float t21 = -determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m30, src.m32, src.m33);
|
||||
float t22 = determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m30, src.m31, src.m33);
|
||||
float t23 = -determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m30, src.m31, src.m32);
|
||||
float t30 = -determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m21, src.m22, src.m23);
|
||||
float t31 = determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m20, src.m22, src.m23);
|
||||
float t32 = -determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m20, src.m21, src.m23);
|
||||
float t33 = determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m20, src.m21, src.m22);
|
||||
|
||||
dest.m00 = t00 * determinant_inv;
|
||||
dest.m11 = t11 * determinant_inv;
|
||||
dest.m22 = t22 * determinant_inv;
|
||||
dest.m33 = t33 * determinant_inv;
|
||||
dest.m01 = t10 * determinant_inv;
|
||||
dest.m10 = t01 * determinant_inv;
|
||||
dest.m20 = t02 * determinant_inv;
|
||||
dest.m02 = t20 * determinant_inv;
|
||||
dest.m12 = t21 * determinant_inv;
|
||||
dest.m21 = t12 * determinant_inv;
|
||||
dest.m03 = t30 * determinant_inv;
|
||||
dest.m30 = t03 * determinant_inv;
|
||||
dest.m13 = t31 * determinant_inv;
|
||||
dest.m31 = t13 * determinant_inv;
|
||||
dest.m32 = t23 * determinant_inv;
|
||||
dest.m23 = t32 * determinant_inv;
|
||||
|
||||
return dest;
|
||||
} else {
|
||||
dest.setIdentity();
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(float x, float y, float z) {
|
||||
VECTOR_STORAGE.set(x, y, z);
|
||||
return translate(VECTOR_STORAGE, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(Vec3f vec) {
|
||||
return translate(vec, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(Vec3f vec, OpenMatrix4f dest) {
|
||||
return translate(vec, this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f translate(Vec3f vec, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m30 += src.m00 * vec.x + src.m10 * vec.y + src.m20 * vec.z;
|
||||
dest.m31 += src.m01 * vec.x + src.m11 * vec.y + src.m21 * vec.z;
|
||||
dest.m32 += src.m02 * vec.x + src.m12 * vec.y + src.m22 * vec.z;
|
||||
dest.m33 += src.m03 * vec.x + src.m13 * vec.y + src.m23 * vec.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f createTranslation(float x, float y, float z) {
|
||||
return ofTranslation(x, y, z, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f ofTranslation(float x, float y, float z, OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.setIdentity();
|
||||
dest.m30 = x;
|
||||
dest.m31 = y;
|
||||
dest.m32 = z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotateDeg(float angle, Vec3f axis) {
|
||||
return rotate((float)Math.toRadians(angle), axis);
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotate(float angle, Vec3f axis) {
|
||||
return rotate(angle, axis, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotate(float angle, Vec3f axis, OpenMatrix4f dest) {
|
||||
return rotate(angle, axis, this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f createRotatorDeg(float degree, Vec3f axis) {
|
||||
return rotate((float)Math.toRadians(degree), axis, new OpenMatrix4f(), null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f ofRotationDegree(float degree, Vec3f axis, @Nullable OpenMatrix4f dest) {
|
||||
dest.setIdentity();
|
||||
return rotate((float)Math.toRadians(degree), axis, dest, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f rotate(float angle, Vec3f axis, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float c = (float) Math.cos(angle);
|
||||
float s = (float) Math.sin(angle);
|
||||
float oneminusc = 1.0f - c;
|
||||
float xy = axis.x * axis.y;
|
||||
float yz = axis.y * axis.z;
|
||||
float xz = axis.x * axis.z;
|
||||
float xs = axis.x * s;
|
||||
float ys = axis.y * s;
|
||||
float zs = axis.z * s;
|
||||
|
||||
float f00 = axis.x * axis.x * oneminusc+c;
|
||||
float f01 = xy * oneminusc + zs;
|
||||
float f02 = xz * oneminusc - ys;
|
||||
// n[3] not used
|
||||
float f10 = xy * oneminusc - zs;
|
||||
float f11 = axis.y * axis.y * oneminusc+c;
|
||||
float f12 = yz * oneminusc + xs;
|
||||
// n[7] not used
|
||||
float f20 = xz * oneminusc + ys;
|
||||
float f21 = yz * oneminusc - xs;
|
||||
float f22 = axis.z * axis.z * oneminusc+c;
|
||||
|
||||
float t00 = src.m00 * f00 + src.m10 * f01 + src.m20 * f02;
|
||||
float t01 = src.m01 * f00 + src.m11 * f01 + src.m21 * f02;
|
||||
float t02 = src.m02 * f00 + src.m12 * f01 + src.m22 * f02;
|
||||
float t03 = src.m03 * f00 + src.m13 * f01 + src.m23 * f02;
|
||||
float t10 = src.m00 * f10 + src.m10 * f11 + src.m20 * f12;
|
||||
float t11 = src.m01 * f10 + src.m11 * f11 + src.m21 * f12;
|
||||
float t12 = src.m02 * f10 + src.m12 * f11 + src.m22 * f12;
|
||||
float t13 = src.m03 * f10 + src.m13 * f11 + src.m23 * f12;
|
||||
|
||||
dest.m20 = src.m00 * f20 + src.m10 * f21 + src.m20 * f22;
|
||||
dest.m21 = src.m01 * f20 + src.m11 * f21 + src.m21 * f22;
|
||||
dest.m22 = src.m02 * f20 + src.m12 * f21 + src.m22 * f22;
|
||||
dest.m23 = src.m03 * f20 + src.m13 * f21 + src.m23 * f22;
|
||||
dest.m00 = t00;
|
||||
dest.m01 = t01;
|
||||
dest.m02 = t02;
|
||||
dest.m03 = t03;
|
||||
dest.m10 = t10;
|
||||
dest.m11 = t11;
|
||||
dest.m12 = t12;
|
||||
dest.m13 = t13;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f toTranslationVector() {
|
||||
return toTranslationVector(this);
|
||||
}
|
||||
|
||||
public Vec3f toTranslationVector(Vec3f dest) {
|
||||
return toTranslationVector(this, dest);
|
||||
}
|
||||
|
||||
public static Vec3f toTranslationVector(OpenMatrix4f matrix) {
|
||||
return toTranslationVector(matrix, null);
|
||||
}
|
||||
|
||||
public static Vec3f toTranslationVector(OpenMatrix4f matrix, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = matrix.m30;
|
||||
dest.y = matrix.m31;
|
||||
dest.z = matrix.m32;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Quaternionf toQuaternion() {
|
||||
return OpenMatrix4f.toQuaternion(this);
|
||||
}
|
||||
|
||||
public Quaternionf toQuaternion(Quaternionf dest) {
|
||||
return OpenMatrix4f.toQuaternion(this, dest);
|
||||
}
|
||||
|
||||
public static Quaternionf toQuaternion(OpenMatrix4f matrix) {
|
||||
return toQuaternion(matrix, new Quaternionf());
|
||||
}
|
||||
|
||||
private static final OpenMatrix4f MATRIX_STORAGE = new OpenMatrix4f();
|
||||
|
||||
public static Quaternionf toQuaternion(OpenMatrix4f matrix, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
OpenMatrix4f.load(matrix, MATRIX_STORAGE);
|
||||
|
||||
float w, x, y, z;
|
||||
MATRIX_STORAGE.transpose();
|
||||
|
||||
float lenX = MATRIX_STORAGE.m00 * MATRIX_STORAGE.m00 + MATRIX_STORAGE.m01 * MATRIX_STORAGE.m01 + MATRIX_STORAGE.m02 * MATRIX_STORAGE.m02;
|
||||
float lenY = MATRIX_STORAGE.m10 * MATRIX_STORAGE.m10 + MATRIX_STORAGE.m11 * MATRIX_STORAGE.m11 + MATRIX_STORAGE.m12 * MATRIX_STORAGE.m12;
|
||||
float lenZ = MATRIX_STORAGE.m20 * MATRIX_STORAGE.m20 + MATRIX_STORAGE.m21 * MATRIX_STORAGE.m21 + MATRIX_STORAGE.m22 * MATRIX_STORAGE.m22;
|
||||
|
||||
if (lenX == 0.0F || lenY == 0.0F || lenZ == 0.0F) {
|
||||
return new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
lenX = Math.invsqrt(lenX);
|
||||
lenY = Math.invsqrt(lenY);
|
||||
lenZ = Math.invsqrt(lenZ);
|
||||
|
||||
MATRIX_STORAGE.m00 *= lenX; MATRIX_STORAGE.m01 *= lenX; MATRIX_STORAGE.m02 *= lenX;
|
||||
MATRIX_STORAGE.m10 *= lenY; MATRIX_STORAGE.m11 *= lenY; MATRIX_STORAGE.m12 *= lenY;
|
||||
MATRIX_STORAGE.m20 *= lenZ; MATRIX_STORAGE.m21 *= lenZ; MATRIX_STORAGE.m22 *= lenZ;
|
||||
|
||||
float t;
|
||||
float tr = MATRIX_STORAGE.m00 + MATRIX_STORAGE.m11 + MATRIX_STORAGE.m22;
|
||||
|
||||
if (tr >= 0.0F) {
|
||||
t = (float)Math.sqrt(tr + 1.0F);
|
||||
w = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
x = (MATRIX_STORAGE.m12 - MATRIX_STORAGE.m21) * t;
|
||||
y = (MATRIX_STORAGE.m20 - MATRIX_STORAGE.m02) * t;
|
||||
z = (MATRIX_STORAGE.m01 - MATRIX_STORAGE.m10) * t;
|
||||
} else {
|
||||
if (MATRIX_STORAGE.m00 >= MATRIX_STORAGE.m11 && MATRIX_STORAGE.m00 >= MATRIX_STORAGE.m22) {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m00 - (MATRIX_STORAGE.m11 + MATRIX_STORAGE.m22) + 1.0);
|
||||
x = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
y = (MATRIX_STORAGE.m10 + MATRIX_STORAGE.m01) * t;
|
||||
z = (MATRIX_STORAGE.m02 + MATRIX_STORAGE.m20) * t;
|
||||
w = (MATRIX_STORAGE.m12 - MATRIX_STORAGE.m21) * t;
|
||||
} else if (MATRIX_STORAGE.m11 > MATRIX_STORAGE.m22) {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m11 - (MATRIX_STORAGE.m22 + MATRIX_STORAGE.m00) + 1.0F);
|
||||
y = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
z = (MATRIX_STORAGE.m21 + MATRIX_STORAGE.m12) * t;
|
||||
x = (MATRIX_STORAGE.m10 + MATRIX_STORAGE.m01) * t;
|
||||
w = (MATRIX_STORAGE.m20 - MATRIX_STORAGE.m02) * t;
|
||||
} else {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m22 - (MATRIX_STORAGE.m00 + MATRIX_STORAGE.m11) + 1.0F);
|
||||
z = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
x = (MATRIX_STORAGE.m02 + MATRIX_STORAGE.m20) * t;
|
||||
y = (MATRIX_STORAGE.m21 + MATRIX_STORAGE.m12) * t;
|
||||
w = (MATRIX_STORAGE.m01 - MATRIX_STORAGE.m10) * t;
|
||||
}
|
||||
}
|
||||
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.z = z;
|
||||
dest.w = w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f fromQuaternion(Quaternionf quaternion) {
|
||||
return fromQuaternion(quaternion, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f fromQuaternion(Quaternionf quaternion, OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float x = quaternion.x();
|
||||
float y = quaternion.y();
|
||||
float z = quaternion.z();
|
||||
float w = quaternion.w();
|
||||
float xy = x * y;
|
||||
float xz = x * z;
|
||||
float xw = x * w;
|
||||
float yz = y * z;
|
||||
float yw = y * w;
|
||||
float zw = z * w;
|
||||
float xSquared = 2F * x * x;
|
||||
float ySquared = 2F * y * y;
|
||||
float zSquared = 2F * z * z;
|
||||
dest.m00 = 1.0F - ySquared - zSquared;
|
||||
dest.m01 = 2.0F * (xy - zw);
|
||||
dest.m02 = 2.0F * (xz + yw);
|
||||
dest.m10 = 2.0F * (xy + zw);
|
||||
dest.m11 = 1.0F - xSquared - zSquared;
|
||||
dest.m12 = 2.0F * (yz - xw);
|
||||
dest.m20 = 2.0F * (xz - yw);
|
||||
dest.m21 = 2.0F * (yz + xw);
|
||||
dest.m22 = 1.0F - xSquared - ySquared;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f scale(float x, float y, float z) {
|
||||
VECTOR_STORAGE.set(x, y, z);
|
||||
return this.scale(VECTOR_STORAGE);
|
||||
}
|
||||
|
||||
public OpenMatrix4f scale(Vec3f vec) {
|
||||
return scale(vec, this, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f scale(Vec3f vec, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = src.m00 * vec.x;
|
||||
dest.m01 = src.m01 * vec.x;
|
||||
dest.m02 = src.m02 * vec.x;
|
||||
dest.m03 = src.m03 * vec.x;
|
||||
dest.m10 = src.m10 * vec.y;
|
||||
dest.m11 = src.m11 * vec.y;
|
||||
dest.m12 = src.m12 * vec.y;
|
||||
dest.m13 = src.m13 * vec.y;
|
||||
dest.m20 = src.m20 * vec.z;
|
||||
dest.m21 = src.m21 * vec.z;
|
||||
dest.m22 = src.m22 * vec.z;
|
||||
dest.m23 = src.m23 * vec.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f toScaleVector() {
|
||||
return toScaleVector(null);
|
||||
}
|
||||
|
||||
public Vec3f toScaleVector(Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
VECTOR_STORAGE.set(this.m00, this.m01, this.m02);
|
||||
dest.x = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(this.m10, this.m11, this.m12);
|
||||
dest.y = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(this.m20, this.m21, this.m22);
|
||||
dest.z = VECTOR_STORAGE.length();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f removeTranslation() {
|
||||
return removeTranslation(this, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f removeTranslation(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.load(src);
|
||||
dest.m30 = 0.0F;
|
||||
dest.m31 = 0.0F;
|
||||
dest.m32 = 0.0F;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f removeScale() {
|
||||
return removeScale(this, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f removeScale(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
VECTOR_STORAGE.set(src.m00, src.m01, src.m02);
|
||||
float xScale = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(src.m10, src.m11, src.m12);
|
||||
float yScale = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(src.m20, src.m21, src.m22);
|
||||
float zScale = VECTOR_STORAGE.length();
|
||||
|
||||
dest.load(src);
|
||||
dest.scale(1.0F / xScale, 1.0F / yScale, 1.0F / zScale);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\n" +
|
||||
String.format("%.4f", m00) + " " + String.format("%.4f", m01) + " " + String.format("%.4f", m02) + " " + String.format("%.4f", m03) + "\n" +
|
||||
String.format("%.4f", m10) + " " + String.format("%.4f", m11) + " " + String.format("%.4f", m12) + " " + String.format("%.4f", m13) + "\n" +
|
||||
String.format("%.4f", m20) + " " + String.format("%.4f", m21) + " " + String.format("%.4f", m22) + " " + String.format("%.4f", m23) + "\n" +
|
||||
String.format("%.4f", m30) + " " + String.format("%.4f", m31) + " " + String.format("%.4f", m32) + " " + String.format("%.4f", m33) + "\n"
|
||||
;
|
||||
}
|
||||
|
||||
public static Matrix4f exportToMojangMatrix(OpenMatrix4f src) {
|
||||
return exportToMojangMatrix(src, null);
|
||||
}
|
||||
|
||||
public static Matrix4f exportToMojangMatrix(OpenMatrix4f src, Matrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Matrix4f();
|
||||
}
|
||||
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
src.store(MATRIX_TRANSFORMER);
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
|
||||
return dest.set(MATRIX_TRANSFORMER);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f importFromMojangMatrix(Matrix4f src) {
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
src.get(MATRIX_TRANSFORMER);
|
||||
|
||||
return OpenMatrix4f.load(null, MATRIX_TRANSFORMER);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f[] allocateMatrixArray(int size) {
|
||||
OpenMatrix4f[] matrixArray = new OpenMatrix4f[size];
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
matrixArray[i] = new OpenMatrix4f();
|
||||
}
|
||||
|
||||
return matrixArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public class QuaternionUtils {
|
||||
public static Axis XN = new Axis(-1.0F, 0.0F, 0.0F);
|
||||
public static Axis XP = new Axis(1.0F, 0.0F, 0.0F);
|
||||
public static Axis YN = new Axis(0.0F, -1.0F, 0.0F);
|
||||
public static Axis YP = new Axis(0.0F, 1.0F, 0.0F);
|
||||
public static Axis ZN = new Axis(0.0F, 0.0F, -1.0F);
|
||||
public static Axis ZP = new Axis(0.0F, 0.0F, 1.0F);
|
||||
|
||||
public static Quaternionf rotationDegrees(Vector3f axis, float degress) {
|
||||
float angle = degress * (float) Math.PI / 180;
|
||||
return rotation(axis, angle);
|
||||
}
|
||||
|
||||
public static Quaternionf rotation(Vector3f axis, float angle) {
|
||||
Quaternionf quat = new Quaternionf();
|
||||
quat.setAngleAxis(angle, axis.x, axis.y, axis.z);
|
||||
return quat;
|
||||
}
|
||||
|
||||
public static class Axis {
|
||||
private final Vector3f axis;
|
||||
|
||||
public Axis(float x, float y, float z) {
|
||||
this.axis = new Vector3f(x, y, z);
|
||||
}
|
||||
|
||||
public Quaternionf rotation(float angle) {
|
||||
return QuaternionUtils.rotation(axis, angle);
|
||||
}
|
||||
|
||||
public Quaternionf rotationDegrees(float degrees) {
|
||||
return QuaternionUtils.rotationDegrees(axis, degrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/main/java/com/tiedup/remake/rig/math/ValueModifier.java
Normal file
139
src/main/java/com/tiedup/remake/rig/math/ValueModifier.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
public interface ValueModifier {
|
||||
public static final Codec<Unified> CODEC =
|
||||
RecordCodecBuilder.create(instance -> instance.group(
|
||||
Codec.FLOAT.fieldOf("adder").forGetter(Unified::adder),
|
||||
Codec.FLOAT.fieldOf("multiplier").forGetter(Unified::multiplier),
|
||||
Codec.FLOAT.fieldOf("setter").forGetter(Unified::setter)
|
||||
).apply(instance, Unified::new)
|
||||
);
|
||||
|
||||
public void attach(ResultCalculator calculator);
|
||||
|
||||
public static ValueModifier adder(float value) {
|
||||
return new Adder(value);
|
||||
}
|
||||
|
||||
public static ValueModifier multiplier(float value) {
|
||||
return new Multiplier(value);
|
||||
}
|
||||
|
||||
public static ValueModifier setter(float arg) {
|
||||
return new Setter(arg);
|
||||
}
|
||||
|
||||
public static record Adder(float adder) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
calculator.add += this.adder;
|
||||
}
|
||||
}
|
||||
|
||||
public static record Multiplier(float multiplier) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
calculator.multiply *= this.multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static record Setter(float setter) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.setter;
|
||||
} else if (!Float.isNaN(this.setter)) {
|
||||
calculator.set = Math.min(calculator.set, this.setter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static record Unified(float adder, float multiplier, float setter) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.setter;
|
||||
} else if (!Float.isNaN(this.setter)) {
|
||||
calculator.set = Math.min(calculator.set, this.setter);
|
||||
}
|
||||
|
||||
calculator.add += this.adder;
|
||||
calculator.multiply *= this.multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static ResultCalculator calculator() {
|
||||
return new ResultCalculator();
|
||||
}
|
||||
|
||||
public static class ResultCalculator implements ValueModifier {
|
||||
private float set = Float.NaN;
|
||||
private float add = 0.0F;
|
||||
private float multiply = 1.0F;
|
||||
|
||||
public ResultCalculator attach(ValueModifier valueModifier) {
|
||||
valueModifier.attach(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.set;
|
||||
} else if (!Float.isNaN(this.set)) {
|
||||
calculator.set = Math.min(calculator.set, this.set);
|
||||
}
|
||||
|
||||
calculator.add += this.add;
|
||||
calculator.multiply *= this.multiply;
|
||||
}
|
||||
|
||||
public ValueModifier toValueModifier() {
|
||||
if (Float.isNaN(this.set)) {
|
||||
if (Float.compare(this.add, 0.0F) == 0 && Float.compare(this.multiply, 1.0F) != 0) {
|
||||
return new Multiplier(this.multiply);
|
||||
} else if (Float.compare(this.add, 0.0F) != 0 && Float.compare(this.multiply, 1.0F) == 0) {
|
||||
return new Adder(this.add);
|
||||
}
|
||||
} else if (Float.compare(this.add, 0.0F) == 0 && Float.compare(this.multiply, 1.0F) == 0) {
|
||||
return new Setter(this.set);
|
||||
}
|
||||
|
||||
return new Unified(this.set, this.add, this.multiply);
|
||||
}
|
||||
|
||||
public void set(float f) {
|
||||
this.set = f;
|
||||
}
|
||||
|
||||
public void add(float f) {
|
||||
this.add += add;
|
||||
}
|
||||
|
||||
public void multiply(float f) {
|
||||
this.multiply *= f;
|
||||
}
|
||||
|
||||
public float getResult(float baseValue) {
|
||||
float result = baseValue;
|
||||
|
||||
if (!Float.isNaN(this.set)) {
|
||||
result = this.set;
|
||||
}
|
||||
|
||||
result += this.add;
|
||||
result *= this.multiply;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/main/java/com/tiedup/remake/rig/math/Vec2f.java
Normal file
33
src/main/java/com/tiedup/remake/rig/math/Vec2f.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
public class Vec2f {
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public Vec2f() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
public Vec2f(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Vec2f scale(float f) {
|
||||
this.x *= f;
|
||||
this.y *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vec2f[" + this.x + ", " + this.y + ", " + "]";
|
||||
}
|
||||
}
|
||||
465
src/main/java/com/tiedup/remake/rig/math/Vec3f.java
Normal file
465
src/main/java/com/tiedup/remake/rig/math/Vec3f.java
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class Vec3f extends Vec2f {
|
||||
public static final Vec3f X_AXIS = new Vec3f(1.0F, 0.0F, 0.0F);
|
||||
public static final Vec3f Y_AXIS = new Vec3f(0.0F, 1.0F, 0.0F);
|
||||
public static final Vec3f Z_AXIS = new Vec3f(0.0F, 0.0F, 1.0F);
|
||||
public static final Vec3f M_X_AXIS = new Vec3f(-1.0F, 0.0F, 0.0F);
|
||||
public static final Vec3f M_Y_AXIS = new Vec3f(0.0F, -1.0F, 0.0F);
|
||||
public static final Vec3f M_Z_AXIS = new Vec3f(0.0F, 0.0F, -1.0F);
|
||||
public static final Vec3f ZERO = new Vec3f(0.0F, 0.0F, 0.0F);
|
||||
|
||||
public float z;
|
||||
|
||||
public Vec3f() {
|
||||
super();
|
||||
this.z = 0;
|
||||
}
|
||||
|
||||
public Vec3f(float x, float y, float z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public Vec3f(double x, double y, double z) {
|
||||
this((float)x, (float)y, (float)z);
|
||||
}
|
||||
|
||||
public Vec3f(Vec3 mojangVec) {
|
||||
this((float)mojangVec.x, (float)mojangVec.y, (float)mojangVec.z);
|
||||
}
|
||||
|
||||
public Vec3f set(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f set(Vec3 vec3f) {
|
||||
this.x = (float)vec3f.x;
|
||||
this.y = (float)vec3f.y;
|
||||
this.z = (float)vec3f.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f set(Vec3f vec3f) {
|
||||
this.x = vec3f.x;
|
||||
this.y = vec3f.y;
|
||||
this.z = vec3f.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f add(float x, float y, float z) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f add(Vec3f vec) {
|
||||
return this.add(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public Vec3f add(Vec3 vec) {
|
||||
return this.add((float)vec.x, (float)vec.y, (float)vec.z);
|
||||
}
|
||||
|
||||
public Vec3f sub(float x, float y, float z) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
this.z -= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f sub(Vec3f vec) {
|
||||
return this.sub(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public static Vec3f add(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
return new Vec3f(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
} else {
|
||||
dest.set(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3f sub(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
return new Vec3f(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
} else {
|
||||
dest.set(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3f multiply(Vec3f vec) {
|
||||
return multiply(this, this, vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public Vec3f multiply(float x, float y, float z) {
|
||||
return multiply(this, this, x, y, z);
|
||||
}
|
||||
|
||||
public static Vec3f multiply(Vec3f src, Vec3f dest, float x, float y, float z) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = src.x * x;
|
||||
dest.y = src.y * y;
|
||||
dest.z = src.z * z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3f scale(float f) {
|
||||
return scale(this, this, f);
|
||||
}
|
||||
|
||||
public static Vec3f scale(Vec3f src, Vec3f dest, float f) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = src.x * f;
|
||||
dest.y = src.y * f;
|
||||
dest.z = src.z * f;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f copy() {
|
||||
return new Vec3f(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public float length() {
|
||||
return (float) Math.sqrt(this.lengthSqr());
|
||||
}
|
||||
|
||||
public float lengthSqr() {
|
||||
return this.x * this.x + this.y * this.y + this.z * this.z;
|
||||
}
|
||||
|
||||
public float distance(Vec3f opponent) {
|
||||
return (float)Math.sqrt(this.distanceSqr(opponent));
|
||||
}
|
||||
|
||||
public float distanceSqr(Vec3f opponent) {
|
||||
return (float)(Math.pow(this.x - opponent.x, 2) + Math.pow(this.y - opponent.y, 2) + Math.pow(this.z - opponent.z, 2));
|
||||
}
|
||||
|
||||
public float horizontalDistance() {
|
||||
return (float)Math.sqrt(this.x * this.x + this.z * this.z);
|
||||
}
|
||||
|
||||
public float horizontalDistanceSqr() {
|
||||
return this.x * this.x + this.z * this.z;
|
||||
}
|
||||
|
||||
public void rotate(float degree, Vec3f axis) {
|
||||
rotate(degree, axis, this, this);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
this.x = Float.NaN;
|
||||
this.y = Float.NaN;
|
||||
this.z = Float.NaN;
|
||||
}
|
||||
|
||||
public boolean validateValues() {
|
||||
return Float.isFinite(this.x) && Float.isFinite(this.y) && Float.isFinite(this.z);
|
||||
}
|
||||
|
||||
public static Vec3f rotate(float degree, Vec3f axis, Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
return OpenMatrix4f.transform3v(OpenMatrix4f.createRotatorDeg(degree, axis), src, dest);
|
||||
}
|
||||
|
||||
private static final Vector3f SRC = new Vector3f();
|
||||
private static final Vector3f TRANSFORM_RESULT = new Vector3f();
|
||||
|
||||
public static Vec3f rotate(Quaternionf rot, Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
SRC.set(src.x, src.y, src.z);
|
||||
rot.transform(SRC, TRANSFORM_RESULT);
|
||||
dest.set(TRANSFORM_RESULT.x, TRANSFORM_RESULT.y, TRANSFORM_RESULT.z);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static float dot(Vec3f left, Vec3f right) {
|
||||
return left.x * right.x + left.y * right.y + left.z * right.z;
|
||||
}
|
||||
|
||||
public static Vec3f cross(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(left.y * right.z - left.z * right.y, right.x * left.z - right.z * left.x, left.x * right.y - left.y * right.x);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static float getAngleBetween(Vec3f a, Vec3f b) {
|
||||
return (float) Math.acos(Math.min(1.0F, Vec3f.dot(a, b) / (a.length() * b.length())));
|
||||
}
|
||||
|
||||
public static Quaternionf getRotatorBetween(Vec3f a, Vec3f b, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
Vec3f axis = Vec3f.cross(a, b, null).normalize();
|
||||
float dotDivLength = Vec3f.dot(a, b) / (a.length() * b.length());
|
||||
|
||||
if (!Float.isFinite(dotDivLength)) {
|
||||
TiedUpRigConstants.LOGGER.info("Warning : given vector's length is zero");
|
||||
(new IllegalArgumentException()).printStackTrace();
|
||||
dotDivLength = 1.0F;
|
||||
}
|
||||
|
||||
float radian = (float)Math.acos(Math.min(1.0F, dotDivLength));
|
||||
dest.setAngleAxis(radian, axis.x, axis.y, axis.z);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3f interpolate(Vec3f from, Vec3f to, float interpolation, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = from.x + (to.x - from.x) * interpolation;
|
||||
dest.y = from.y + (to.y - from.y) * interpolation;
|
||||
dest.z = from.z + (to.z - from.z) * interpolation;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f normalize() {
|
||||
return normalize(this, this);
|
||||
}
|
||||
|
||||
public static Vec3f normalize(Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
float norm = (float) Math.sqrt(src.x * src.x + src.y * src.y + src.z * src.z);
|
||||
|
||||
if (norm > 1E-5F) {
|
||||
dest.x = src.x / norm;
|
||||
dest.y = src.y / norm;
|
||||
dest.z = src.z / norm;
|
||||
} else {
|
||||
dest.x = 0;
|
||||
dest.y = 0;
|
||||
dest.z = 0;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o instanceof Vec3f vec3f) {
|
||||
return Float.compare(this.x, vec3f.x) == 0 && Float.compare(this.y, vec3f.y) == 0 && Float.compare(this.z, vec3f.z) == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int j = Float.floatToIntBits(this.x);
|
||||
int i = (int) (j ^ j >>> 32);
|
||||
j = Float.floatToIntBits(this.y);
|
||||
i = 31 * i + (int) (j ^ j >>> 32);
|
||||
j = Float.floatToIntBits(this.z);
|
||||
|
||||
return 31 * i + (int) (j ^ j >>> 32);
|
||||
}
|
||||
|
||||
public static Vec3f average(Collection<Vec3f> vectors, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (Vec3f v : vectors) {
|
||||
dest.add(v);
|
||||
}
|
||||
|
||||
dest.scale(1.0F / vectors.size());
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3f average(Vec3f dest, Vec3f... vectors) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (Vec3f v : vectors) {
|
||||
dest.add(v);
|
||||
}
|
||||
|
||||
dest.scale(vectors.length);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static int getNearest(Vec3f from, List<Vec3f> vectors) {
|
||||
float minLength = Float.MAX_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.size(); i++) {
|
||||
if (vectors.get(i) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors.get(i).validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distSqr = from.distanceSqr(vectors.get(i));
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int getNearest(Vec3f from, Vec3f... vectors) {
|
||||
float minLength = Float.MAX_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors[i].validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distSqr = from.distanceSqr(vectors[i]);
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int getMostSimilar(Vec3f start, Vec3f end, Vec3f... vectors) {
|
||||
Vec3f.sub(end, start, BASIS_DIRECTION);
|
||||
float maxDot = Float.MIN_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors[i].validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3f.sub(vectors[i], start, COMPARISION);
|
||||
float dot = Vec3f.dot(BASIS_DIRECTION, COMPARISION) / BASIS_DIRECTION.length() * COMPARISION.length();
|
||||
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static final Vec3f BASIS_DIRECTION = new Vec3f();
|
||||
private static final Vec3f COMPARISION = new Vec3f();
|
||||
|
||||
public static int getMostSimilar(Vec3f start, Vec3f end, List<Vec3f> vectors) {
|
||||
Vec3f.sub(end, start, BASIS_DIRECTION);
|
||||
float maxDot = Float.MIN_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.size(); i++) {
|
||||
if (vectors.get(i) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors.get(i).validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3f.sub(vectors.get(i), start, COMPARISION);
|
||||
float dot = Vec3f.dot(BASIS_DIRECTION, COMPARISION) / BASIS_DIRECTION.length() * COMPARISION.length();
|
||||
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public Vec3 toDoubleVector() {
|
||||
return new Vec3(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public static Vec3f fromDoubleVector(Vec3 vec3) {
|
||||
return new Vec3f((float)vec3.x(), (float)vec3.y(), (float)vec3.z());
|
||||
}
|
||||
|
||||
private static final OpenMatrix4f DEST = new OpenMatrix4f();
|
||||
|
||||
public Vec3f rotateDegree(Vec3f axis, float degree) {
|
||||
OpenMatrix4f.ofRotationDegree(degree, axis, DEST);
|
||||
OpenMatrix4f.transform3v(DEST, this, this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
75
src/main/java/com/tiedup/remake/rig/math/Vec4f.java
Normal file
75
src/main/java/com/tiedup/remake/rig/math/Vec4f.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.math;
|
||||
|
||||
public class Vec4f extends Vec3f {
|
||||
public float w;
|
||||
|
||||
public Vec4f() {
|
||||
super();
|
||||
this.w = 0;
|
||||
}
|
||||
|
||||
public Vec4f(float x, float y, float z, float w) {
|
||||
super(x, y, z);
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public Vec4f(Vec3f vec3f) {
|
||||
super(vec3f.x, vec3f.y, vec3f.z);
|
||||
this.w = 1.0F;
|
||||
}
|
||||
|
||||
public void set(float x, float y, float z, float w) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public void set(Vec4f vec4f) {
|
||||
super.set(vec4f);
|
||||
this.w = vec4f.w;
|
||||
}
|
||||
|
||||
public Vec4f add(float x, float y, float z, float w) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
this.w += w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Vec4f add(Vec4f left, Vec4f right, Vec4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec4f();
|
||||
}
|
||||
|
||||
dest.x = left.x + right.x;
|
||||
dest.y = left.y + right.y;
|
||||
dest.z = left.z + right.z;
|
||||
dest.w = left.w + right.w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4f scale(float f) {
|
||||
super.scale(f);
|
||||
this.w *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec4f transform(OpenMatrix4f matrix) {
|
||||
return OpenMatrix4f.transform(matrix, this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vec4f[" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + "]";
|
||||
}
|
||||
}
|
||||
104
src/main/java/com/tiedup/remake/rig/mesh/ClassicMesh.java
Normal file
104
src/main/java/com/tiedup/remake/rig/mesh/ClassicMesh.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import com.tiedup.remake.rig.mesh.ClassicMesh.ClassicMeshPart;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class ClassicMesh extends StaticMesh<ClassicMeshPart> {
|
||||
public ClassicMesh(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, ClassicMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, partBuilders, parent, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, ClassicMeshPart> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders) {
|
||||
Map<String, ClassicMeshPart> parts = Maps.newHashMap();
|
||||
|
||||
partBuilders.forEach((partDefinition, vertexBuilder) -> {
|
||||
parts.put(partDefinition.partName(), new ClassicMeshPart(vertexBuilder, partDefinition.renderProperties(), partDefinition.getModelPartAnimationProvider()));
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassicMeshPart getOrLogException(Map<String, ClassicMeshPart> parts, String name) {
|
||||
if (!parts.containsKey(name)) {
|
||||
TiedUpRigConstants.LOGGER.debug("Can not find the mesh part named " + name + " in " + this.getClass().getCanonicalName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
for (ClassicMeshPart part : this.parts.values()) {
|
||||
part.draw(poseStack, vertexConsumer, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, Armature armature, OpenMatrix4f[] poses) {
|
||||
this.draw(poseStack, vertexConsumer, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
|
||||
public class ClassicMeshPart extends MeshPart {
|
||||
public ClassicMeshPart(List<VertexBuilder> verticies, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
super(verticies, renderProperties, vanillaPartTracer);
|
||||
}
|
||||
|
||||
protected static final Vector4f POSITION = new Vector4f();
|
||||
protected static final Vector3f NORMAL = new Vector3f();
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
if (this.isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4f color = this.getColor(r, g, b, a);
|
||||
poseStack.pushPose();
|
||||
OpenMatrix4f transform = this.getVanillaPartTransform();
|
||||
|
||||
if (transform != null) {
|
||||
poseStack.mulPoseMatrix(OpenMatrix4f.exportToMojangMatrix(transform));
|
||||
}
|
||||
|
||||
Matrix4f matrix4f = poseStack.last().pose();
|
||||
Matrix3f matrix3f = poseStack.last().normal();
|
||||
|
||||
for (VertexBuilder vi : this.getVertices()) {
|
||||
getVertexPosition(vi.position, POSITION);
|
||||
getVertexNormal(vi.normal, NORMAL);
|
||||
POSITION.mul(matrix4f);
|
||||
NORMAL.mul(matrix3f);
|
||||
|
||||
drawingFunction.draw(bufferbuilder, POSITION.x(), POSITION.y(), POSITION.z(), NORMAL.x(), NORMAL.y(), NORMAL.z(), packedLight, color.x, color.y, color.z, color.w, uvs[vi.uv * 2], uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/main/java/com/tiedup/remake/rig/mesh/CompositeMesh.java
Normal file
77
src/main/java/com/tiedup/remake/rig/mesh/CompositeMesh.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObjectBuilder;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public class CompositeMesh implements Mesh, SoftBodyTranslatable {
|
||||
private final StaticMesh<?> staticMesh;
|
||||
private final SoftBodyTranslatable softBodyMesh;
|
||||
|
||||
public CompositeMesh(StaticMesh<?> staticMesh, SoftBodyTranslatable softBodyMesh) {
|
||||
this.staticMesh = staticMesh;
|
||||
this.softBodyMesh = softBodyMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.staticMesh.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
this.staticMesh.draw(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
this.softBodyMesh.getOriginalMesh().draw(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, Armature armature, OpenMatrix4f[] poses) {
|
||||
this.staticMesh.drawPosed(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
this.softBodyMesh.getOriginalMesh().drawPosed(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStartSoftBodySimulation() {
|
||||
return this.softBodyMesh.canStartSoftBodySimulation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClothObject createSimulationData(@Nullable SoftBodyTranslatable provider, ClothSimulatable simOwner, ClothObjectBuilder simBuilder) {
|
||||
return this.softBodyMesh.createSimulationData(this, simOwner, simBuilder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StaticMesh<?> getStaticMesh() {
|
||||
return this.staticMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StaticMesh<?> getOriginalMesh() {
|
||||
return (StaticMesh<?>)this.softBodyMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
this.softBodyMesh.putSoftBodySimulationInfo(sofyBodySimulationInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
return this.softBodyMesh.getSoftBodySimulationInfo();
|
||||
}
|
||||
}
|
||||
57
src/main/java/com/tiedup/remake/rig/mesh/HumanoidMesh.java
Normal file
57
src/main/java/com/tiedup/remake/rig/mesh/HumanoidMesh.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.Meshes;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.VertexBuilder;
|
||||
|
||||
public class HumanoidMesh extends SkinnedMesh {
|
||||
public final SkinnedMeshPart head;
|
||||
public final SkinnedMeshPart torso;
|
||||
public final SkinnedMeshPart leftArm;
|
||||
public final SkinnedMeshPart rightArm;
|
||||
public final SkinnedMeshPart leftLeg;
|
||||
public final SkinnedMeshPart rightLeg;
|
||||
public final SkinnedMeshPart hat;
|
||||
public final SkinnedMeshPart jacket;
|
||||
public final SkinnedMeshPart leftSleeve;
|
||||
public final SkinnedMeshPart rightSleeve;
|
||||
public final SkinnedMeshPart leftPants;
|
||||
public final SkinnedMeshPart rightPants;
|
||||
|
||||
public HumanoidMesh(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<VertexBuilder>> parts, SkinnedMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, parts, parent, properties);
|
||||
|
||||
this.head = this.getOrLogException(this.parts, "head");
|
||||
this.torso = this.getOrLogException(this.parts, "torso");
|
||||
this.leftArm = this.getOrLogException(this.parts, "leftArm");
|
||||
this.rightArm = this.getOrLogException(this.parts, "rightArm");
|
||||
this.leftLeg = this.getOrLogException(this.parts, "leftLeg");
|
||||
this.rightLeg = this.getOrLogException(this.parts, "rightLeg");
|
||||
|
||||
this.hat = this.getOrLogException(this.parts, "hat");
|
||||
this.jacket = this.getOrLogException(this.parts, "jacket");
|
||||
this.leftSleeve = this.getOrLogException(this.parts, "leftSleeve");
|
||||
this.rightSleeve = this.getOrLogException(this.parts, "rightSleeve");
|
||||
this.leftPants = this.getOrLogException(this.parts, "leftPants");
|
||||
this.rightPants = this.getOrLogException(this.parts, "rightPants");
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends SkinnedMesh> getHumanoidArmorModel(EquipmentSlot slot) {
|
||||
// RIG : Meshes.{HELMET,CHESTPLATE,LEGGINS,BOOTS} strippés en Phase 0 (armor rendering
|
||||
// hors scope bondage V1). Re-implém Phase 2 si besoin de rendre armures vanilla
|
||||
// sur le rig — pour l'instant retour null = armor rendering off.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
214
src/main/java/com/tiedup/remake/rig/mesh/Mesh.java
Normal file
214
src/main/java/com/tiedup/remake/rig/mesh/Mesh.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.client.extensions.IForgeVertexConsumer;
|
||||
import net.minecraftforge.client.model.IQuadTransformer;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
|
||||
public interface Mesh {
|
||||
|
||||
void initialize();
|
||||
|
||||
/* Draw wihtout mesh deformation */
|
||||
void draw(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay);
|
||||
|
||||
/* Draw with mesh deformation */
|
||||
void drawPosed(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses);
|
||||
|
||||
/* Universal method */
|
||||
default void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
this.drawPosed(poseStack, bufferSources.getBuffer(TiedUpRenderTypes.getTriangulated(renderType)), drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
public static record RenderProperties(ResourceLocation customTexturePath, Vec3f customColor, boolean isTransparent) {
|
||||
public static class Builder {
|
||||
protected String customTexturePath;
|
||||
protected Vec3f customColor = new Vec3f();
|
||||
protected boolean isTransparent;
|
||||
|
||||
public RenderProperties.Builder customTexturePath(String path) {
|
||||
this.customTexturePath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties.Builder transparency(boolean isTransparent) {
|
||||
this.isTransparent = isTransparent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties.Builder customColor(float r, float g, float b) {
|
||||
this.customColor.x = r;
|
||||
this.customColor.y = g;
|
||||
this.customColor.z = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties build() {
|
||||
return new RenderProperties(this.customTexturePath == null ? null : ResourceLocation.parse(this.customTexturePath), this.customColor, this.isTransparent);
|
||||
}
|
||||
|
||||
public static RenderProperties.Builder create() {
|
||||
return new RenderProperties.Builder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DrawingFunction {
|
||||
public static final DrawingFunction NEW_ENTITY = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ, r, g, b, a, u, v, overlay, packedLight, normX, normY, normZ);
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX_COLOR_NORMAL = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.color(r, g, b, a);
|
||||
builder.normal(normX, normY, normZ);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX_COLOR_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_NORMAL = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.normal(normX, normY, normZ);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_TEX_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv(u, v);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public void draw(VertexConsumer vertexConsumer, float posX, float posY, float posZ, float normX, float normY, float normZ, int packedLight, float r, float g, float b, float a, float u, float v, int overlay);
|
||||
|
||||
default void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, VertexConsumer vertexConsumer, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, boolean readExistingColor) {
|
||||
putBulkDataWithDrawingFunction(this, vertexConsumer, pose, bakedQuad, new float[] { 1.0F, 1.0F, 1.0F, 1.0F }, red, green, blue, alpha, new int[] { packedLight, packedLight, packedLight, packedLight }, packedOverlay, readExistingColor);
|
||||
}
|
||||
|
||||
static void putBulkDataWithDrawingFunction(DrawingFunction drawingFunction, VertexConsumer builder, PoseStack.Pose pPoseEntry, BakedQuad pQuad, float[] pColorMuls, float pRed, float pGreen, float pBlue, float alpha, int[] pCombinedLights, int pCombinedOverlay, boolean pMulColor) {
|
||||
float[] afloat = new float[] { pColorMuls[0], pColorMuls[1], pColorMuls[2], pColorMuls[3] };
|
||||
int[] aint1 = pQuad.getVertices();
|
||||
Vec3i vec3i = pQuad.getDirection().getNormal();
|
||||
Matrix4f matrix4f = pPoseEntry.pose();
|
||||
Vector3f vector3f = pPoseEntry.normal().transform(new Vector3f((float) vec3i.getX(), (float) vec3i.getY(), (float) vec3i.getZ()));
|
||||
int j = aint1.length / 8;
|
||||
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
ByteBuffer bytebuffer = memorystack.malloc(DefaultVertexFormat.BLOCK.getVertexSize());
|
||||
IntBuffer intbuffer = bytebuffer.asIntBuffer();
|
||||
|
||||
for (int k = 0; k < j; ++k) {
|
||||
intbuffer.clear();
|
||||
intbuffer.put(aint1, k * 8, 8);
|
||||
float f = bytebuffer.getFloat(0);
|
||||
float f1 = bytebuffer.getFloat(4);
|
||||
float f2 = bytebuffer.getFloat(8);
|
||||
float f3;
|
||||
float f4;
|
||||
float f5;
|
||||
|
||||
if (pMulColor) {
|
||||
float f6 = (float) (bytebuffer.get(12) & 255) / 255.0F;
|
||||
float f7 = (float) (bytebuffer.get(13) & 255) / 255.0F;
|
||||
float f8 = (float) (bytebuffer.get(14) & 255) / 255.0F;
|
||||
f3 = f6 * afloat[k] * pRed;
|
||||
f4 = f7 * afloat[k] * pGreen;
|
||||
f5 = f8 * afloat[k] * pBlue;
|
||||
} else {
|
||||
f3 = afloat[k] * pRed;
|
||||
f4 = afloat[k] * pGreen;
|
||||
f5 = afloat[k] * pBlue;
|
||||
}
|
||||
|
||||
int l = applyBakedLighting(pCombinedLights[k], bytebuffer);
|
||||
float f9 = bytebuffer.getFloat(16);
|
||||
float f10 = bytebuffer.getFloat(20);
|
||||
Vector4f vector4f = matrix4f.transform(new Vector4f(f, f1, f2, 1.0F));
|
||||
applyBakedNormals(vector3f, bytebuffer, pPoseEntry.normal());
|
||||
float vertexAlpha = pMulColor ? alpha * (float) (bytebuffer.get(15) & 255) / 255.0F : alpha;
|
||||
drawingFunction.draw(builder, vector4f.x(), vector4f.y(), vector4f.z(), vector3f.x(), vector3f.y(), vector3f.z(), l, f3, f4, f5, vertexAlpha, f9, f10, pCombinedOverlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Code copy from {@link IForgeVertexConsumer#applyBakedLighting}
|
||||
*/
|
||||
static int applyBakedLighting(int packedLight, ByteBuffer data) {
|
||||
int bl = packedLight & 0xFFFF;
|
||||
int sl = (packedLight >> 16) & 0xFFFF;
|
||||
int offset = IQuadTransformer.UV2 * 4; // int offset for vertex 0 * 4 bytes per int
|
||||
int blBaked = Short.toUnsignedInt(data.getShort(offset));
|
||||
int slBaked = Short.toUnsignedInt(data.getShort(offset + 2));
|
||||
bl = Math.max(bl, blBaked);
|
||||
sl = Math.max(sl, slBaked);
|
||||
return bl | (sl << 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Code copy from {@link IForgeVertexConsumer#applyBakedNormals}
|
||||
*/
|
||||
static void applyBakedNormals(Vector3f generated, ByteBuffer data, Matrix3f normalTransform) {
|
||||
byte nx = data.get(28);
|
||||
byte ny = data.get(29);
|
||||
byte nz = data.get(30);
|
||||
if (nx != 0 || ny != 0 || nz != 0)
|
||||
{
|
||||
generated.set(nx / 127f, ny / 127f, nz / 127f);
|
||||
generated.mul(normalTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/main/java/com/tiedup/remake/rig/mesh/MeshPart.java
Normal file
89
src/main/java/com/tiedup/remake/rig/mesh/MeshPart.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
|
||||
public abstract class MeshPart {
|
||||
protected final List<VertexBuilder> verticies;
|
||||
protected final Mesh.RenderProperties renderProperties;
|
||||
protected final Supplier<OpenMatrix4f> vanillaPartTracer;
|
||||
protected boolean isHidden;
|
||||
|
||||
public MeshPart(List<VertexBuilder> vertices, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
this.verticies = vertices;
|
||||
this.renderProperties = renderProperties;
|
||||
this.vanillaPartTracer = vanillaPartTracer;
|
||||
}
|
||||
|
||||
public abstract void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay);
|
||||
|
||||
public void setHidden(boolean hidden) {
|
||||
this.isHidden = hidden;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public List<VertexBuilder> getVertices() {
|
||||
return this.verticies;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getVanillaPartTransform() {
|
||||
if (this.vanillaPartTracer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.vanillaPartTracer.get();
|
||||
}
|
||||
|
||||
public VertexConsumer getBufferBuilder(RenderType renderType, MultiBufferSource bufferSource) {
|
||||
if (this.renderProperties.customTexturePath() != null) {
|
||||
return bufferSource.getBuffer(TiedUpRenderTypes.replaceTexture(this.renderProperties.customTexturePath(), renderType));
|
||||
}
|
||||
|
||||
return bufferSource.getBuffer(renderType);
|
||||
}
|
||||
|
||||
protected static final Vector4f COLOR = new Vector4f();
|
||||
|
||||
public Vector4f getColor(float r, float g, float b, float a) {
|
||||
if (this.renderProperties != null && this.renderProperties.customColor() != null) {
|
||||
COLOR.set(
|
||||
this.renderProperties.customColor().x
|
||||
, this.renderProperties.customColor().y
|
||||
, this.renderProperties.customColor().z
|
||||
, a
|
||||
);
|
||||
|
||||
return COLOR;
|
||||
} else {
|
||||
COLOR.set(
|
||||
r
|
||||
, g
|
||||
, b
|
||||
, a
|
||||
);
|
||||
|
||||
return COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public interface MeshPartDefinition {
|
||||
String partName();
|
||||
Mesh.RenderProperties renderProperties();
|
||||
Supplier<OpenMatrix4f> getModelPartAnimationProvider();
|
||||
}
|
||||
185
src/main/java/com/tiedup/remake/rig/mesh/Meshes.java
Normal file
185
src/main/java/com/tiedup/remake/rig/mesh/Meshes.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.mesh.Mesh.RenderProperties;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObjectBuilder;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class Meshes implements PreparableReloadListener {
|
||||
private static final Map<ResourceLocation, MeshAccessor<? extends Mesh>> ACCESSORS = Maps.newHashMap();
|
||||
private static final Map<MeshAccessor<? extends Mesh>, Mesh> MESHES = Maps.newHashMap();
|
||||
private static ResourceManager resourceManager = null;
|
||||
|
||||
//For resource reloader
|
||||
public static final Meshes INSTANCE = new Meshes();
|
||||
|
||||
// RIG : on garde uniquement les meshes humanoïdes (joueur + NPCs bondage).
|
||||
// Les mob meshes combat EF (Creeper/Dragon/Spider/etc.) et armor/particle/cape
|
||||
// ont été retirés du fork — hors scope bondage.
|
||||
public static final MeshAccessor<HumanoidMesh> ALEX = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_slim_arm", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED_OLD_TEX = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_old_texture", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED_OUTLAYER = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_outlayer", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
|
||||
public static void reload(ResourceManager resourceManager) {
|
||||
Meshes.resourceManager = resourceManager;
|
||||
|
||||
ACCESSORS.entrySet().removeIf(entry -> !entry.getValue().inRegistry);
|
||||
|
||||
MESHES.values().forEach((mesh) -> {
|
||||
if (mesh instanceof SkinnedMesh skinnedMesh) {
|
||||
skinnedMesh.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
MESHES.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <M extends Mesh> AssetAccessor<M> get(ResourceLocation id) {
|
||||
return (AssetAccessor<M>) ACCESSORS.get(id);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M extends Mesh> AssetAccessor<M> getOrCreate(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader) {
|
||||
return ACCESSORS.containsKey(id) ? (AssetAccessor<M>)ACCESSORS.get(id) : MeshAccessor.create(id, jsonLoader, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M extends Mesh> Set<AssetAccessor<M>> entry(Class<? extends Mesh> filter) {
|
||||
return ACCESSORS.values().stream().filter((accessor) -> filter.isAssignableFrom(accessor.get().getClass())).map((accessor) -> (AssetAccessor<M>)accessor).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static ResourceLocation wrapLocation(ResourceLocation rl) {
|
||||
return rl.getPath().matches("animmodels/.*\\.json") ? rl : ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), "animmodels/" + rl.getPath() + ".json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> reload(PreparableReloadListener.PreparationBarrier stage, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
Meshes.reload(resourceManager);
|
||||
}, gameExecutor).thenCompose(stage::wait);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MeshContructor<P extends MeshPart, V extends VertexBuilder, M extends StaticMesh<P>> {
|
||||
M invoke(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<V>> parts, M parent, RenderProperties properties);
|
||||
}
|
||||
|
||||
public static record MeshAccessor<M extends Mesh> (ResourceLocation registryName, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) implements AssetAccessor<M>, SoftBodyTranslatable {
|
||||
public static <M extends Mesh> MeshAccessor<M> create(String namespaceId, String path, Function<JsonAssetLoader, M> jsonLoader) {
|
||||
return create(ResourceLocation.fromNamespaceAndPath(namespaceId, path), jsonLoader, true);
|
||||
}
|
||||
|
||||
private static <M extends Mesh> MeshAccessor<M> create(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) {
|
||||
MeshAccessor<M> accessor = new MeshAccessor<M> (id, jsonLoader, inRegistry);
|
||||
ACCESSORS.put(id, accessor);
|
||||
return accessor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public M get() {
|
||||
if (!MESHES.containsKey(this)) {
|
||||
JsonAssetLoader jsonModelLoader = new JsonAssetLoader(resourceManager, wrapLocation(this.registryName));
|
||||
MESHES.put(this, this.jsonLoader.apply(jsonModelLoader));
|
||||
}
|
||||
|
||||
return (M)MESHES.get(this);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.registryName.toString();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.registryName.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
} else if (obj instanceof MeshAccessor armatureAccessor) {
|
||||
return this.registryName.equals(armatureAccessor.registryName());
|
||||
} else if (obj instanceof ResourceLocation rl) {
|
||||
return this.registryName.equals(rl);
|
||||
} else if (obj instanceof String name) {
|
||||
return this.registryName.toString().equals(name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStartSoftBodySimulation() {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof StaticMesh<?> staticMesh) {
|
||||
return staticMesh.canStartSoftBodySimulation();
|
||||
} else if (mesh instanceof CompositeMesh compositeMesh) {
|
||||
return compositeMesh.canStartSoftBodySimulation();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClothObject createSimulationData(SoftBodyTranslatable provider, ClothSimulatable simOwner, ClothObjectBuilder simBuilder) {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof StaticMesh<?> staticMesh) {
|
||||
return staticMesh.createSimulationData(provider, simOwner, simBuilder);
|
||||
} else if (mesh instanceof CompositeMesh compositeMesh) {
|
||||
return compositeMesh.createSimulationData(provider, simOwner, simBuilder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
|
||||
softBodyTranslatable.putSoftBodySimulationInfo(sofyBodySimulationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
|
||||
return softBodyTranslatable.getSoftBodySimulationInfo();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.floats.FloatList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import com.tiedup.remake.rig.math.Vec2f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
public class SingleGroupVertexBuilder {
|
||||
private Vec3f position;
|
||||
private Vec3f normal;
|
||||
private Vec2f textureCoordinate;
|
||||
private Vec3f effectiveJointIDs;
|
||||
private Vec3f effectiveJointWeights;
|
||||
private int effectiveJointNumber;
|
||||
|
||||
public SingleGroupVertexBuilder() {
|
||||
this.position = null;
|
||||
this.normal = null;
|
||||
this.textureCoordinate = null;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder(SingleGroupVertexBuilder vertex) {
|
||||
this.position = vertex.position;
|
||||
this.effectiveJointIDs = vertex.effectiveJointIDs;
|
||||
this.effectiveJointWeights = vertex.effectiveJointWeights;
|
||||
this.effectiveJointNumber = vertex.effectiveJointNumber;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setPosition(Vec3f position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setNormal(Vec3f vector) {
|
||||
this.normal = vector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setTextureCoordinate(Vec2f vector) {
|
||||
this.textureCoordinate = vector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointIDs(Vec3f effectiveJointIDs) {
|
||||
this.effectiveJointIDs = effectiveJointIDs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointWeights(Vec3f effectiveJointWeights) {
|
||||
this.effectiveJointWeights = effectiveJointWeights;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointNumber(int count) {
|
||||
this.effectiveJointNumber = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public State compareTextureCoordinateAndNormal(Vec3f normal, Vec2f textureCoord) {
|
||||
if (this.textureCoordinate == null) {
|
||||
return State.EMPTY;
|
||||
} else if (this.textureCoordinate.equals(textureCoord) && this.normal.equals(normal)) {
|
||||
return State.EQUAL;
|
||||
} else {
|
||||
return State.DIFFERENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static SkinnedMesh loadVertexInformation(List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices) {
|
||||
FloatList positions = new FloatArrayList();
|
||||
FloatList normals = new FloatArrayList();
|
||||
FloatList texCoords = new FloatArrayList();
|
||||
IntList animationIndices = new IntArrayList();
|
||||
FloatList jointWeights = new FloatArrayList();
|
||||
IntList affectCountList = new IntArrayList();
|
||||
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
SingleGroupVertexBuilder vertex = vertices.get(i);
|
||||
Vec3f position = vertex.position;
|
||||
Vec3f normal = vertex.normal;
|
||||
Vec2f texCoord = vertex.textureCoordinate;
|
||||
positions.add(position.x);
|
||||
positions.add(position.y);
|
||||
positions.add(position.z);
|
||||
normals.add(normal.x);
|
||||
normals.add(normal.y);
|
||||
normals.add(normal.z);
|
||||
texCoords.add(texCoord.x);
|
||||
texCoords.add(texCoord.y);
|
||||
|
||||
Vec3f effectIDs = vertex.effectiveJointIDs;
|
||||
Vec3f weights = vertex.effectiveJointWeights;
|
||||
int count = Math.min(vertex.effectiveJointNumber, 3);
|
||||
affectCountList.add(count);
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
switch (j) {
|
||||
case 0:
|
||||
animationIndices.add((int) effectIDs.x);
|
||||
jointWeights.add(weights.x);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
case 1:
|
||||
animationIndices.add((int) effectIDs.y);
|
||||
jointWeights.add(weights.y);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
case 2:
|
||||
animationIndices.add((int) effectIDs.z);
|
||||
jointWeights.add(weights.z);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Float[] positionList = positions.toArray(new Float[0]);
|
||||
Float[] normalList = normals.toArray(new Float[0]);
|
||||
Float[] texCoordList = texCoords.toArray(new Float[0]);
|
||||
Integer[] affectingJointIndices = animationIndices.toArray(new Integer[0]);
|
||||
Float[] jointWeightList = jointWeights.toArray(new Float[0]);
|
||||
Integer[] affectJointCounts = affectCountList.toArray(new Integer[0]);
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshDefinitions = Maps.newHashMap();
|
||||
|
||||
arrayMap.put("positions", positionList);
|
||||
arrayMap.put("normals", normalList);
|
||||
arrayMap.put("uvs", texCoordList);
|
||||
arrayMap.put("weights", jointWeightList);
|
||||
arrayMap.put("vcounts", affectJointCounts);
|
||||
arrayMap.put("vindices", affectingJointIndices);
|
||||
|
||||
for (Map.Entry<MeshPartDefinition, IntList> e : indices.entrySet()) {
|
||||
meshDefinitions.put(e.getKey(), VertexBuilder.create(e.getValue().toIntArray()));
|
||||
}
|
||||
|
||||
return new SkinnedMesh(arrayMap, meshDefinitions, null, null);
|
||||
}
|
||||
|
||||
public enum State {
|
||||
EMPTY, EQUAL, DIFFERENT
|
||||
}
|
||||
}
|
||||
397
src/main/java/com/tiedup/remake/rig/mesh/SkinnedMesh.java
Normal file
397
src/main/java/com/tiedup/remake/rig/mesh/SkinnedMesh.java
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh.SkinnedMeshPart;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec4f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
import com.tiedup.remake.rig.render.compute.ComputeShaderSetup;
|
||||
import com.tiedup.remake.rig.render.compute.ComputeShaderProvider;
|
||||
import com.tiedup.remake.rig.TiedUpAnimationConfig;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
public class SkinnedMesh extends StaticMesh<SkinnedMeshPart> {
|
||||
protected final float[] weights;
|
||||
protected final int[] affectingJointCounts;
|
||||
protected final int[][] affectingWeightIndices;
|
||||
protected final int[][] affectingJointIndices;
|
||||
|
||||
private final int maxJointCount;
|
||||
|
||||
@Nullable
|
||||
private ComputeShaderSetup computerShaderSetup;
|
||||
|
||||
public SkinnedMesh(@Nullable Map<String, Number[]> arrayMap, @Nullable Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, @Nullable SkinnedMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, partBuilders, parent, properties);
|
||||
|
||||
this.weights = parent == null ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("weights")) : parent.weights;
|
||||
this.affectingJointCounts = parent == null ? ParseUtil.unwrapIntWrapperArray(arrayMap.get("vcounts")) : parent.affectingJointCounts;
|
||||
|
||||
if (parent != null) {
|
||||
this.affectingJointIndices = parent.affectingJointIndices;
|
||||
this.affectingWeightIndices = parent.affectingWeightIndices;
|
||||
} else {
|
||||
int[] vindices = ParseUtil.unwrapIntWrapperArray(arrayMap.get("vindices"));
|
||||
this.affectingJointIndices = new int[this.affectingJointCounts.length][];
|
||||
this.affectingWeightIndices = new int[this.affectingJointCounts.length][];
|
||||
int idx = 0;
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts.length; i++) {
|
||||
int count = this.affectingJointCounts[i];
|
||||
int[] jointId = new int[count];
|
||||
int[] weights = new int[count];
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
jointId[j] = vindices[idx * 2];
|
||||
weights[j] = vindices[idx * 2 + 1];
|
||||
idx++;
|
||||
}
|
||||
|
||||
this.affectingJointIndices[i] = jointId;
|
||||
this.affectingWeightIndices[i] = weights;
|
||||
}
|
||||
}
|
||||
|
||||
int maxJointId = 0;
|
||||
|
||||
for (int[] i : this.affectingJointIndices) {
|
||||
for (int j : i) {
|
||||
if (maxJointId < j) {
|
||||
maxJointId = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.maxJointCount = maxJointId;
|
||||
|
||||
if (ComputeShaderProvider.supportComputeShader()) {
|
||||
if (RenderSystem.isOnRenderThread()) {
|
||||
this.computerShaderSetup = ComputeShaderProvider.getComputeShaderSetup(this);
|
||||
} else {
|
||||
RenderSystem.recordRenderCall(() -> {
|
||||
this.computerShaderSetup = ComputeShaderProvider.getComputeShaderSetup(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (RenderSystem.isOnRenderThread()) {
|
||||
if (this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.destroyBuffers();
|
||||
}
|
||||
} else {
|
||||
RenderSystem.recordRenderCall(() -> {
|
||||
if (this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.destroyBuffers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, SkinnedMeshPart> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders) {
|
||||
Map<String, SkinnedMeshPart> parts = Maps.newHashMap();
|
||||
|
||||
partBuilders.forEach((partDefinition, vertexBuilder) -> {
|
||||
parts.put(partDefinition.partName(), new SkinnedMeshPart(vertexBuilder, partDefinition.renderProperties(), partDefinition.getModelPartAnimationProvider()));
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SkinnedMeshPart getOrLogException(Map<String, SkinnedMeshPart> parts, String name) {
|
||||
if (!parts.containsKey(name)) {
|
||||
if (TiedUpRigConstants.IS_DEV_ENV) {
|
||||
TiedUpRigConstants.LOGGER.debug("Cannot find the mesh part named " + name + " in " + this.getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts.get(name);
|
||||
}
|
||||
|
||||
private static final Vec4f TRANSFORM = new Vec4f();
|
||||
private static final Vec4f POS = new Vec4f();
|
||||
private static final Vec4f TOTAL_POS = new Vec4f();
|
||||
|
||||
@Override
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
int index = positionIndex * 3;
|
||||
|
||||
POS.set(this.positions[index], this.positions[index + 1], this.positions[index + 2], 1.0F);
|
||||
TOTAL_POS.set(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts[positionIndex]; i++) {
|
||||
int jointIndex = this.affectingJointIndices[positionIndex][i];
|
||||
int weightIndex = this.affectingWeightIndices[positionIndex][i];
|
||||
float weight = this.weights[weightIndex];
|
||||
|
||||
Vec4f.add(OpenMatrix4f.transform(poses[jointIndex], POS, TRANSFORM).scale(weight), TOTAL_POS, TOTAL_POS);
|
||||
}
|
||||
|
||||
dest.set(TOTAL_POS.x, TOTAL_POS.y, TOTAL_POS.z, 1.0F);
|
||||
}
|
||||
|
||||
private static final Vec4f NORM = new Vec4f();
|
||||
private static final Vec4f TOTAL_NORM = new Vec4f();
|
||||
|
||||
@Override
|
||||
public void getVertexNormal(int positionIndex, int normalIndex, Vector3f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
int index = normalIndex * 3;
|
||||
NORM.set(this.normals[index], this.normals[index + 1], this.normals[index + 2], 1.0F);
|
||||
TOTAL_NORM.set(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts[positionIndex]; i++) {
|
||||
int jointIndex = this.affectingJointIndices[positionIndex][i];
|
||||
int weightIndex = this.affectingWeightIndices[positionIndex][i];
|
||||
float weight = this.weights[weightIndex];
|
||||
Vec4f.add(OpenMatrix4f.transform(poses[jointIndex], NORM, TRANSFORM).scale(weight), TOTAL_NORM, TOTAL_NORM);
|
||||
}
|
||||
|
||||
dest.set(TOTAL_NORM.x, TOTAL_NORM.y, TOTAL_NORM.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the model without applying animation
|
||||
*/
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
part.draw(poseStack, bufferbuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final Vector4f POSITION = new Vector4f();
|
||||
protected static final Vector3f NORMAL = new Vector3f();
|
||||
|
||||
/**
|
||||
* Draws the model to vanilla buffer
|
||||
*/
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
Matrix3f normal = poseStack.last().normal();
|
||||
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
if (!part.isHidden()) {
|
||||
OpenMatrix4f transform = part.getVanillaPartTransform();
|
||||
|
||||
for (int i = 0; i < poses.length; i++) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].load(poses[i]);
|
||||
|
||||
if (armature != null) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].mulBack(armature.searchJointById(i).getToOrigin());
|
||||
}
|
||||
|
||||
if (transform != null) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].mulBack(transform);
|
||||
}
|
||||
|
||||
ComputeShaderSetup.TOTAL_NORMALS[i] = ComputeShaderSetup.TOTAL_POSES[i].removeTranslation();
|
||||
}
|
||||
|
||||
for (VertexBuilder vi : part.getVertices()) {
|
||||
this.getVertexPosition(vi.position, POSITION, ComputeShaderSetup.TOTAL_POSES);
|
||||
this.getVertexNormal(vi.position, vi.normal, NORMAL, ComputeShaderSetup.TOTAL_NORMALS);
|
||||
|
||||
POSITION.mul(pose);
|
||||
NORMAL.mul(normal);
|
||||
|
||||
drawingFunction.draw(bufferbuilder, POSITION.x, POSITION.y, POSITION.z, NORMAL.x, NORMAL.y, NORMAL.z, packedLight, r, g, b, a, this.uvs[vi.uv * 2], this.uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the model depending on animation shader option
|
||||
* @param armature give this parameter as null if @param poses already bound origin translation
|
||||
* @param poses
|
||||
*/
|
||||
public void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
this.draw(poseStack, bufferSources, renderType, Mesh.DrawingFunction.NEW_ENTITY, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
if (TiedUpAnimationConfig.activateComputeShader && this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.drawWithShader(this, poseStack, bufferSources, TiedUpRenderTypes.getTriangulated(renderType), packedLight, r, g, b, a, overlay, armature, poses);
|
||||
} else {
|
||||
this.drawPosed(poseStack, bufferSources.getBuffer(TiedUpRenderTypes.getTriangulated(renderType)), drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxJointCount() {
|
||||
return this.maxJointCount;
|
||||
}
|
||||
|
||||
public float[] weights() {
|
||||
return this.weights;
|
||||
}
|
||||
|
||||
public int[] affectingJointCounts() {
|
||||
return this.affectingJointCounts;
|
||||
}
|
||||
|
||||
public int[][] affectingWeightIndices() {
|
||||
return this.affectingWeightIndices;
|
||||
}
|
||||
|
||||
public int[][] affectingJointIndices() {
|
||||
return this.affectingJointIndices;
|
||||
}
|
||||
|
||||
public class SkinnedMeshPart extends MeshPart {
|
||||
private ComputeShaderSetup.MeshPartBuffer partVBO;
|
||||
|
||||
public SkinnedMeshPart(List<VertexBuilder> animatedMeshPartList, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
super(animatedMeshPartList, renderProperties, vanillaPartTracer);
|
||||
}
|
||||
|
||||
public void initVBO(ComputeShaderSetup.MeshPartBuffer partVBO) {
|
||||
this.partVBO = partVBO;
|
||||
}
|
||||
|
||||
public ComputeShaderSetup.MeshPartBuffer getPartVBO() {
|
||||
return this.partVBO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
if (this.isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4f color = this.getColor(r, g, b, a);
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
Matrix3f normal = poseStack.last().normal();
|
||||
|
||||
for (VertexBuilder vi : this.getVertices()) {
|
||||
getVertexPosition(vi.position, POSITION);
|
||||
getVertexNormal(vi.normal, NORMAL);
|
||||
POSITION.mul(pose);
|
||||
NORMAL.mul(normal);
|
||||
drawingFunction.draw(bufferBuilder, POSITION.x(), POSITION.y(), POSITION.z(), NORMAL.x(), NORMAL.y(), NORMAL.z(), packedLight, color.x, color.y, color.z, color.w, uvs[vi.uv * 2], uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this model as Json format
|
||||
*/
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject root = new JsonObject();
|
||||
JsonObject vertices = new JsonObject();
|
||||
float[] positions = this.positions.clone();
|
||||
float[] normals = this.normals.clone();
|
||||
|
||||
for (int i = 0; i < positions.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positions[k], positions[k+1], positions[k+2], 1.0F);
|
||||
posVector.transform(JsonAssetLoader.MINECRAFT_TO_BLENDER_COORD);
|
||||
positions[k] = posVector.x;
|
||||
positions[k+1] = posVector.y;
|
||||
positions[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
for (int i = 0; i < normals.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normals[k], normals[k+1], normals[k+2], 1.0F);
|
||||
normVector.transform(JsonAssetLoader.MINECRAFT_TO_BLENDER_COORD);
|
||||
normals[k] = normVector.x;
|
||||
normals[k+1] = normVector.y;
|
||||
normals[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
IntList affectingJointAndWeightIndices = new IntArrayList();
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts.length; i++) {
|
||||
for (int j = 0; j < this.affectingJointCounts[j]; j++) {
|
||||
affectingJointAndWeightIndices.add(this.affectingJointIndices[i][j]);
|
||||
affectingJointAndWeightIndices.add(this.affectingWeightIndices[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
vertices.add("positions", ParseUtil.farrayToJsonObject(positions, 3));
|
||||
vertices.add("uvs", ParseUtil.farrayToJsonObject(this.uvs, 2));
|
||||
vertices.add("normals", ParseUtil.farrayToJsonObject(normals, 3));
|
||||
vertices.add("vcounts", ParseUtil.iarrayToJsonObject(this.affectingJointCounts, 1));
|
||||
vertices.add("weights", ParseUtil.farrayToJsonObject(this.weights, 1));
|
||||
vertices.add("vindices", ParseUtil.iarrayToJsonObject(affectingJointAndWeightIndices.toIntArray(), 1));
|
||||
|
||||
if (!this.parts.isEmpty()) {
|
||||
JsonObject parts = new JsonObject();
|
||||
|
||||
for (Map.Entry<String, SkinnedMeshPart> partEntry : this.parts.entrySet()) {
|
||||
IntList indicesArray = new IntArrayList();
|
||||
|
||||
for (VertexBuilder vertexIndicator : partEntry.getValue().getVertices()) {
|
||||
indicesArray.add(vertexIndicator.position);
|
||||
indicesArray.add(vertexIndicator.uv);
|
||||
indicesArray.add(vertexIndicator.normal);
|
||||
}
|
||||
|
||||
parts.add(partEntry.getKey(), ParseUtil.iarrayToJsonObject(indicesArray.toIntArray(), 3));
|
||||
}
|
||||
|
||||
vertices.add("parts", parts);
|
||||
} else {
|
||||
int i = 0;
|
||||
int[] indices = new int[this.vertexCount * 3];
|
||||
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
for (VertexBuilder vertexIndicator : part.getVertices()) {
|
||||
indices[i * 3] = vertexIndicator.position;
|
||||
indices[i * 3 + 1] = vertexIndicator.uv;
|
||||
indices[i * 3 + 2] = vertexIndicator.normal;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.add("indices", ParseUtil.iarrayToJsonObject(indices, 3));
|
||||
}
|
||||
|
||||
root.add("vertices", vertices);
|
||||
|
||||
if (this.renderProperties != null) {
|
||||
JsonObject renderProperties = new JsonObject();
|
||||
renderProperties.addProperty("texture_path", this.renderProperties.customTexturePath().toString());
|
||||
renderProperties.addProperty("transparent", this.renderProperties.isTransparent());
|
||||
root.add("render_properties", renderProperties);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.tiedup.remake.rig.mesh.Meshes.MeshAccessor;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject.ClothPart.ConstraintType;
|
||||
import com.tiedup.remake.rig.physics.SimulationProvider;
|
||||
|
||||
public interface SoftBodyTranslatable extends SimulationProvider<ClothSimulatable, ClothSimulator.ClothObject, ClothSimulator.ClothObjectBuilder, SoftBodyTranslatable> {
|
||||
public static final List<ClothSimulatable> TRACKING_SIMULATION_SUBJECTS = Lists.newArrayList();
|
||||
|
||||
default boolean canStartSoftBodySimulation() {
|
||||
return this.getSoftBodySimulationInfo() != null;
|
||||
}
|
||||
|
||||
void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo);
|
||||
|
||||
Map<String, ClothSimulationInfo> getSoftBodySimulationInfo();
|
||||
|
||||
default StaticMesh<?> getOriginalMesh() {
|
||||
if (this instanceof MeshAccessor<?> meshAccessor) {
|
||||
return (StaticMesh<?>)meshAccessor.get();
|
||||
} else {
|
||||
return (StaticMesh<?>)this;
|
||||
}
|
||||
}
|
||||
|
||||
public static record ClothSimulationInfo(float particleMass, float selfCollision, List<int[]> constraints, ConstraintType[] constraintTypes, float[] compliances, int[] particles, float[] weights, float[] rootDistance, int[] normalOffsetMapping) {
|
||||
}
|
||||
}
|
||||
150
src/main/java/com/tiedup/remake/rig/mesh/StaticMesh.java
Normal file
150
src/main/java/com/tiedup/remake/rig/mesh/StaticMesh.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public abstract class StaticMesh<P extends MeshPart> implements Mesh, SoftBodyTranslatable {
|
||||
protected final float[] positions;
|
||||
protected final float[] normals;
|
||||
protected final float[] uvs;
|
||||
|
||||
protected final int vertexCount;
|
||||
protected final Mesh.RenderProperties renderProperties;
|
||||
protected final Map<String, P> parts;
|
||||
protected final List<Vec3> normalList;
|
||||
|
||||
private Map<String, ClothSimulationInfo> softBodySimulationInfo;
|
||||
|
||||
/**
|
||||
* @param arrayMap Null if parent is not null
|
||||
* @param partBuilders Null if parent is not null
|
||||
* @param parent Null if arrayMap and parts are not null
|
||||
* @param renderProperties
|
||||
*/
|
||||
public StaticMesh(@Nullable Map<String, Number[]> arrayMap, @Nullable Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, @Nullable StaticMesh<P> parent, Mesh.RenderProperties renderProperties) {
|
||||
this.positions = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("positions")) : parent.positions;
|
||||
this.normals = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("normals")) : parent.normals;
|
||||
this.uvs = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("uvs")) : parent.uvs;
|
||||
this.parts = (parent == null) ? this.createModelPart(partBuilders) : parent.parts;
|
||||
this.renderProperties = renderProperties;
|
||||
|
||||
int totalV = 0;
|
||||
|
||||
for (MeshPart modelpart : this.parts.values()) {
|
||||
totalV += modelpart.getVertices().size();
|
||||
}
|
||||
|
||||
this.vertexCount = totalV;
|
||||
|
||||
if (this.canStartSoftBodySimulation()) {
|
||||
ImmutableList.Builder<Vec3> normalBuilder = ImmutableList.builder();
|
||||
|
||||
for (int i = 0; i < this.normals.length / 3; i++) {
|
||||
normalBuilder.add(new Vec3(this.normals[i * 3], this.normals[i * 3 + 1], this.normals[i * 3 + 2]));
|
||||
}
|
||||
|
||||
this.normalList = normalBuilder.build();
|
||||
} else {
|
||||
this.normalList = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Map<String, P> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders);
|
||||
protected abstract P getOrLogException(Map<String, P> parts, String name);
|
||||
|
||||
public boolean hasPart(String part) {
|
||||
return this.parts.containsKey(part);
|
||||
}
|
||||
|
||||
public MeshPart getPart(String part) {
|
||||
return this.parts.get(part);
|
||||
}
|
||||
|
||||
public Collection<P> getAllParts() {
|
||||
return this.parts.values();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<String, P>> getPartEntry() {
|
||||
return this.parts.entrySet();
|
||||
}
|
||||
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
this.softBodySimulationInfo = sofyBodySimulationInfo;
|
||||
}
|
||||
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
return this.softBodySimulationInfo;
|
||||
}
|
||||
|
||||
public Mesh.RenderProperties getRenderProperties() {
|
||||
return this.renderProperties;
|
||||
}
|
||||
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest) {
|
||||
int index = positionIndex * 3;
|
||||
dest.set(this.positions[index], this.positions[index + 1], this.positions[index + 2], 1.0F);
|
||||
}
|
||||
|
||||
public void getVertexNormal(int normalIndex, Vector3f dest) {
|
||||
int index = normalIndex * 3;
|
||||
dest.set(this.normals[index], this.normals[index + 1], this.normals[index + 2]);
|
||||
}
|
||||
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
this.getVertexPosition(positionIndex, dest);
|
||||
}
|
||||
|
||||
public void getVertexNormal(int positionIndex, int normalIndex, Vector3f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
this.getVertexNormal(normalIndex, dest);
|
||||
}
|
||||
|
||||
public float[] positions() {
|
||||
return this.positions;
|
||||
}
|
||||
|
||||
public float[] normals() {
|
||||
return this.normals;
|
||||
}
|
||||
|
||||
public float[] uvs() {
|
||||
return this.uvs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Vec3> normalList() {
|
||||
return this.normalList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.parts.values().forEach((part) -> part.setHidden(false));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ClothSimulator.ClothObject createSimulationData(@Nullable SoftBodyTranslatable provider, ClothSimulatable simObject, ClothSimulator.ClothObjectBuilder simBuilder) {
|
||||
return new ClothObject(simBuilder, provider == null ? this : provider, (Map<String, MeshPart>)this.parts, this.positions);
|
||||
}
|
||||
}
|
||||
62
src/main/java/com/tiedup/remake/rig/mesh/VertexBuilder.java
Normal file
62
src/main/java/com/tiedup/remake/rig/mesh/VertexBuilder.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
|
||||
|
||||
// Vertex Indices
|
||||
public class VertexBuilder {
|
||||
public static List<VertexBuilder> create(int[] drawingIndices) {
|
||||
List<VertexBuilder> vertexIndicators = Lists.newArrayList();
|
||||
|
||||
for (int i = 0; i < drawingIndices.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
int position = drawingIndices[k];
|
||||
int uv = drawingIndices[k + 1];
|
||||
int normal = drawingIndices[k + 2];
|
||||
VertexBuilder vi = new VertexBuilder(position, uv, normal);
|
||||
vertexIndicators.add(vi);
|
||||
}
|
||||
|
||||
return vertexIndicators;
|
||||
}
|
||||
|
||||
public final int position;
|
||||
public final int uv;
|
||||
public final int normal;
|
||||
|
||||
public VertexBuilder(int position, int uv, int normal) {
|
||||
this.position = position;
|
||||
this.uv = uv;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof VertexBuilder vb) {
|
||||
return this.position == vb.position && this.uv == vb.uv && this.normal == vb.normal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
|
||||
result = prime * result + this.position;
|
||||
result = prime * result + this.uv;
|
||||
result = prime * result + this.normal;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh.transformer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.Model;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ArmorItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.HumanoidMesh;
|
||||
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class HumanoidModelBaker {
|
||||
static final Map<ResourceLocation, SkinnedMesh> BAKED_MODELS = Maps.newHashMap();
|
||||
static final List<HumanoidModelTransformer> MODEL_TRANSFORMERS = Lists.newArrayList();
|
||||
|
||||
static final Set<ArmorItem> EXCEPTIONAL_MODELS = Sets.newHashSet();
|
||||
static final Set<ModelPart> MODEL_PARTS = Sets.newHashSet();
|
||||
|
||||
public static final HumanoidModelTransformer VANILLA_TRANSFORMER = new VanillaModelTransformer();
|
||||
|
||||
public interface ModelProvider {
|
||||
public Model get(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot slot, HumanoidModel<?> _default);
|
||||
}
|
||||
|
||||
public static void registerNewTransformer(HumanoidModelTransformer transformer) {
|
||||
MODEL_TRANSFORMERS.add(transformer);
|
||||
}
|
||||
|
||||
public static void exportModels(File resourcePackDirectory) throws IOException {
|
||||
File zipFile = new File(resourcePackDirectory, "epicfight_custom_armors.zip");
|
||||
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
|
||||
|
||||
for (Map.Entry<ResourceLocation, SkinnedMesh> entry : BAKED_MODELS.entrySet()) {
|
||||
ZipEntry zipEntry = new ZipEntry(String.format("assets/%s/animmodels/armor/%s.json", entry.getKey().getNamespace(), entry.getKey().getPath()));
|
||||
Gson gson = new GsonBuilder().create();
|
||||
out.putNextEntry(zipEntry);
|
||||
out.write(gson.toJson(entry.getValue().toJsonObject()).getBytes());
|
||||
out.closeEntry();
|
||||
TiedUpRigConstants.LOGGER.info("Exported custom armor model : " + entry.getKey());
|
||||
}
|
||||
|
||||
ZipEntry zipEntry = new ZipEntry("pack.mcmeta");
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
JsonObject root = new JsonObject();
|
||||
JsonObject pack = new JsonObject();
|
||||
pack.addProperty("description", "epicfight_custom_armor_models");
|
||||
pack.addProperty("pack_format", SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES));
|
||||
root.add("pack", pack);
|
||||
out.putNextEntry(zipEntry);
|
||||
out.write(gson.toJson(root).getBytes());
|
||||
out.closeEntry();
|
||||
out.close();
|
||||
}
|
||||
|
||||
public static SkinnedMesh bakeArmor(LivingEntity entityLiving, ItemStack itemstack, ArmorItem armorItem, EquipmentSlot slot, HumanoidModel<?> originalModel, Model forgeModel, HumanoidModel<?> entityModel, HumanoidMesh entityMesh) {
|
||||
SkinnedMesh skinnedArmorModel = null;
|
||||
|
||||
if (!EXCEPTIONAL_MODELS.contains(armorItem)) {
|
||||
if (forgeModel == originalModel || !(forgeModel instanceof HumanoidModel humanoidModel)) {
|
||||
// RIG Phase 0 : Meshes.HELMET/CHESTPLATE/LEGGINS/BOOTS strippés →
|
||||
// HumanoidMesh.getHumanoidArmorModel(slot) retourne null.
|
||||
// Safe-guard ici pour éviter NPE si bakeArmor est appelé avant que
|
||||
// V3-REW-04 soit implémenté (armor rendering rework).
|
||||
var armorAccessor = entityMesh.getHumanoidArmorModel(slot);
|
||||
return armorAccessor != null ? armorAccessor.get() : null;
|
||||
}
|
||||
|
||||
for (HumanoidModelTransformer modelTransformer : MODEL_TRANSFORMERS) {
|
||||
try {
|
||||
skinnedArmorModel = modelTransformer.transformArmorModel(humanoidModel);
|
||||
} catch (Exception e) {
|
||||
TiedUpRigConstants.LOGGER.warn("Can't transform the model of " + ForgeRegistries.ITEMS.getKey(armorItem) + " because of :");
|
||||
e.printStackTrace();
|
||||
EXCEPTIONAL_MODELS.add(armorItem);
|
||||
}
|
||||
|
||||
if (skinnedArmorModel != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skinnedArmorModel == null) {
|
||||
skinnedArmorModel = VANILLA_TRANSFORMER.transformArmorModel(humanoidModel);
|
||||
}
|
||||
}
|
||||
|
||||
BAKED_MODELS.put(ForgeRegistries.ITEMS.getKey(armorItem), skinnedArmorModel);
|
||||
|
||||
return skinnedArmorModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh.transformer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public abstract class HumanoidModelTransformer {
|
||||
public abstract SkinnedMesh transformArmorModel(HumanoidModel<?> humanoidModel);
|
||||
|
||||
public static abstract class PartTransformer<T> {
|
||||
public abstract void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, T cube, List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices, IndexCounter indexCounter);
|
||||
|
||||
static void triangluatePolygon(Map<MeshPartDefinition, IntList> indices, MeshPartDefinition partDefinition, IndexCounter indexCounter) {
|
||||
IntList list = indices.computeIfAbsent(partDefinition, (key) -> new IntArrayList());
|
||||
|
||||
//Optimization: do not split vertices in a cube.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.first());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.second());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.fourth());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.fourth());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.second());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
list.add(indexCounter.third());
|
||||
}
|
||||
|
||||
indexCounter.count();
|
||||
}
|
||||
|
||||
public static class IndexCounter {
|
||||
private int indexCounter = 0;
|
||||
|
||||
private int first() {
|
||||
return this.indexCounter;
|
||||
}
|
||||
|
||||
private int second() {
|
||||
return this.indexCounter + 1;
|
||||
}
|
||||
|
||||
private int third() {
|
||||
return this.indexCounter + 2;
|
||||
}
|
||||
|
||||
private int fourth() {
|
||||
return this.indexCounter + 3;
|
||||
}
|
||||
|
||||
private void count() {
|
||||
this.indexCounter += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh.transformer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.mesh.Mesh.RenderProperties;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.QuaternionUtils;
|
||||
import com.tiedup.remake.rig.math.Vec2f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.mixin.client.MixinAgeableListModel;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class VanillaModelTransformer extends HumanoidModelTransformer {
|
||||
public static final SimpleTransformer HEAD = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 9);
|
||||
public static final SimpleTransformer LEFT_FEET = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 5);
|
||||
public static final SimpleTransformer RIGHT_FEET = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 2);
|
||||
public static final LimbPartTransformer LEFT_ARM = new LimbPartTransformer(AABB.ofSize(new Vec3(1.0D, 6.0D, 0.0D), 4.0D, 12.0D, 4.0D), 16, 17, 19, 19.0F, false, AABB.ofSize(new Vec3(-6.0D, 18.0D, 0), 8.0D, 14.0D, 8.0D));
|
||||
public static final LimbPartTransformer RIGHT_ARM = new LimbPartTransformer(AABB.ofSize(new Vec3(-1.0D, 6.0D, 0.0D), 4.0D, 12.0D, 4.0D), 11, 12, 14, 19.0F, false, AABB.ofSize(new Vec3(6.0D, 18.0D, 0), 8.0D, 14.0D, 8.0D));
|
||||
public static final LimbPartTransformer LEFT_LEG = new LimbPartTransformer(AABB.ofSize(new Vec3(1.9D, 18.0D, 0.0D), 4.0D, 12.0D, 4.0D), 4, 5, 6, 6.0F, true, AABB.ofSize(new Vec3(-2.0D, 6.0D, 0), 8.0D, 14.0D, 8.0D));
|
||||
public static final LimbPartTransformer RIGHT_LEG = new LimbPartTransformer(AABB.ofSize(new Vec3(-1.9D, 18.0D, 0.0D), 4.0D, 12.0D, 4.0D), 1, 2, 3, 6.0F, true, AABB.ofSize(new Vec3(2.0D, 6.0D, 0), 8.0D, 14.0D, 8.0D));
|
||||
public static final ChestPartTransformer CHEST = new ChestPartTransformer(AABB.ofSize(new Vec3(0.0D, 6.0D, 0.0D), 8.0D, 12.0D, 4.0D), 8, 7, 18.0F, AABB.ofSize(new Vec3(0, 18.0D, 0), 12.0D, 14.0D, 6.0D));
|
||||
|
||||
private static PartTransformer<ModelPart.Cube> getModelPartTransformer(ModelPart modelPart) {
|
||||
if (HEAD.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return HEAD;
|
||||
} else if (LEFT_FEET.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return LEFT_FEET;
|
||||
} else if (RIGHT_FEET.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return RIGHT_FEET;
|
||||
} else if (LEFT_ARM.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return LEFT_ARM;
|
||||
} else if (RIGHT_ARM.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return RIGHT_ARM;
|
||||
} else if (LEFT_LEG.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return LEFT_LEG;
|
||||
} else if (RIGHT_LEG.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return RIGHT_LEG;
|
||||
} else if (CHEST.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) {
|
||||
return CHEST;
|
||||
}
|
||||
|
||||
return CHEST;
|
||||
}
|
||||
|
||||
static record VanillaModelPartition(PartTransformer<ModelPart.Cube> partTransformer, ModelPart modelPart, String partName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinnedMesh transformArmorModel(HumanoidModel<?> humanoidModel) {
|
||||
List<VanillaModelPartition> partitions = Lists.newArrayList();
|
||||
|
||||
//Remove entity animation
|
||||
humanoidModel.head.loadPose(humanoidModel.head.getInitialPose());
|
||||
humanoidModel.hat.loadPose(humanoidModel.hat.getInitialPose());
|
||||
humanoidModel.body.loadPose(humanoidModel.body.getInitialPose());
|
||||
humanoidModel.leftArm.loadPose(humanoidModel.leftArm.getInitialPose());
|
||||
humanoidModel.rightArm.loadPose(humanoidModel.rightArm.getInitialPose());
|
||||
humanoidModel.leftLeg.loadPose(humanoidModel.leftLeg.getInitialPose());
|
||||
humanoidModel.rightLeg.loadPose(humanoidModel.rightLeg.getInitialPose());
|
||||
|
||||
List<ModelPart> modelParts = Lists.newArrayList();
|
||||
MixinAgeableListModel accessorAgeableListModel = ((MixinAgeableListModel)humanoidModel);
|
||||
|
||||
Iterable<ModelPart> headParts = accessorAgeableListModel.invoke_headParts();
|
||||
Iterable<ModelPart> bodyParts = accessorAgeableListModel.invoke_bodyParts();
|
||||
|
||||
if (headParts != null) {
|
||||
headParts.forEach(modelParts::add);
|
||||
}
|
||||
|
||||
if (bodyParts != null) {
|
||||
bodyParts.forEach(modelParts::add);
|
||||
}
|
||||
|
||||
modelParts.forEach((modelPart) -> modelPart.loadPose(modelPart.getInitialPose()));
|
||||
|
||||
if (humanoidModel.head.skipDraw || humanoidModel.head.visible) {
|
||||
partitions.add(new VanillaModelPartition(HEAD, humanoidModel.head, "head"));
|
||||
}
|
||||
|
||||
if (humanoidModel.hat.skipDraw || humanoidModel.hat.visible) {
|
||||
partitions.add(new VanillaModelPartition(HEAD, humanoidModel.hat, "hat"));
|
||||
}
|
||||
|
||||
if (humanoidModel.body.skipDraw || humanoidModel.body.visible) {
|
||||
partitions.add(new VanillaModelPartition(CHEST, humanoidModel.body, "body"));
|
||||
}
|
||||
|
||||
if (humanoidModel.rightArm.skipDraw || humanoidModel.rightArm.visible) {
|
||||
partitions.add(new VanillaModelPartition(RIGHT_ARM, humanoidModel.rightArm, "rightArm"));
|
||||
}
|
||||
|
||||
if (humanoidModel.leftArm.skipDraw || humanoidModel.leftArm.visible) {
|
||||
partitions.add(new VanillaModelPartition(LEFT_ARM, humanoidModel.leftArm, "leftArm"));
|
||||
}
|
||||
|
||||
if (humanoidModel.leftLeg.skipDraw || humanoidModel.leftLeg.visible) {
|
||||
partitions.add(new VanillaModelPartition(LEFT_LEG, humanoidModel.leftLeg, "leftLeg"));
|
||||
}
|
||||
|
||||
if (humanoidModel.rightLeg.skipDraw || humanoidModel.rightLeg.visible) {
|
||||
partitions.add(new VanillaModelPartition(RIGHT_LEG, humanoidModel.rightLeg, "rightLeg"));
|
||||
}
|
||||
|
||||
modelParts.remove(humanoidModel.head);
|
||||
modelParts.remove(humanoidModel.hat);
|
||||
modelParts.remove(humanoidModel.body);
|
||||
modelParts.remove(humanoidModel.rightArm);
|
||||
modelParts.remove(humanoidModel.leftArm);
|
||||
modelParts.remove(humanoidModel.rightLeg);
|
||||
modelParts.remove(humanoidModel.leftLeg);
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (ModelPart modelpart : modelParts) {
|
||||
if (modelpart.skipDraw || modelpart.visible) {
|
||||
partitions.add(new VanillaModelPartition(getModelPartTransformer(modelpart), modelpart, "part" + (i++)));
|
||||
}
|
||||
}
|
||||
|
||||
return bakeMeshFromCubes(partitions);
|
||||
}
|
||||
|
||||
private static SkinnedMesh bakeMeshFromCubes(List<VanillaModelPartition> partitions) {
|
||||
List<SingleGroupVertexBuilder> vertices = Lists.newArrayList();
|
||||
Map<MeshPartDefinition, IntList> indices = Maps.newHashMap();
|
||||
PoseStack poseStack = new PoseStack();
|
||||
PartTransformer.IndexCounter indexCounter = new PartTransformer.IndexCounter();
|
||||
|
||||
poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F));
|
||||
poseStack.mulPose(QuaternionUtils.XP.rotationDegrees(180.0F));
|
||||
poseStack.translate(0.0F, -24.0F, 0.0F);
|
||||
|
||||
for (VanillaModelPartition modelpartition : partitions) {
|
||||
bake(poseStack, modelpartition.partName, modelpartition, modelpartition.modelPart, vertices, indices, Lists.newArrayList(), indexCounter, false);
|
||||
}
|
||||
|
||||
return SingleGroupVertexBuilder.loadVertexInformation(vertices, indices);
|
||||
}
|
||||
|
||||
private static void bake(PoseStack poseStack, String partName, VanillaModelPartition modelpartition, ModelPart part, List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices, List<String> path, PartTransformer.IndexCounter indexCounter, boolean bindPart) {
|
||||
PartPose initialPose = part.getInitialPose();
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(initialPose.x, initialPose.y, initialPose.z);
|
||||
poseStack.mulPose(new Quaternionf().rotationZYX(initialPose.zRot, initialPose.yRot, initialPose.xRot));
|
||||
|
||||
if (!bindPart) {
|
||||
poseStack.scale(part.xScale, part.yScale, part.zScale);
|
||||
}
|
||||
|
||||
List<String> newList = new ArrayList<>(path);
|
||||
|
||||
if (bindPart) {
|
||||
newList.add(partName);
|
||||
}
|
||||
|
||||
if (part.visible && !part.skipDraw) {
|
||||
MeshPartDefinition partDefinition = VanillaMeshPartDefinition.of(partName);
|
||||
|
||||
if (bindPart) {
|
||||
OpenMatrix4f invertedParentTransform = OpenMatrix4f.importFromMojangMatrix(poseStack.last().pose());
|
||||
invertedParentTransform.m30 *= 0.0625F;
|
||||
invertedParentTransform.m31 *= 0.0625F;
|
||||
invertedParentTransform.m32 *= 0.0625F;
|
||||
invertedParentTransform.invert();
|
||||
partDefinition = VanillaMeshPartDefinition.of(partName, newList, invertedParentTransform, modelpartition.modelPart);
|
||||
}
|
||||
|
||||
for (ModelPart.Cube cube : part.cubes) {
|
||||
modelpartition.partTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, ModelPart> child : part.children.entrySet()) {
|
||||
bake(poseStack, child.getKey(), modelpartition, child.getValue(), vertices, indices, newList, indexCounter, true);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
static class SimpleTransformer extends PartTransformer<ModelPart.Cube> {
|
||||
final int jointId;
|
||||
final AABB coverArea;
|
||||
|
||||
public SimpleTransformer(AABB coverArea, int jointId) {
|
||||
this.coverArea = coverArea;
|
||||
this.jointId = jointId;
|
||||
}
|
||||
|
||||
public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices, PartTransformer.IndexCounter indexCounter) {
|
||||
for (ModelPart.Polygon quad : cube.polygons) {
|
||||
Vector3f norm = new Vector3f(quad.normal);
|
||||
norm.mul(poseStack.last().normal());
|
||||
|
||||
for (ModelPart.Vertex vertex : quad.vertices) {
|
||||
Vector4f pos = new Vector4f(vertex.pos, 1.0F);
|
||||
pos.mul(poseStack.last().pose());
|
||||
vertices.add(new SingleGroupVertexBuilder()
|
||||
.setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F))
|
||||
.setNormal(new Vec3f(norm.x(), norm.y(), norm.z()))
|
||||
.setTextureCoordinate(new Vec2f(vertex.u, vertex.v))
|
||||
.setEffectiveJointIDs(new Vec3f(this.jointId, 0, 0))
|
||||
.setEffectiveJointWeights(new Vec3f(1.0F, 0.0F, 0.0F))
|
||||
.setEffectiveJointNumber(1)
|
||||
);
|
||||
}
|
||||
|
||||
triangluatePolygon(indices, partDefinition, indexCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ChestPartTransformer extends PartTransformer<ModelPart.Cube> {
|
||||
static final float X_PLANE = 0.0F;
|
||||
static final VertexWeight[] WEIGHT_ALONG_Y = { new VertexWeight(13.6666F, 0.230F, 0.770F), new VertexWeight(15.8333F, 0.254F, 0.746F), new VertexWeight(18.0F, 0.5F, 0.5F), new VertexWeight(20.1666F, 0.744F, 0.256F), new VertexWeight(22.3333F, 0.770F, 0.230F)};
|
||||
final SimpleTransformer upperAttachmentTransformer;
|
||||
final SimpleTransformer lowerAttachmentTransformer;
|
||||
final AABB noneAttachmentArea;
|
||||
final AABB coverArea;
|
||||
final float yClipCoord;
|
||||
|
||||
public ChestPartTransformer(AABB coverArea, int upperJoint, int lowerJoint, float yBasis, AABB noneAttachmentArea) {
|
||||
this.coverArea = coverArea;
|
||||
this.noneAttachmentArea = noneAttachmentArea;
|
||||
this.upperAttachmentTransformer = new SimpleTransformer(null, upperJoint);
|
||||
this.lowerAttachmentTransformer = new SimpleTransformer(null, lowerJoint);
|
||||
this.yClipCoord = yBasis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices, PartTransformer.IndexCounter indexCounter) {
|
||||
Vec3 centerOfCube = getCenterOfCube(poseStack, cube);
|
||||
|
||||
if (!this.noneAttachmentArea.contains(centerOfCube)) {
|
||||
if (centerOfCube.y < this.yClipCoord) {
|
||||
this.lowerAttachmentTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter);
|
||||
} else {
|
||||
this.upperAttachmentTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<AnimatedPolygon> xClipPolygons = Lists.<AnimatedPolygon>newArrayList();
|
||||
List<AnimatedPolygon> xyClipPolygons = Lists.<AnimatedPolygon>newArrayList();
|
||||
|
||||
for (ModelPart.Polygon polygon : cube.polygons) {
|
||||
Matrix4f matrix = poseStack.last().pose();
|
||||
|
||||
ModelPart.Vertex pos0 = getTranslatedVertex(polygon.vertices[0], matrix);
|
||||
ModelPart.Vertex pos1 = getTranslatedVertex(polygon.vertices[1], matrix);
|
||||
ModelPart.Vertex pos2 = getTranslatedVertex(polygon.vertices[2], matrix);
|
||||
ModelPart.Vertex pos3 = getTranslatedVertex(polygon.vertices[3], matrix);
|
||||
Direction direction = getDirectionFromVector(polygon.normal);
|
||||
|
||||
VertexWeight pos0Weight = getYClipWeight(pos0.pos.y());
|
||||
VertexWeight pos1Weight = getYClipWeight(pos1.pos.y());
|
||||
VertexWeight pos2Weight = getYClipWeight(pos2.pos.y());
|
||||
VertexWeight pos3Weight = getYClipWeight(pos3.pos.y());
|
||||
|
||||
if (pos1.pos.x() > X_PLANE != pos2.pos.x() > X_PLANE) {
|
||||
float distance = pos2.pos.x() - pos1.pos.x();
|
||||
float textureU = pos1.u + (pos2.u - pos1.u) * ((X_PLANE - pos1.pos.x()) / distance);
|
||||
ModelPart.Vertex pos4 = new ModelPart.Vertex(X_PLANE, pos0.pos.y(), pos0.pos.z(), textureU, pos0.v);
|
||||
ModelPart.Vertex pos5 = new ModelPart.Vertex(X_PLANE, pos1.pos.y(), pos1.pos.z(), textureU, pos1.v);
|
||||
|
||||
xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos0, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos4, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos5, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos3, 8, 7, 0, pos3Weight.chestWeight, pos3Weight.torsoWeight, 0)
|
||||
}, direction));
|
||||
xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos1, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos2, 8, 7, 0, pos2Weight.chestWeight, pos2Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos5, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0)
|
||||
}, direction));
|
||||
} else {
|
||||
xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos0, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos1, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos2, 8, 7, 0, pos2Weight.chestWeight, pos2Weight.torsoWeight, 0),
|
||||
new AnimatedVertex(pos3, 8, 7, 0, pos3Weight.chestWeight, pos3Weight.torsoWeight, 0)
|
||||
}, direction));
|
||||
}
|
||||
}
|
||||
|
||||
for (AnimatedPolygon polygon : xClipPolygons) {
|
||||
boolean upsideDown = polygon.animatedVertexPositions[1].pos.y() > polygon.animatedVertexPositions[2].pos.y();
|
||||
AnimatedVertex pos0 = upsideDown ? polygon.animatedVertexPositions[2] : polygon.animatedVertexPositions[0];
|
||||
AnimatedVertex pos1 = upsideDown ? polygon.animatedVertexPositions[3] : polygon.animatedVertexPositions[1];
|
||||
AnimatedVertex pos2 = upsideDown ? polygon.animatedVertexPositions[0] : polygon.animatedVertexPositions[2];
|
||||
AnimatedVertex pos3 = upsideDown ? polygon.animatedVertexPositions[1] : polygon.animatedVertexPositions[3];
|
||||
Direction direction = getDirectionFromVector(polygon.normal);
|
||||
List<VertexWeight> vertexWeights = getMiddleYClipWeights(pos1.pos.y(), pos2.pos.y());
|
||||
List<AnimatedVertex> animatedVertices = Lists.<AnimatedVertex>newArrayList();
|
||||
animatedVertices.add(pos0);
|
||||
animatedVertices.add(pos1);
|
||||
|
||||
if (vertexWeights.size() > 0) {
|
||||
for (VertexWeight vertexWeight : vertexWeights) {
|
||||
float distance = pos2.pos.y() - pos1.pos.y();
|
||||
float textureV = pos1.v + (pos2.v - pos1.v) * ((vertexWeight.yClipCoord - pos1.pos.y()) / distance);
|
||||
Vector3f clipPos1 = getClipPoint(pos1.pos, pos2.pos, vertexWeight.yClipCoord);
|
||||
Vector3f clipPos2 = getClipPoint(pos0.pos, pos3.pos, vertexWeight.yClipCoord);
|
||||
ModelPart.Vertex pos4 = new ModelPart.Vertex(clipPos2, pos0.u, textureV);
|
||||
ModelPart.Vertex pos5 = new ModelPart.Vertex(clipPos1, pos1.u, textureV);
|
||||
animatedVertices.add(new AnimatedVertex(pos4, 8, 7, 0, vertexWeight.chestWeight, vertexWeight.torsoWeight, 0));
|
||||
animatedVertices.add(new AnimatedVertex(pos5, 8, 7, 0, vertexWeight.chestWeight, vertexWeight.torsoWeight, 0));
|
||||
}
|
||||
}
|
||||
|
||||
animatedVertices.add(pos3);
|
||||
animatedVertices.add(pos2);
|
||||
|
||||
for (int i = 0; i < (animatedVertices.size() - 2) / 2; i++) {
|
||||
int start = i*2;
|
||||
AnimatedVertex p0 = animatedVertices.get(start);
|
||||
AnimatedVertex p1 = animatedVertices.get(start + 1);
|
||||
AnimatedVertex p2 = animatedVertices.get(start + 3);
|
||||
AnimatedVertex p3 = animatedVertices.get(start + 2);
|
||||
xyClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(p0, 8, 7, 0, p0.weight.x, p0.weight.y, 0),
|
||||
new AnimatedVertex(p1, 8, 7, 0, p1.weight.x, p1.weight.y, 0),
|
||||
new AnimatedVertex(p2, 8, 7, 0, p2.weight.x, p2.weight.y, 0),
|
||||
new AnimatedVertex(p3, 8, 7, 0, p3.weight.x, p3.weight.y, 0)
|
||||
}, direction));
|
||||
}
|
||||
}
|
||||
|
||||
for (AnimatedPolygon polygon : xyClipPolygons) {
|
||||
Vector3f norm = new Vector3f(polygon.normal);
|
||||
norm.mul(poseStack.last().normal());
|
||||
|
||||
for (AnimatedVertex vertex : polygon.animatedVertexPositions) {
|
||||
Vector4f pos = new Vector4f(vertex.pos, 1.0F);
|
||||
float weight1 = vertex.weight.x;
|
||||
float weight2 = vertex.weight.y;
|
||||
int joint1 = vertex.jointId.getX();
|
||||
int joint2 = vertex.jointId.getY();
|
||||
int count = weight1 > 0.0F && weight2 > 0.0F ? 2 : 1;
|
||||
|
||||
if (weight1 <= 0.0F) {
|
||||
joint1 = joint2;
|
||||
weight1 = weight2;
|
||||
}
|
||||
|
||||
vertices.add(new SingleGroupVertexBuilder()
|
||||
.setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F))
|
||||
.setNormal(new Vec3f(norm.x(), norm.y(), norm.z()))
|
||||
.setTextureCoordinate(new Vec2f(vertex.u, vertex.v))
|
||||
.setEffectiveJointIDs(new Vec3f(joint1, joint2, 0))
|
||||
.setEffectiveJointWeights(new Vec3f(weight1, weight2, 0.0F))
|
||||
.setEffectiveJointNumber(count)
|
||||
);
|
||||
}
|
||||
|
||||
triangluatePolygon(indices, partDefinition, indexCounter);
|
||||
}
|
||||
}
|
||||
|
||||
static VertexWeight getYClipWeight(float y) {
|
||||
if (y < WEIGHT_ALONG_Y[0].yClipCoord) {
|
||||
return new VertexWeight(y, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
for (int i = 0; i < WEIGHT_ALONG_Y.length; i++) {
|
||||
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
VertexWeight pair = WEIGHT_ALONG_Y[index];
|
||||
return new VertexWeight(y, pair.chestWeight, pair.torsoWeight);
|
||||
}
|
||||
|
||||
return new VertexWeight(y, 1.0F, 0.0F);
|
||||
}
|
||||
|
||||
static class VertexWeight {
|
||||
final float yClipCoord;
|
||||
final float chestWeight;
|
||||
final float torsoWeight;
|
||||
|
||||
public VertexWeight(float yClipCoord, float chestWeight, float torsoWeight) {
|
||||
this.yClipCoord = yClipCoord;
|
||||
this.chestWeight = chestWeight;
|
||||
this.torsoWeight = torsoWeight;
|
||||
}
|
||||
}
|
||||
|
||||
static List<VertexWeight> getMiddleYClipWeights(float minY, float maxY) {
|
||||
List<VertexWeight> cutYs = Lists.<VertexWeight>newArrayList();
|
||||
for (VertexWeight vertexWeight : WEIGHT_ALONG_Y) {
|
||||
if (vertexWeight.yClipCoord > minY && maxY >= vertexWeight.yClipCoord) {
|
||||
cutYs.add(vertexWeight);
|
||||
}
|
||||
}
|
||||
return cutYs;
|
||||
}
|
||||
}
|
||||
|
||||
static class LimbPartTransformer extends PartTransformer<ModelPart.Cube> {
|
||||
final int upperJoint;
|
||||
final int lowerJoint;
|
||||
final int middleJoint;
|
||||
final boolean bendInFront;
|
||||
final SimpleTransformer upperAttachmentTransformer;
|
||||
final SimpleTransformer lowerAttachmentTransformer;
|
||||
final AABB noneAttachmentArea;
|
||||
final AABB coverArea;
|
||||
final float yClipCoord;
|
||||
|
||||
public LimbPartTransformer(AABB coverArea, int upperJoint, int lowerJoint, int middleJoint, float yClipCoord, boolean bendInFront, AABB noneAttachmentArea) {
|
||||
this.upperJoint = upperJoint;
|
||||
this.lowerJoint = lowerJoint;
|
||||
this.middleJoint = middleJoint;
|
||||
this.bendInFront = bendInFront;
|
||||
this.upperAttachmentTransformer = new SimpleTransformer(null, upperJoint);
|
||||
this.lowerAttachmentTransformer = new SimpleTransformer(null, lowerJoint);
|
||||
this.noneAttachmentArea = noneAttachmentArea;
|
||||
this.coverArea = coverArea;
|
||||
this.yClipCoord = yClipCoord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices, PartTransformer.IndexCounter indexCounter) {
|
||||
List<AnimatedPolygon> polygons = Lists.<AnimatedPolygon>newArrayList();
|
||||
|
||||
for (ModelPart.Polygon quad : cube.polygons) {
|
||||
Matrix4f matrix = poseStack.last().pose();
|
||||
ModelPart.Vertex pos0 = getTranslatedVertex(quad.vertices[0], matrix);
|
||||
ModelPart.Vertex pos1 = getTranslatedVertex(quad.vertices[1], matrix);
|
||||
ModelPart.Vertex pos2 = getTranslatedVertex(quad.vertices[2], matrix);
|
||||
ModelPart.Vertex pos3 = getTranslatedVertex(quad.vertices[3], matrix);
|
||||
Direction direction = getDirectionFromVector(quad.normal);
|
||||
|
||||
if (pos1.pos.y() > this.yClipCoord != pos2.pos.y() > this.yClipCoord) {
|
||||
float distance = pos2.pos.y() - pos1.pos.y();
|
||||
float textureV = pos1.v + (pos2.v - pos1.v) * ((this.yClipCoord - pos1.pos.y()) / distance);
|
||||
Vector3f clipPos1 = getClipPoint(pos1.pos, pos2.pos, this.yClipCoord);
|
||||
Vector3f clipPos2 = getClipPoint(pos0.pos, pos3.pos, this.yClipCoord);
|
||||
ModelPart.Vertex pos4 = new ModelPart.Vertex(clipPos2, pos0.u, textureV);
|
||||
ModelPart.Vertex pos5 = new ModelPart.Vertex(clipPos1, pos1.u, textureV);
|
||||
|
||||
int upperId, lowerId;
|
||||
if (distance > 0) {
|
||||
upperId = this.lowerJoint;
|
||||
lowerId = this.upperJoint;
|
||||
} else {
|
||||
upperId = this.upperJoint;
|
||||
lowerId = this.lowerJoint;
|
||||
}
|
||||
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos0, upperId), new AnimatedVertex(pos1, upperId),
|
||||
new AnimatedVertex(pos5, upperId), new AnimatedVertex(pos4, upperId)
|
||||
}, direction));
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, lowerId), new AnimatedVertex(pos5, lowerId),
|
||||
new AnimatedVertex(pos2, lowerId), new AnimatedVertex(pos3, lowerId)
|
||||
}, direction));
|
||||
|
||||
boolean hasSameZ = pos4.pos.z() < 0.0F == pos5.pos.z() < 0.0F;
|
||||
boolean isFront = hasSameZ && (pos4.pos.z() < 0.0F == this.bendInFront);
|
||||
|
||||
if (isFront) {
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, this.middleJoint), new AnimatedVertex(pos5, this.middleJoint),
|
||||
new AnimatedVertex(pos5, this.upperJoint), new AnimatedVertex(pos4, this.upperJoint)
|
||||
}, 0.001F, direction));
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, this.lowerJoint), new AnimatedVertex(pos5, this.lowerJoint),
|
||||
new AnimatedVertex(pos5, this.middleJoint), new AnimatedVertex(pos4, this.middleJoint)
|
||||
}, 0.001F, direction));
|
||||
} else if (!hasSameZ) {
|
||||
boolean startFront = pos4.pos.z() > 0;
|
||||
int firstJoint = this.lowerJoint;
|
||||
int secondJoint = this.lowerJoint;
|
||||
int thirdJoint = startFront ? this.upperJoint : this.middleJoint;
|
||||
int fourthJoint = startFront ? this.middleJoint : this.upperJoint;
|
||||
int fifthJoint = this.upperJoint;
|
||||
int sixthJoint = this.upperJoint;
|
||||
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, firstJoint), new AnimatedVertex(pos5, secondJoint),
|
||||
new AnimatedVertex(pos5, thirdJoint), new AnimatedVertex(pos4, fourthJoint)
|
||||
}, 0.001F, direction));
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos4, fourthJoint), new AnimatedVertex(pos5, thirdJoint),
|
||||
new AnimatedVertex(pos5, fifthJoint), new AnimatedVertex(pos4, sixthJoint)
|
||||
}, 0.001F, direction));
|
||||
}
|
||||
} else {
|
||||
int jointId = pos0.pos.y() > this.yClipCoord ? this.upperJoint : this.lowerJoint;
|
||||
polygons.add(new AnimatedPolygon(new AnimatedVertex[] {
|
||||
new AnimatedVertex(pos0, jointId), new AnimatedVertex(pos1, jointId),
|
||||
new AnimatedVertex(pos2, jointId), new AnimatedVertex(pos3, jointId)
|
||||
}, direction));
|
||||
}
|
||||
}
|
||||
|
||||
for (AnimatedPolygon quad : polygons) {
|
||||
Vector3f norm = new Vector3f(quad.normal);
|
||||
norm.mul(poseStack.last().normal());
|
||||
|
||||
for (AnimatedVertex vertex : quad.animatedVertexPositions) {
|
||||
Vector4f pos = new Vector4f(vertex.pos, 1.0F);
|
||||
vertices.add(new SingleGroupVertexBuilder()
|
||||
.setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F))
|
||||
.setNormal(new Vec3f(norm.x(), norm.y(), norm.z()))
|
||||
.setTextureCoordinate(new Vec2f(vertex.u, vertex.v))
|
||||
.setEffectiveJointIDs(new Vec3f(vertex.jointId.getX(), 0, 0))
|
||||
.setEffectiveJointWeights(new Vec3f(1.0F, 0.0F, 0.0F))
|
||||
.setEffectiveJointNumber(1)
|
||||
);
|
||||
}
|
||||
|
||||
triangluatePolygon(indices, partDefinition, indexCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Direction getDirectionFromVector(Vector3f directionVec) {
|
||||
for (Direction direction : Direction.values()) {
|
||||
Vector3f direcVec = new Vector3f(Float.compare(directionVec.x(), -0.0F) == 0 ? 0.0F : directionVec.x(), directionVec.y(), directionVec.z());
|
||||
if (direcVec.equals(direction.step())) {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Vec3 getCenterOfCube(PoseStack poseStack, ModelPart.Cube cube) {
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
double minZ = Double.MAX_VALUE;
|
||||
double maxX = Double.MIN_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
double maxZ = Double.MIN_VALUE;
|
||||
|
||||
Matrix4f matrix = poseStack.last().pose();
|
||||
|
||||
for (ModelPart.Polygon quad : cube.polygons) {
|
||||
for (ModelPart.Vertex v : quad.vertices) {
|
||||
Vector4f translatedPosition = new Vector4f(v.pos, 1.0F);
|
||||
translatedPosition.mul(matrix);
|
||||
|
||||
if (minX > translatedPosition.x()) {
|
||||
minX = translatedPosition.x();
|
||||
}
|
||||
|
||||
if (minY > translatedPosition.y()) {
|
||||
minY = translatedPosition.y();
|
||||
}
|
||||
|
||||
if (minZ > translatedPosition.z()) {
|
||||
minZ = translatedPosition.z();
|
||||
}
|
||||
|
||||
if (maxX < translatedPosition.x()) {
|
||||
maxX = translatedPosition.x();
|
||||
}
|
||||
|
||||
if (maxY < translatedPosition.y()) {
|
||||
maxY = translatedPosition.y();
|
||||
}
|
||||
|
||||
if (maxZ < translatedPosition.z()) {
|
||||
maxZ = translatedPosition.z();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Vec3(minX + (maxX - minX) * 0.5D, minY + (maxY - minY) * 0.5D, minZ + (maxZ - minZ) * 0.5D);
|
||||
}
|
||||
|
||||
static Vector3f getClipPoint(Vector3f pos1, Vector3f pos2, float yClip) {
|
||||
Vector3f direct = new Vector3f(pos2);
|
||||
direct.sub(pos1);
|
||||
direct.mul((yClip - pos1.y()) / (pos2.y() - pos1.y()));
|
||||
|
||||
Vector3f clipPoint = new Vector3f(pos1);
|
||||
clipPoint.add(direct);
|
||||
|
||||
return clipPoint;
|
||||
}
|
||||
|
||||
static ModelPart.Vertex getTranslatedVertex(ModelPart.Vertex original, Matrix4f matrix) {
|
||||
Vector4f translatedPosition = new Vector4f(original.pos, 1.0F);
|
||||
translatedPosition.mul(matrix);
|
||||
|
||||
return new ModelPart.Vertex(translatedPosition.x(), translatedPosition.y(), translatedPosition.z(), original.u, original.v);
|
||||
}
|
||||
|
||||
static class AnimatedVertex extends ModelPart.Vertex {
|
||||
final Vec3i jointId;
|
||||
final Vec3f weight;
|
||||
|
||||
public AnimatedVertex(ModelPart.Vertex posTexVertx, int jointId) {
|
||||
this(posTexVertx, jointId, 0, 0, 1.0F, 0.0F, 0.0F);
|
||||
}
|
||||
|
||||
public AnimatedVertex(ModelPart.Vertex posTexVertx, int jointId1, int jointId2, int jointId3, float weight1, float weight2, float weight3) {
|
||||
this(posTexVertx, new Vec3i(jointId1, jointId2, jointId3), new Vec3f(weight1, weight2, weight3));
|
||||
}
|
||||
|
||||
public AnimatedVertex(ModelPart.Vertex posTexVertx, Vec3i ids, Vec3f weights) {
|
||||
this(posTexVertx, posTexVertx.u, posTexVertx.v, ids, weights);
|
||||
}
|
||||
|
||||
public AnimatedVertex(ModelPart.Vertex posTexVertx, float u, float v, Vec3i ids, Vec3f weights) {
|
||||
super(posTexVertx.pos.x(), posTexVertx.pos.y(), posTexVertx.pos.z(), u, v);
|
||||
this.jointId = ids;
|
||||
this.weight = weights;
|
||||
}
|
||||
}
|
||||
|
||||
static class AnimatedPolygon {
|
||||
public final AnimatedVertex[] animatedVertexPositions;
|
||||
public final Vector3f normal;
|
||||
|
||||
public AnimatedPolygon(AnimatedVertex[] positionsIn, Direction directionIn) {
|
||||
this.animatedVertexPositions = positionsIn;
|
||||
this.normal = directionIn.step();
|
||||
}
|
||||
|
||||
public AnimatedPolygon(AnimatedVertex[] positionsIn, float cor, Direction directionIn) {
|
||||
this.animatedVertexPositions = positionsIn;
|
||||
positionsIn[0] = new AnimatedVertex(positionsIn[0], positionsIn[0].u, positionsIn[0].v + cor, positionsIn[0].jointId, positionsIn[0].weight);
|
||||
positionsIn[1] = new AnimatedVertex(positionsIn[1], positionsIn[1].u, positionsIn[1].v + cor, positionsIn[1].jointId, positionsIn[1].weight);
|
||||
positionsIn[2] = new AnimatedVertex(positionsIn[2], positionsIn[2].u, positionsIn[2].v - cor, positionsIn[2].jointId, positionsIn[2].weight);
|
||||
positionsIn[3] = new AnimatedVertex(positionsIn[3], positionsIn[3].u, positionsIn[3].v - cor, positionsIn[3].jointId, positionsIn[3].weight);
|
||||
this.normal = directionIn.step();
|
||||
}
|
||||
}
|
||||
|
||||
public record VanillaMeshPartDefinition(String partName, RenderProperties renderProperties, List<String> path, OpenMatrix4f invertedParentTransform, ModelPart root) implements MeshPartDefinition {
|
||||
public static MeshPartDefinition of(String partName, RenderProperties renderProperties) {
|
||||
return new VanillaMeshPartDefinition(partName, renderProperties, null, null, null);
|
||||
}
|
||||
|
||||
public static MeshPartDefinition of(String partName) {
|
||||
return new VanillaMeshPartDefinition(partName, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* For animated models
|
||||
* @param partName
|
||||
* @param path
|
||||
* @param invertedParentTransform
|
||||
* @param root
|
||||
* @return
|
||||
*/
|
||||
public static MeshPartDefinition of(String partName, List<String> path, OpenMatrix4f invertedParentTransform, ModelPart root) {
|
||||
return new VanillaMeshPartDefinition(partName, null, path, invertedParentTransform, root);
|
||||
}
|
||||
|
||||
public Supplier<OpenMatrix4f> getModelPartAnimationProvider() {
|
||||
return this.root == null ? () -> null : () -> {
|
||||
PoseStack poseStack = new PoseStack();
|
||||
poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F));
|
||||
poseStack.mulPose(QuaternionUtils.XP.rotationDegrees(180.0F));
|
||||
poseStack.translate(0.0F, -24.0F, 0.0F);
|
||||
|
||||
this.progress(this.root, poseStack, false);
|
||||
ModelPart part = this.root;
|
||||
int idx = 0;
|
||||
|
||||
for (String childPartName : this.path) {
|
||||
idx++;
|
||||
part = part.getChild(childPartName);
|
||||
this.progress(part, poseStack, idx == this.path.size());
|
||||
}
|
||||
|
||||
OpenMatrix4f animParentTransform = OpenMatrix4f.importFromMojangMatrix(poseStack.last().pose());
|
||||
animParentTransform.m30 *= 0.0625F;
|
||||
animParentTransform.m31 *= 0.0625F;
|
||||
animParentTransform.m32 *= 0.0625F;
|
||||
|
||||
ModelPart lastPart = part;
|
||||
PartPose partPose = part.getInitialPose();
|
||||
OpenMatrix4f partAnimation = OpenMatrix4f.mulMatrices(animParentTransform,
|
||||
new OpenMatrix4f().mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(partPose.zRot, partPose.yRot, partPose.xRot)).transpose().invert())
|
||||
.translate(new Vec3f(lastPart.x - partPose.x, lastPart.y - partPose.y, lastPart.z - partPose.z).scale(0.0625F))
|
||||
.mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(partPose.zRot, partPose.yRot, partPose.xRot)).transpose())
|
||||
.mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(lastPart.zRot - partPose.zRot, lastPart.yRot - partPose.yRot, lastPart.xRot - partPose.xRot)).transpose())
|
||||
.scale(new Vec3f(lastPart.xScale, lastPart.yScale, lastPart.zScale)),
|
||||
this.invertedParentTransform);
|
||||
|
||||
return partAnimation;
|
||||
};
|
||||
}
|
||||
|
||||
private void progress(ModelPart part, PoseStack poseStack, boolean last) {
|
||||
PartPose initialPose = part.getInitialPose();
|
||||
|
||||
if (last) {
|
||||
poseStack.translate(initialPose.x, initialPose.y, initialPose.z);
|
||||
poseStack.mulPose(new Quaternionf().rotationZYX(initialPose.zRot, initialPose.yRot, initialPose.xRot));
|
||||
} else {
|
||||
poseStack.translate(part.x, part.y, part.z);
|
||||
poseStack.mulPose(new Quaternionf().rotationZYX(part.zRot, part.yRot, part.xRot));
|
||||
poseStack.scale(part.xScale, part.yScale, part.zScale);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o instanceof MeshPartDefinition comparision) {
|
||||
return this.partName.equals(comparision.partName());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.partName.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user