Refactor V2 animation, furniture, and GLTF rendering
Broad consolidation of the V2 bondage-item, furniture-entity, and
client-side GLTF pipeline.
Parsing and rendering
- Shared GLB parsing helpers consolidated into GlbParserUtils
(accessor reads, weight normalization, joint-index clamping,
coordinate-space conversion, animation parse, primitive loop).
- Grow-on-demand Matrix4f[] scratch pool in GltfSkinningEngine and
GltfLiveBoneReader — removes per-frame joint-matrix allocation
from the render hot path.
- emitVertex helper dedups three parallel loops in GltfMeshRenderer.
- TintColorResolver.resolve has a zero-alloc path when the item
declares no tint channels.
- itemAnimCache bounded to 256 entries (access-order LRU) with
atomic get-or-compute under the map's monitor.
Animation correctness
- First-in-joint-order wins when body and torso both map to the
same PlayerAnimator slot; duplicate writes log a single WARN.
- Multi-item composites honor the FullX / FullHeadX opt-in that
the single-item path already recognized.
- Seat transforms converted to Minecraft model-def space so
asymmetric furniture renders passengers at the correct offset.
- GlbValidator: IBM count / type / presence, JOINTS_0 presence,
animation channel target validation, multi-skin support.
Furniture correctness and anti-exploit
- Seat assignment synced via SynchedEntityData (server is
authoritative; eliminates client-server divergence on multi-seat).
- Force-mount authorization requires same dimension and a free
seat; cross-dimension distance checks rejected.
- Reconnection on login checks for seat takeover before re-mount
and force-loads the target chunk for cross-dimension cases.
- tiedup_furniture_lockpick_ctx carries a session UUID nonce so
stale context can't misroute a body-item lockpick.
- tiedup_locked_furniture survives death without keepInventory
(Forge 1.20.1 does not auto-copy persistent data on respawn).
Lifecycle and memory
- EntityCleanupHandler fans EntityLeaveLevelEvent out to every
per-entity state map on the client.
- DogPoseRenderHandler re-keyed by UUID (stable across dimension
change; entity int ids are recycled).
- PetBedRenderHandler, PlayerArmHideEventHandler, and
HeldItemHideHandler use receiveCanceled + sentinel sets so
Pre-time mutations are restored even when a downstream handler
cancels the render.
Tests
- JUnit harness with 76+ tests across GlbParserUtils, GltfPoseConverter,
FurnitureSeatGeometry, and FurnitureAuthPredicate.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.tiedup.remake.client.gltf;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.kosmx.playerAnim.core.util.Pair;
|
||||
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
|
||||
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
|
||||
@@ -42,6 +43,22 @@ public final class GltfLiveBoneReader {
|
||||
|
||||
private GltfLiveBoneReader() {}
|
||||
|
||||
// Scratch pools for joint-matrix computation. Render-thread-only
|
||||
// (asserted below). Pre-populated Matrix4f slots are reused via
|
||||
// set() / identity() / mul(). See GltfSkinningEngine for the twin pool.
|
||||
private static Matrix4f[] scratchJointMatrices = new Matrix4f[0];
|
||||
private static Matrix4f[] scratchWorldTransforms = new Matrix4f[0];
|
||||
private static final Matrix4f scratchLocal = new Matrix4f();
|
||||
|
||||
private static Matrix4f[] ensureScratch(Matrix4f[] current, int needed) {
|
||||
if (current.length >= needed) return current;
|
||||
Matrix4f[] next = new Matrix4f[needed];
|
||||
int i = 0;
|
||||
for (; i < current.length; i++) next[i] = current[i];
|
||||
for (; i < needed; i++) next[i] = new Matrix4f();
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute joint matrices by reading live skeleton state from the HumanoidModel.
|
||||
* <p>
|
||||
@@ -57,7 +74,9 @@ public final class GltfLiveBoneReader {
|
||||
* @param model the HumanoidModel after PlayerAnimator has applied rotations
|
||||
* @param data parsed glTF data (MC-converted)
|
||||
* @param entity the living entity being rendered
|
||||
* @return array of joint matrices ready for skinning, or null on failure
|
||||
* @return live reference to an internal scratch buffer (or null on failure).
|
||||
* Caller MUST consume before the next call to any {@code compute*}
|
||||
* method on this class; do not store.
|
||||
*/
|
||||
public static Matrix4f[] computeJointMatricesFromModel(
|
||||
HumanoidModel<?> model,
|
||||
@@ -65,10 +84,17 @@ public final class GltfLiveBoneReader {
|
||||
LivingEntity entity
|
||||
) {
|
||||
if (model == null || data == null || entity == null) return null;
|
||||
assert RenderSystem.isOnRenderThread()
|
||||
: "GltfLiveBoneReader.computeJointMatricesFromModel must run on the render thread (scratch buffers are not thread-safe)";
|
||||
|
||||
int jointCount = data.jointCount();
|
||||
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
|
||||
Matrix4f[] worldTransforms = new Matrix4f[jointCount];
|
||||
scratchJointMatrices = ensureScratch(scratchJointMatrices, jointCount);
|
||||
scratchWorldTransforms = ensureScratch(
|
||||
scratchWorldTransforms,
|
||||
jointCount
|
||||
);
|
||||
Matrix4f[] jointMatrices = scratchJointMatrices;
|
||||
Matrix4f[] worldTransforms = scratchWorldTransforms;
|
||||
|
||||
int[] parents = data.parentJointIndices();
|
||||
String[] jointNames = data.jointNames();
|
||||
@@ -109,23 +135,22 @@ public final class GltfLiveBoneReader {
|
||||
}
|
||||
|
||||
// Build local transform: translate(restTranslation) * rotate(localRot)
|
||||
Matrix4f local = new Matrix4f();
|
||||
local.translate(restTranslations[j]);
|
||||
local.rotate(localRot);
|
||||
scratchLocal.identity();
|
||||
scratchLocal.translate(restTranslations[j]);
|
||||
scratchLocal.rotate(localRot);
|
||||
|
||||
// Compose with parent to get world transform
|
||||
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
|
||||
worldTransforms[j] = new Matrix4f(
|
||||
worldTransforms[parents[j]]
|
||||
).mul(local);
|
||||
// Compose with parent to get world transform.
|
||||
// Same semantics as pre-refactor: treat as root when parent hasn't
|
||||
// been processed yet (parents[j] >= j was a null in the old array).
|
||||
Matrix4f world = worldTransforms[j];
|
||||
if (parents[j] >= 0 && parents[j] < j) {
|
||||
world.set(worldTransforms[parents[j]]).mul(scratchLocal);
|
||||
} else {
|
||||
worldTransforms[j] = new Matrix4f(local);
|
||||
world.set(scratchLocal);
|
||||
}
|
||||
|
||||
// Final joint matrix = worldTransform * inverseBindMatrix
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
jointMatrices[j].set(world).mul(data.inverseBindMatrices()[j]);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
|
||||
Reference in New Issue
Block a user