Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements #24

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group

configurations {
implementation.extendsFrom lib
api.extendsFrom lib
}

repositories {
maven {
url = uri("https://maven.pkg.github.com/oryxel/cubeconverter")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}

dependencies {
//to change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
Expand All @@ -19,8 +34,7 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"

// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
lib "org.oryxel.cube:cubeconverter:1.0.5-build4"
}

processResources {
Expand All @@ -47,6 +61,14 @@ java {

jar {
from "LICENSE"

dependsOn configurations.lib
from {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
configurations.lib.collect {
zipTree(it)
}
}
}

// configure the maven publication
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import net.minecraft.world.entity.LivingEntity;

public class BedrockPlayerEntityModel<T extends LivingEntity> extends PlayerModel<T> {
public BedrockPlayerEntityModel(ModelPart root) {
super(root, false);

public BedrockPlayerEntityModel(ModelPart root, boolean slim) {
super(root, slim);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.camotoy.bedrockskinutility.client;

import net.camotoy.bedrockskinutility.client.pluginmessage.BedrockMessageHandler;
import net.camotoy.bedrockskinutility.client.pluginmessage.GeyserSkinManagerListener;
import net.camotoy.bedrockskinutility.client.message.BedrockMessageHandler;
import net.camotoy.bedrockskinutility.client.message.GeyserSkinManagerListener;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
Expand Down
235 changes: 106 additions & 129 deletions src/main/java/net/camotoy/bedrockskinutility/client/GeometryUtil.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package net.camotoy.bedrockskinutility.client;

import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.logging.LogUtils;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
Expand All @@ -12,6 +9,12 @@
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.core.Direction;
import org.oryxel.cube.model.bedrock.BedrockGeometry;
import org.oryxel.cube.model.bedrock.model.Bone;
import org.oryxel.cube.model.bedrock.model.Cube;
import org.oryxel.cube.parser.bedrock.BedrockGeometrySerializer;
import org.oryxel.cube.util.ArrayUtil;
import org.oryxel.cube.util.UVUtil;

import java.util.*;

Expand All @@ -21,156 +24,130 @@ public class GeometryUtil {
private static final Set<Direction> ALL_VISIBLE = EnumSet.allOf(Direction.class);

public static BedrockPlayerEntityModel<AbstractClientPlayer> bedrockGeoToJava(SkinInfo info) {
// There are some times when the skin image file is larger than the geometry UV points.
// In this case, we need to scale UV calls
// https://github.com/Camotoy/BedrockSkinUtility/issues/9
int uvHeight = info.getHeight();
int uvWidth = info.getWidth();

// Construct a list of all bones we need to translate
List<JsonObject> bones = new ArrayList<>();
if (info.getGeometryRaw() == null || info.getGeometryRaw().isEmpty())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

return null;

BedrockGeometry geometry = null;

try {
String geometryName = info.getGeometryName().getAsJsonObject("geometry").get("default").getAsString();
if (info.getGeometry().get("format_version").getAsString().equals("1.8.0")) {
for (JsonElement node : info.getGeometry().getAsJsonObject(geometryName).getAsJsonArray("bones")) {
bones.add(node.getAsJsonObject());
}
} else { // Seen with format_version 1.12.0
for (JsonElement node : info.getGeometry().getAsJsonArray("minecraft:geometry")) {
JsonObject o = node.getAsJsonObject();
JsonObject description = o.get("description").getAsJsonObject();
if (!description.get("identifier").getAsString().equals(geometryName)) {
continue;
} else {
uvHeight = description.get("texture_height").getAsInt();
uvWidth = description.get("texture_width").getAsInt();
}
for (JsonElement subNode : o.get("bones").getAsJsonArray()) {
bones.add(subNode.getAsJsonObject());
}
}
}
geometry = BedrockGeometrySerializer.deserialize(info.getGeometryRaw());
} catch (Exception e) {
LOGGER.error("Error while parsing geometry!");
e.printStackTrace();
// e.printStackTrace();
return null;
}

Map<String, PartInfo> stringToPart = new HashMap<>();
try {
for (JsonObject bone : bones) {
// Iterate through all bones
String name = bone.get("name").getAsString();

JsonElement jsonParent = bone.get("parent");
JsonObject parentPart = null;
String parent = null;
if (jsonParent != null) {
// Search through all bones to find the parent part
parent = jsonParent.getAsString();
for (JsonObject otherBone : bones) {
if (parent.equals(otherBone.get("name").getAsString())) {
parentPart = otherBone;
break;
}
}
}
if (geometry == null)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

return null;

List<ModelPart.Cube> cuboids = new ArrayList<>();
JsonArray pivot = bone.getAsJsonArray("pivot");
float pivotX = pivot.get(0).getAsFloat();
float pivotY = pivot.get(1).getAsFloat();
float pivotZ = pivot.get(2).getAsFloat();

JsonArray cubes = bone.getAsJsonArray("cubes");
if (cubes != null) {
for (JsonElement node : cubes) {
JsonObject cube = node.getAsJsonObject();
JsonElement mirrorNode = cube.get("mirror"); // Can be null on the llama skins in the Wandering Trader pack
boolean mirrored = mirrorNode != null && mirrorNode.getAsBoolean();
JsonArray origin = cube.getAsJsonArray("origin");
float originX = origin.get(0).getAsFloat();
float originY = origin.get(1).getAsFloat();
float originZ = origin.get(2).getAsFloat();
JsonArray size = cube.getAsJsonArray("size");
float sizeX = size.get(0).getAsFloat();
float sizeY = size.get(1).getAsFloat();
float sizeZ = size.get(2).getAsFloat();
JsonArray uv = cube.getAsJsonArray("uv");
JsonElement inflateNode = cube.get("inflate"); // Again, the llama skin
float inflate = inflateNode != null ? inflateNode.getAsFloat() : 0f;
// I didn't use the below, but it may be a helpful reference in the future
// The Y needs to be inverted, for whatever reason
// https://github.com/JannisX11/blockbench/blob/8529c0adee8565f8dac4b4583c3473b60679966d/js/transform.js#L148
cuboids.add(new ModelPart.Cube((int) uv.get(0).getAsFloat(), (int) uv.get(1).getAsFloat(),
(originX - pivotX), (-(originY + sizeY) + pivotY), (originZ - pivotZ),
sizeX, sizeY, sizeZ, inflate, inflate, inflate, mirrored, uvHeight, uvWidth, ALL_VISIBLE));
}
}
int uvHeight = geometry.textureHeight();
int uvWidth = geometry.textureWidth();

Map<String, ModelPart> children = new HashMap<>();
ModelPart part = new ModelPart(cuboids, children);
if (parentPart != null) {
// This appears to be a difference between Bedrock and Java - pivots are carried over for us
JsonArray parentPivot = parentPart.getAsJsonArray("pivot");
part.setPos(pivotX - parentPivot.get(0).getAsFloat(),
pivotY - parentPivot.get(1).getAsFloat(),
pivotZ - parentPivot.get(2).getAsFloat());
} else {
part.setPos(pivotX, pivotY, pivotZ);
}
final Map<String, Bone> boneMap = new HashMap<>();
for (Bone bone : geometry.bones()) {
boneMap.put(bone.name(), bone);
}

switch (name) { // Also do this with the overlays? Those are final, though.
case "head", "hat", "rightArm", "body", "leftArm", "leftLeg", "rightLeg" -> parent = "root";
final Map<String, PartInfo> stringToPart = new HashMap<>();
for (Bone bone : geometry.bones()) {
final List<ModelPart.Cube> cuboids = new ArrayList<>();
float pivotX = (float) bone.pivot()[0], pivotY = (float) bone.pivot()[1], pivotZ = (float) bone.pivot()[2];
Bone parentBone = null;
if (!bone.parent().isEmpty())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

parentBone = boneMap.get(bone.parent());
String parent = bone.parent(), name = bone.name();

for (Cube cube : bone.cubes()) {
double[] uv = new double[3];
if (cube instanceof Cube.PerFaceCube perFaceCube) { // support for perface uv?
uv = UVUtil.portToBoxUv(perFaceCube.uvMap(), perFaceCube.origin(),
ArrayUtil.combineArray(perFaceCube.origin(), perFaceCube.size()));
} else if (cube instanceof Cube.BoxCube boxCube) {
uv = boxCube.uvOffset();
}

name = adjustFormatting(name);
float originX = (float) cube.origin()[0], originY = (float) cube.origin()[1], originZ = (float) cube.origin()[2];
float sizeX = (float) cube.size()[0], sizeY = (float) cube.size()[1], sizeZ = (float) cube.size()[2];
float inflate = (float) cube.inflate();
// I didn't use the below, but it may be a helpful reference in the future
// The Y needs to be inverted, for whatever reason
// https://github.com/JannisX11/blockbench/blob/8529c0adee8565f8dac4b4583c3473b60679966d/js/transform.js#L148
cuboids.add(new ModelPart.Cube((int) uv[0], (int) uv[1],
(originX - pivotX), (-(originY + sizeY) + pivotY), (originZ - pivotZ),
sizeX, sizeY, sizeZ, inflate, inflate, inflate, cube.mirror(), uvHeight, uvWidth, ALL_VISIBLE));
}

Map<String, ModelPart> children = new HashMap<>();
ModelPart part = new ModelPart(cuboids, children);
// set rotation (if there is one)
part.setRotation((float) -bone.rotation()[0], (float) -bone.rotation()[1], (float) bone.rotation()[2]);

if (parentBone != null) {
// This appears to be a difference between Bedrock and Java - pivots are carried over for us
part.setPos((float) (pivotX - parentBone.pivot()[0]), (float) (pivotY - parentBone.pivot()[1]), (float) (pivotZ - parentBone.pivot()[2]));
} else part.setPos(pivotX, pivotY, pivotZ);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets


stringToPart.put(name, new PartInfo(adjustFormatting(parent), part, children));
// Please lowercase this, it can be lowercase...
switch (name.toLowerCase()) { // Also do this with the overlays? Those are final, though.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toLowerCase(Locale.ROOT)

case "head", "hat", "rightarm", "body", "leftarm", "leftleg", "rightleg" -> parent = "root";
}

for (Map.Entry<String, PartInfo> entry : stringToPart.entrySet()) {
if (entry.getValue().parent != null) {
PartInfo parentPart = stringToPart.get(entry.getValue().parent);
if (parentPart != null) {
parentPart.children.put(entry.getKey(), entry.getValue().part);
}
name = adjustFormatting(name);

stringToPart.put(name, new PartInfo(adjustFormatting(parent), part, children));
}

PartInfo root = stringToPart.get("root");

for (Map.Entry<String, PartInfo> entry : stringToPart.entrySet()) {
if (entry.getValue().parent != null) {
PartInfo parentPart = stringToPart.get(entry.getValue().parent);
if (parentPart != null)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

parentPart.children.put(entry.getKey(), entry.getValue().part);
else {
if (root != null && entry.getValue().part != root.part) // put to root if you can't find the parent.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

root.children.put(entry.getKey(), entry.getValue().part);
}
}
}

PartInfo root = stringToPart.get("root");

ensureAvailable(root.children, "ear");
root.children.computeIfAbsent("cloak", (string) -> // Required to allow a cape to render
HumanoidModel.createMesh(CubeDeformation.NONE, 0.0F).getRoot().addOrReplaceChild(string,
CubeListBuilder.create()
.texOffs(0, 0)
.addBox(-5.0F, 0.0F, -1.0F, 10.0F, 16.0F, 1.0F, CubeDeformation.NONE, 1.0F, 0.5F),
PartPose.offset(0.0F, 0.0F, 0.0F)).bake(64, 64));
ensureAvailable(root.children, "left_sleeve");
ensureAvailable(root.children, "right_sleeve");
ensureAvailable(root.children, "left_pants");
ensureAvailable(root.children, "right_pants");
ensureAvailable(root.children, "jacket");

// Create base model
return new BedrockPlayerEntityModel<>(root.part);
} catch (Exception e) {
LOGGER.error("Error while parsing geometry into model!", e);
if (root == null)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

return null;
}

ensureAvailable(root.children, "ear");
root.children.computeIfAbsent("cloak", (string) -> // Required to allow a cape to render
HumanoidModel.createMesh(CubeDeformation.NONE, 0.0F).getRoot().addOrReplaceChild(string,
CubeListBuilder.create()
.texOffs(0, 0)
.addBox(-5.0F, 0.0F, -1.0F, 10.0F, 16.0F, 1.0F, CubeDeformation.NONE, 1.0F, 0.5F),
PartPose.offset(0.0F, 0.0F, 0.0F)).bake(64, 64));
ensureAvailable(root.children, "left_sleeve");
ensureAvailable(root.children, "right_sleeve");
ensureAvailable(root.children, "left_pants");
ensureAvailable(root.children, "right_pants");
ensureAvailable(root.children, "jacket");

// Just to be safe, some model seems to have only head, arm, etc. I mean...
ensureAvailable(root.children, "hat");
ensureAvailable(root.children, "body");
ensureAvailable(root.children, "left_arm");
ensureAvailable(root.children, "right_arm");
ensureAvailable(root.children, "left_leg");
ensureAvailable(root.children, "right_leg");

return new BedrockPlayerEntityModel<>(root.part, false);
}

private static String adjustFormatting(String name) {
if (name == null) {
return null;
}

return switch (name) {
case "leftArm" -> "left_arm";
case "rightArm" -> "right_arm";
case "leftLeg" -> "left_leg";
case "rightLeg" -> "right_leg";
// it can be lowercase, use lowercase...
return switch (name.toLowerCase()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toLowerCase(Locale.ROOT)

case "leftarm" -> "left_arm";
case "rightarm" -> "right_arm";
case "leftleg" -> "left_leg";
case "rightleg" -> "right_leg";
default -> name;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public String getRefMapperConfig() {

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (mixinClassName.equals("net.camotoy.bedrockskinutility.client.mixin.CapeFeatureRendererMixin")) {
if (mixinClassName.equals("net.camotoy.bedrockskinutility.client.mixin.CapeFeatureRendererMixin") ||
mixinClassName.equalsIgnoreCase("net.camotoy.bedrockskinutility.client.mixin.AbstractClientPlayer")) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will never be called as the class name is AbstractClientPlayerMixin

boolean capes = FabricLoader.getInstance().getModContainer("capes").isPresent()
|| FabricLoader.getInstance().getModContainer("cosmetica").isPresent();
if (capes) {
Expand Down
Loading