Skip to content

Commit cec2907

Browse files
committed
fix more cosmetics rendering bugs 💀
1 parent cebd23a commit cec2907

6 files changed

Lines changed: 96 additions & 18 deletions

File tree

‎src/main/kotlin/org/polyfrost/polyplus/client/bedrock/model/BedrockEffectBoneTreeBuilder.kt‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal class BedrockEffectBoneTreeBuilder(
1616
private val textureHeight = geometry.description.textureHeight
1717
private val childrenByParent = geometry.childrenByParent()
1818
private val built = mutableMapOf<String, BedrockBoneRenderer>()
19+
private val visiting = mutableSetOf<String>()
1920

2021
val bones: Map<String, BedrockBoneRenderer> get() = built
2122

@@ -24,7 +25,11 @@ internal class BedrockEffectBoneTreeBuilder(
2425

2526
val bone = geometry.bones[name] ?: error("Missing bone $name")
2627
val effectiveLevel = bone.lightLevel.takeIf { it >= 0 } ?: parentLevel
27-
val children = (childrenByParent[name] ?: emptyList()).map { buildBone(it, effectiveLevel) }
28+
check(visiting.add(name)) { "Cyclic bone parenting detected at $name" }
29+
val children = (childrenByParent[name] ?: emptyList())
30+
.filter { it != name && it !in visiting }
31+
.map { buildBone(it, effectiveLevel) }
32+
visiting.remove(name)
2833
val position = bone.initialEffectPosition(geometry.bones, playerGeometry)
2934
val rotation = bone.initialEffectRotation()
3035

‎src/main/kotlin/org/polyfrost/polyplus/client/bedrock/render/BedrockMesh.kt‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class BedrockMesh private constructor(
9595
val modelMaxY = -(minY - pivot.y) + inflate
9696

9797
var modelMinZ = minZ
98-
var modelMaxZ = minZ + depth
98+
var modelMaxZ = minZ + depth + 2f * inflate
9999
if (depth < BILLBOARD_EPSILON) {
100100
val centerZ = (min(cube.origin.z, endZ) + max(cube.origin.z, endZ)) * 0.5f - pivot.z
101101
modelMinZ = centerZ - BILLBOARD_HALF_THICKNESS
@@ -106,7 +106,7 @@ class BedrockMesh private constructor(
106106
minX = minX,
107107
minY = modelMinY,
108108
minZ = modelMinZ,
109-
maxX = minX + width,
109+
maxX = minX + width + 2f * inflate,
110110
maxY = modelMaxY,
111111
maxZ = modelMaxZ,
112112
)
@@ -278,12 +278,12 @@ class BedrockMesh private constructor(
278278
if (width >= BILLBOARD_EPSILON && height >= BILLBOARD_EPSILON) {
279279
addFace(
280280
arrayOf(north1, north0, north3, north2),
281-
u0 + uvDepth, v0 + uvDepth, u0 + uvDepth + uvWidth, v0 + uvDepth + uvHeight,
281+
u1, v1, u2, v2,
282282
Direction.NORTH,
283283
)
284284
addFace(
285285
arrayOf(south0, south1, south2, south3),
286-
u0 + uvDepth, v0 + uvDepth, u0 + uvDepth + uvWidth, v0 + uvDepth + uvHeight,
286+
u3, v1, u4, v2,
287287
Direction.SOUTH,
288288
)
289289
}

‎src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/assets/AttachedCosmeticParser.kt‎

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ internal object AttachedCosmeticParser {
8181
}
8282

8383
private fun applyAutoMirror(geometry: BedrockGeometry): BedrockGeometry {
84+
if (geometry.bones.values.any { bone -> bone.cubes.any { it.mirror } }) {
85+
return geometry
86+
}
87+
8488
val bones = geometry.bones.values
8589
val rewritten = geometry.bones.mapValues inner@{ (_, bone) ->
8690
if (bone.pivot.x <= 0f || bone.cubes.none { it.uv.faces.isEmpty() }) return@inner bone
@@ -150,19 +154,52 @@ internal object AttachedCosmeticParser {
150154
return geometry
151155
}
152156

153-
val boneNames = geometry.bones.keys
154-
val renderable = geometry.renderableBoneNames()
155-
val rewritten = geometry.bones.mapValues { (name, bone) ->
156-
val isTopLevel = bone.parent.isEmpty() || bone.parent !in boneNames
157-
if (!isTopLevel || name !in renderable) {
157+
val prepared = if (slot == BodySlot.Boots) splitStraddlingLegBones(geometry) else geometry
158+
159+
val renderable = prepared.renderableBoneNames()
160+
val rewritten = prepared.bones.mapValues { (name, bone) ->
161+
if (name !in renderable || !isAttachmentRoot(prepared, bone)) {
158162
return@mapValues bone
159163
}
160-
val target = attachTargetFor(geometry, slot, bone) ?: return@mapValues bone
164+
val target = attachTargetFor(prepared, slot, bone) ?: return@mapValues bone
161165
bone.copy(parent = target.serializedName)
162166
}
163-
return geometry.copy(bones = rewritten)
167+
return prepared.copy(bones = rewritten)
168+
}
169+
170+
private fun isAttachmentRoot(geometry: BedrockGeometry, bone: BedrockBone): Boolean {
171+
if (bone.cubes.isEmpty()) return false
172+
var parent = geometry.bones[bone.parent]
173+
while (parent != null) {
174+
if (parent.cubes.isNotEmpty()) return false
175+
parent = geometry.bones[parent.parent]
176+
}
177+
return true
178+
}
179+
180+
private fun splitStraddlingLegBones(geometry: BedrockGeometry): BedrockGeometry {
181+
val hasChild = geometry.bones.values.mapTo(hashSetOf()) { it.parent }
182+
val renderable = geometry.renderableBoneNames()
183+
val result = LinkedHashMap<String, BedrockBone>(geometry.bones.size)
184+
for ((name, bone) in geometry.bones) {
185+
if (name !in renderable || name in hasChild || bone.cubes.isEmpty()) {
186+
result[name] = bone
187+
continue
188+
}
189+
val right = bone.cubes.filter { cubeCenterX(it) < 0f }
190+
val left = bone.cubes.filter { cubeCenterX(it) >= 0f }
191+
if (right.isEmpty() || left.isEmpty()) {
192+
result[name] = bone
193+
continue
194+
}
195+
result["${name}_r"] = bone.copy(name = "${name}_r", cubes = right)
196+
result["${name}_l"] = bone.copy(name = "${name}_l", cubes = left)
197+
}
198+
return geometry.copy(bones = result)
164199
}
165200

201+
private fun cubeCenterX(cube: BedrockCube): Float = cube.origin.x + cube.size.x * 0.5f
202+
166203
private fun attachTargetFor(geometry: BedrockGeometry, slot: BodySlot, bone: BedrockBone): PlayerModelBone? {
167204
if (slot == BodySlot.Boots) {
168205
legForBone(geometry, bone)?.let { return it }

‎src/main/kotlin/org/polyfrost/polyplus/client/gui/PolyPlusCosmeticsScreen.kt‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,9 +817,12 @@ private fun PreviewPanel(
817817
.border(1.dp, LocalTheme.current.borderColor, RoundedCornerShape(12.dp)),
818818
) {
819819
Box(modifier = Modifier.weight(1f).fillMaxHeight()) {
820+
val hasHeadCosmetic = CosmeticCatalog.localEquipped().equipped
821+
.containsKey(org.polyfrost.polyplus.client.network.http.responses.BodySlot.Hat)
820822
PlayerPreview(
821823
Modifier.align(Alignment.Center).size(190.dp, 330.dp),
822824
source = PlayerPreviewSource.LocalLive,
825+
verticalAnchor = if (hasHeadCosmetic) 0.64f else 0.5f,
823826
)
824827
if (selected != null) {
825828
Column(Modifier.align(Alignment.TopStart).padding(18.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
@@ -1275,7 +1278,7 @@ private fun CosmeticThumbnail(item: CosmeticUiItem, modifier: Modifier) {
12751278
source = source,
12761279
allowDrag = false,
12771280
modelScale = 0.42f,
1278-
verticalAnchor = 0.52f,
1281+
verticalAnchor = if (item.type == CosmeticType.Hat) 0.64f else 0.52f,
12791282
previewKey = "card-${item.groupId}",
12801283
)
12811284
}
@@ -1344,9 +1347,13 @@ private fun BundlePreviewPanel(
13441347
.background(cardBrush())
13451348
.border(1.dp, LocalTheme.current.borderColor, RoundedCornerShape(12.dp)),
13461349
) {
1350+
val bundleSource = rememberBundlePreviewSource(bundleView)
1351+
val bundleHasHat = (bundleSource as? PlayerPreviewSource.Override)
1352+
?.equipment?.get(org.polyfrost.polyplus.client.network.http.responses.BodySlot.Hat) != null
13471353
PlayerPreview(
13481354
Modifier.align(Alignment.Center).size(190.dp, 300.dp),
1349-
source = rememberBundlePreviewSource(bundleView),
1355+
source = bundleSource,
1356+
verticalAnchor = if (bundleHasHat) 0.64f else 0.5f,
13501357
)
13511358
}
13521359
}

‎src/main/kotlin/org/polyfrost/polyplus/client/gui/PolyPlusMainMenuScreen.kt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ class PolyPlusMainMenuScreen : ComposeScreen(RenderMode.CONTINUOUS) {
121121
}
122122
//?}
123123

124+
//? if <26.1 {
125+
/*override fun renderBackground(ctx: net.minecraft.client.gui.GuiGraphics, mouseX: Int, mouseY: Int, tickDelta: Float) {
126+
if (mainMenuPanoramaEnabled()) return
127+
super.renderBackground(ctx, mouseX, mouseY, tickDelta)
128+
}
129+
*///?} else {
130+
override fun extractBackground(ctx: net.minecraft.client.gui.GuiGraphicsExtractor, mouseX: Int, mouseY: Int, tickDelta: Float) {
131+
if (mainMenuPanoramaEnabled()) return
132+
super.extractBackground(ctx, mouseX, mouseY, tickDelta)
133+
}
134+
//?}
135+
124136
@Composable
125137
override fun compose() {
126138
val mc = net.minecraft.client.Minecraft.getInstance()

‎src/main/kotlin/org/polyfrost/polyplus/client/gui/preview/PlayerPreviewRenderer.kt‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@ object PlayerPreviewRenderer {
183183
return cols
184184
}
185185

186+
private fun topFadeRows(h: Int): FloatArray {
187+
val rows = FloatArray(h)
188+
val fadePx = h * EDGE_FADE_FRACTION
189+
for (r in 0 until h) {
190+
val d = r + 0.5f // distance from top edge (pixel center)
191+
val t = if (fadePx <= 0f) 1f else (d / fadePx).coerceIn(0f, 1f)
192+
rows[r] = t * t * t * (t * (t * 6f - 15f) + 10f)
193+
}
194+
return rows
195+
}
196+
186197
private fun scaleByte(v: Int, f: Float): Byte {
187198
val s = (v * f).toInt()
188199
return (if (s > 255) 255 else s).toByte()
@@ -542,13 +553,16 @@ object PlayerPreviewRenderer {
542553
private fun toImageBitmap(data: java.nio.ByteBuffer, w: Int, h: Int, pixelSize: Int): ImageBitmap {
543554
val out = ByteArray(w * h * 4)
544555
val fade = edgeFadeColumns(w)
556+
val rowFade = topFadeRows(h)
545557
for (y in 0 until h) {
546-
val dstRow = (h - 1 - y) * w
558+
val outRow = h - 1 - y
559+
val dstRow = outRow * w
547560
val srcRow = y * w
561+
val rf = rowFade[outRow]
548562
for (x in 0 until w) {
549563
val si = (srcRow + x) * pixelSize
550564
val di = (dstRow + x) * 4
551-
val f = fade[x]
565+
val f = fade[x] * rf
552566
out[di] = scaleByte(data.get(si + 2).toInt() and 0xFF, f)
553567
out[di + 1] = scaleByte(data.get(si + 1).toInt() and 0xFF, f)
554568
out[di + 2] = scaleByte(data.get(si).toInt() and 0xFF, f)
@@ -895,8 +909,11 @@ object PlayerPreviewRenderer {
895909
try {
896910
val out = ByteArray(w * h * 4)
897911
val fade = edgeFadeColumns(w)
912+
val rowFade = topFadeRows(h)
898913
for (y in 0 until h) {
899-
val dstRow = (h - 1 - y) * w
914+
val outRow = h - 1 - y
915+
val dstRow = outRow * w
916+
val rf = rowFade[outRow]
900917
for (x in 0 until w) {
901918
//? if >= 1.21.4 {
902919
val px = img.getPixel(x, y)
@@ -905,7 +922,7 @@ object PlayerPreviewRenderer {
905922
/*val px = img.getPixelRGBA(x, y)
906923
*///?}
907924
val di = (dstRow + x) * 4
908-
val f = fade[x]
925+
val f = fade[x] * rf
909926
out[di] = scaleByte((px ushr 16) and 0xFF, f)
910927
out[di + 1] = scaleByte((px ushr 8) and 0xFF, f)
911928
out[di + 2] = scaleByte(px and 0xFF, f)

0 commit comments

Comments
 (0)