From fa1d1f312a402bfd60a256b7f420a19400e94d07 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 10 Apr 2026 04:07:12 +0200 Subject: [PATCH] Improved cloak physics 1.21.5 --- .../impl/cosmetics/CloakFeatureRenderer.java | 320 ++++++++++++------ 1 file changed, 222 insertions(+), 98 deletions(-) diff --git a/versions/1.21.5/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java b/versions/1.21.5/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java index 54ce803..e549057 100644 --- a/versions/1.21.5/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java +++ b/versions/1.21.5/src/main/java/org/saturnclient/impl/cosmetics/CloakFeatureRenderer.java @@ -2,6 +2,10 @@ package org.saturnclient.impl.cosmetics; import java.util.Arrays; +import org.joml.Matrix3f; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; import org.saturnclient.client.player.SaturnPlayer; import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.config.Config; @@ -24,6 +28,7 @@ import net.minecraft.component.type.EquippableComponent; import net.minecraft.item.ItemStack; import net.minecraft.item.equipment.EquipmentAsset; import net.minecraft.registry.RegistryKey; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; @@ -38,6 +43,8 @@ public class CloakFeatureRenderer extends FeatureRenderer 0) ? (2.0f - segmentValues[i - 1]) * ((float) Math.PI / 2f) : angle; - float startAvgAngle = (angle + prevAngle) / 2.0f; - float thickYStart = (float) Math.sin(startAvgAngle) * capeDepth; - float thickZStart = (float) Math.cos(startAvgAngle) * capeDepth; + spineY[i + 1] = spineY[i] + dirY * partHeight; + spineZ[i + 1] = spineZ[i] + dirZ * partHeight; + } - // 2. Thickness at the END of this segment (Must match NEXT segment's start) - float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle; - float endAvgAngle = (angle + nextAngle) / 2.0f; - float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth; - float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth; + // ============================= + // 2. Grid + // ============================= - float nextY = curY + dirY * capePartHeight; - float nextZ = curZ + dirZ * capePartHeight; + Vec3d[][] inner = new Vec3d[H_PARTS + 1][PARTS + 1]; + Vec3d[][] outer = new Vec3d[H_PARTS + 1][PARTS + 1]; - // Inner Vertices (The spine of the cape) - Vec3d innerTopLeft = new Vec3d(x2, curY, curZ); - Vec3d innerTopRight = new Vec3d(x1, curY, curZ); - Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ); - Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ); + for (int x = 0; x <= H_PARTS; x++) { - // Outer Vertices (Offset by the specific joint thickness) - Vec3d outerTopLeft = new Vec3d(x2, curY - thickYStart, curZ - thickZStart); - Vec3d outerTopRight = new Vec3d(x1, curY - thickYStart, curZ - thickZStart); - Vec3d outerBotLeft = new Vec3d(x2, nextY - thickYEnd, nextZ - thickZEnd); - Vec3d outerBotRight = new Vec3d(x1, nextY - thickYEnd, nextZ - thickZEnd); + float t = (float) x / H_PARTS; + float xPos = -capeWidth / 2.0f + capeWidth * t; - // FRONT (facing camera) - this.renderCapeQuad(vertexConsumer, matrixStack, - outerBotRight, outerBotLeft, outerTopLeft, outerTopRight, - frontU2, frontV1 + (frontPartV * i), frontU1, frontV1 + (frontPartV * (i + 1)), - light, overlay, 0, 0, 1, false); + float centered = t - 0.5f; + float curve = (centered * centered) * 0.3f; + float zCurve = (curve * horizontalCurve) / PARTS; - // BACK (Facing player) - this.renderCapeQuad(vertexConsumer, matrixStack, - innerBotLeft, innerBotRight, innerTopRight, innerTopLeft, - backU2, backV1 + (backPartV * i), backU1, backV1 + (backPartV * (i + 1)), - light, overlay, 0, 0, -1, false); + for (int i = 0; i <= PARTS; i++) { + float y = spineY[i]; + float z = spineZ[i]; - // LEFT EDGE - this.renderCapeQuad(vertexConsumer, matrixStack, - innerBotLeft, outerBotLeft, outerTopLeft, innerTopLeft, - leftU1, leftV1 + (leftPartV * i), leftU2, leftV1 + (leftPartV * (i + 1)), - light, overlay, 1, 0, 0, false); + int idx = Math.min(i, PARTS - 1); + // angle: 0 is horizontal (y-flat), PI/2 is vertical (z-flat) + float angle = (2.0f - segmentValues[idx]) * ((float) Math.PI / 2f); - // RIGHT EDGE - this.renderCapeQuad(vertexConsumer, matrixStack, - outerBotRight, innerBotRight, innerTopRight, outerTopRight, - rightU1, rightV1 + (rightPartV * i), rightU2, rightV1 + (rightPartV * (i + 1)), + float cosA = (float) Math.cos(angle); + float sinA = (float) Math.sin(angle); + + // Calculate how much the horizontal curve shifts the vertex + float currentCurve = zCurve * i; + + // Rotate the curve offset so it aligns with the cape's tilt + float curveY = sinA * currentCurve; + float curveZ = cosA * currentCurve; + + float thickY = sinA * capeDepth; + float thickZ = cosA * capeDepth; + + inner[x][i] = new Vec3d(xPos, y - curveY, z - curveZ); + outer[x][i] = new Vec3d(xPos, (y - curveY) - thickY, (z - curveZ) - thickZ); + } + + } + + // ============================= + // 3. Front + Back + // ============================= + + for (int x = 0; x < H_PARTS; x++) { + + float uStart = frontU1 + (frontU2 - frontU1) * (1.0f - ((float) x / H_PARTS)); + float uEnd = frontU1 + (frontU2 - frontU1) * (1.0f - ((float) (x + 1) / H_PARTS)); + + float buStart = backU1 + (backU2 - backU1) * ((float) x / H_PARTS); + float buEnd = backU1 + (backU2 - backU1) * ((float) (x + 1) / H_PARTS); + + for (int i = 0; i < PARTS; i++) { + + float vStart = frontV1 + frontPartV * i; + float vEnd = frontV1 + frontPartV * (i + 1); + + float bvStart = backV1 + backPartV * i; + float bvEnd = backV1 + backPartV * (i + 1); + + Vec3d innerTL = inner[x + 1][i]; + Vec3d innerTR = inner[x][i]; + Vec3d innerBL = inner[x + 1][i + 1]; + Vec3d innerBR = inner[x][i + 1]; + + Vec3d outerTL = outer[x + 1][i]; + Vec3d outerTR = outer[x][i]; + Vec3d outerBL = outer[x + 1][i + 1]; + Vec3d outerBR = outer[x][i + 1]; + + // FRONT + renderCapeQuad(vertexConsumer, entry, + outerBR, outerBL, outerTL, outerTR, + uStart, vStart, uEnd, vEnd, + light, overlay, 0, 0, 1, false); + + // BACK + renderCapeQuad(vertexConsumer, entry, + innerBL, innerBR, innerTR, innerTL, + buEnd, bvStart, buStart, bvEnd, + light, overlay, 0, 0, -1, false); + } + } + + // ============================= + // 4. LEFT SIDE (x = H_PARTS) + // ============================= + + for (int i = 0; i < PARTS; i++) { + + Vec3d innerTop = inner[H_PARTS][i]; + Vec3d innerBot = inner[H_PARTS][i + 1]; + Vec3d outerTop = outer[H_PARTS][i]; + Vec3d outerBot = outer[H_PARTS][i + 1]; + + float vStart = leftV1 + leftPartV * i; + float vEnd = leftV1 + leftPartV * (i + 1); + + renderCapeQuad(vertexConsumer, entry, + innerTop, outerTop, outerBot, innerBot, + leftU1, vEnd, + leftU2, vStart, light, overlay, -1, 0, 0, false); + } - // TOP FACE (Only on first segment) - if (i == 0) { - this.renderCapeQuad(vertexConsumer, matrixStack, - outerTopLeft, outerTopRight, innerTopRight, innerTopLeft, - topU1, topV1, topU2, topV2, light, overlay, 0, 1, 0, false); - } + // ============================= + // 5. RIGHT SIDE (x = 0) + // ============================= - // BOTTOM FACE (Only on last segment) - if (i == PARTS - 1) { - this.renderCapeQuad(vertexConsumer, matrixStack, - innerBotLeft, innerBotRight, outerBotRight, outerBotLeft, - bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false); - } + for (int i = 0; i < PARTS; i++) { - curY = nextY; - curZ = nextZ; + Vec3d innerTop = inner[0][i]; + Vec3d innerBot = inner[0][i + 1]; + Vec3d outerTop = outer[0][i]; + Vec3d outerBot = outer[0][i + 1]; + + float vStart = rightV1 + rightPartV * i; + float vEnd = rightV1 + rightPartV * (i + 1); + + renderCapeQuad(vertexConsumer, entry, + outerTop, innerTop, innerBot, outerBot, + rightU1, vEnd, + rightU2, vStart, + light, overlay, 1, 0, 0, false); + } + + // ============================= + // 6. TOP (i = 0) + // ============================= + + for (int x = 0; x < H_PARTS; x++) { + + Vec3d outerL = outer[x][0]; + Vec3d outerR = outer[x + 1][0]; + Vec3d innerR = inner[x + 1][0]; + Vec3d innerL = inner[x][0]; + + float uStart = topU1 + (topU2 - topU1) * ((float) x / H_PARTS); + float uEnd = topU1 + (topU2 - topU1) * ((float) (x + 1) / H_PARTS); + + renderCapeQuad(vertexConsumer, entry, + outerL, outerR, innerR, innerL, + uStart, topV1, uEnd, topV2, + light, overlay, 0, 1, 0, false); + } + + // ============================= + // 7. BOTTOM (i = PARTS) + // ============================= + + for (int x = 0; x < H_PARTS; x++) { + + Vec3d innerL = inner[x][PARTS]; + Vec3d innerR = inner[x + 1][PARTS]; + Vec3d outerR = outer[x + 1][PARTS]; + Vec3d outerL = outer[x][PARTS]; + + float uStart = bottomU1 + (bottomU2 - bottomU1) * ((float) x / H_PARTS); + float uEnd = bottomU1 + (bottomU2 - bottomU1) * ((float) (x + 1) / H_PARTS); + + renderCapeQuad(vertexConsumer, entry, + innerL, innerR, outerR, outerL, + uStart, bottomV1, uEnd, bottomV2, + light, overlay, 0, -1, 0, false); } } @@ -328,7 +450,9 @@ public class CloakFeatureRenderer extends FeatureRenderer