From 58d9687ff8ade0287806c3f13555571b43119a04 Mon Sep 17 00:00:00 2001 From: Adrien1106 Date: Mon, 26 Feb 2024 16:28:02 +0100 Subject: [PATCH] added athena connected texture support (connected blocks do not refresh unless another block is placed next to it) --- build.gradle | 15 ++++ gradle.properties | 4 +- .../client/model/DynamicBakedModel.java | 10 +++ .../client/model/MeshTransformUtil.java | 9 ++- .../reframed/client/model/QuadPosBounds.java | 79 +++++++++++++++++++ .../reframed/client/model/QuadUvBounds.java | 14 ++-- .../client/model/RetexturingBakedModel.java | 65 ++++++++++----- .../client/model/apperance/Appearance.java | 7 +- .../model/apperance/CamoAppearance.java | 6 +- .../apperance/CamoAppearanceManager.java | 62 +++++++-------- .../model/apperance/ComputedAppearance.java | 18 +---- .../apperance/SingleSpriteAppearance.java | 11 +-- .../model/apperance/SpriteProperties.java | 7 ++ .../apperance/WeightedComputedAppearance.java | 17 +--- .../reframed/compat/RebakedAthenaModel.java | 61 ++++++++++++++ .../reframed/mixin/CompatMixinPlugin.java | 55 +++++++++++++ .../mixin/compat/AthenaBakedModelMixin.java | 75 ++++++++++++++++++ .../AthenaConnectedBlockModelMixin.java | 30 +++++++ .../compat/AthenaWrappedGetterMixin.java | 45 +++++++++++ src/main/resources/fabric.mod.json | 3 + src/main/resources/reframed.mixins.json | 43 +++++----- 21 files changed, 510 insertions(+), 126 deletions(-) create mode 100644 src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java create mode 100644 src/main/java/fr/adrien1106/reframed/client/model/QuadPosBounds.java create mode 100644 src/main/java/fr/adrien1106/reframed/client/model/apperance/SpriteProperties.java create mode 100644 src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java create mode 100644 src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java create mode 100644 src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java create mode 100644 src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaConnectedBlockModelMixin.java create mode 100644 src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java diff --git a/build.gradle b/build.gradle index 37b1448..bb5f154 100755 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,12 @@ repositories { name 'altarik-releases' url 'https://repo.altarik.fr/releases/' } + maven { + url "https://maven.teamresourceful.com/repository/maven-public/" + } + maven { + url "https://maven.resourcefulbees.com/repository/maven-public/" + } mavenCentral() // Add repositories to retrieve artifacts from in here. @@ -82,6 +88,14 @@ dependencies { mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + // Athena for connected texture + modCompileOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_version}" + modRuntimeOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_version}" + + // Chipped to test athena implementation + modRuntimeOnly "com.teamresourceful.resourcefullib:resourcefullib-fabric-${project.minecraft_version}:2.4.7" +// modRuntimeOnly "earth.terrarium.chipped:chipped-fabric-${project.minecraft_version}:3.1.2" TODO not working for some reasons + // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" } @@ -91,6 +105,7 @@ processResources { inputs.property "minecraft_version", project.minecraft_version inputs.property "loader_version", project.loader_version inputs.property "mod_id", project.mod_id + inputs.property "athena_version", project.athena_version filteringCharset "UTF-8" filesMatching("fabric.mod.json") { diff --git a/gradle.properties b/gradle.properties index 374e46b..eb6fe2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ yarn_mappings=1.20.4+build.3 loader_version=0.15.6 # Mod Properties -mod_version = 1.0-SNAPSHOT +mod_version = 1.1 maven_group = fr.adrien1106 archives_base_name = ReFramed mod_id = reframed @@ -19,3 +19,5 @@ fabric_version=0.95.4+1.20.4 git_owner=Altarik git_repo=ReFramed + +athena_version=3.3.0 diff --git a/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java b/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java new file mode 100644 index 0000000..a6f0cf5 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java @@ -0,0 +1,10 @@ +package fr.adrien1106.reframed.client.model; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockRenderView; + +public interface DynamicBakedModel { + BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos); +} diff --git a/src/main/java/fr/adrien1106/reframed/client/model/MeshTransformUtil.java b/src/main/java/fr/adrien1106/reframed/client/model/MeshTransformUtil.java index 9ea3c07..768cb27 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/MeshTransformUtil.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/MeshTransformUtil.java @@ -15,13 +15,16 @@ import java.util.EnumMap; import java.util.Map; public class MeshTransformUtil { - public static Mesh pretransformMesh(Mesh mesh, RenderContext.QuadTransform transform) { + public static Mesh pretransformMesh(Mesh mesh, RetexturingBakedModel.RetexturingTransformer transform) { MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder(); QuadEmitter emitter = builder.getEmitter(); mesh.forEach(quad -> { - emitter.copyFrom(quad); - if(transform.transform(emitter)) emitter.emit(); + int i = -1; + do { + emitter.copyFrom(quad); + i = transform.transform(emitter, i); + } while (i > 0); }); return builder.build(); diff --git a/src/main/java/fr/adrien1106/reframed/client/model/QuadPosBounds.java b/src/main/java/fr/adrien1106/reframed/client/model/QuadPosBounds.java new file mode 100644 index 0000000..392a9f4 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/client/model/QuadPosBounds.java @@ -0,0 +1,79 @@ +package fr.adrien1106.reframed.client.model; + +import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; +import org.joml.Vector3f; + +public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z) { + + public static QuadPosBounds read(QuadView quad) { + return read(quad, true); + } + + public static QuadPosBounds read(QuadView quad, boolean check_full) { + float x0 = quad.x(0), x1 = quad.x(1), x2 = quad.x(2), x3 = quad.x(3); + float y0 = quad.y(0), y1 = quad.y(1), y2 = quad.y(2), y3 = quad.y(3); + float z0 = quad.z(0), z1 = quad.z(1), z2 = quad.z(2), z3 = quad.z(3); + + // Checks if the Dimensions are either 0 or 1 except for the Axis dimension + Direction.Axis axis = quad.nominalFace().getAxis(); + if (check_full && (axis == Direction.Axis.X || ( + (MathHelper.approximatelyEquals(x0, 0) || MathHelper.approximatelyEquals(x0, 1)) + && (MathHelper.approximatelyEquals(x1, 0) || MathHelper.approximatelyEquals(x1, 1)) + && (MathHelper.approximatelyEquals(x2, 0) || MathHelper.approximatelyEquals(x2, 1)) + && (MathHelper.approximatelyEquals(x3, 0) || MathHelper.approximatelyEquals(x3, 1)) + )) && (axis == Direction.Axis.Y || ( + (MathHelper.approximatelyEquals(y0, 0) || MathHelper.approximatelyEquals(y0, 1)) + && (MathHelper.approximatelyEquals(y1, 0) || MathHelper.approximatelyEquals(y1, 1)) + && (MathHelper.approximatelyEquals(y2, 0) || MathHelper.approximatelyEquals(y2, 1)) + && (MathHelper.approximatelyEquals(y3, 0) || MathHelper.approximatelyEquals(y3, 1)) + )) & (axis == Direction.Axis.Z || ( + (MathHelper.approximatelyEquals(z0, 0) || MathHelper.approximatelyEquals(z0, 1)) + && (MathHelper.approximatelyEquals(z1, 0) || MathHelper.approximatelyEquals(z1, 1)) + && (MathHelper.approximatelyEquals(z2, 0) || MathHelper.approximatelyEquals(z2, 1)) + && (MathHelper.approximatelyEquals(z3, 0) || MathHelper.approximatelyEquals(z3, 1)) + )) + ) return null; + + return new QuadPosBounds( + Math.min(Math.min(x0, x1), Math.min(x2, x3)), + Math.max(Math.max(x0, x1), Math.max(x2, x3)), + Math.min(Math.min(y0, y1), Math.min(y2, y3)), + Math.max(Math.max(y0, y1), Math.max(y2, y3)), + Math.min(Math.min(z0, z1), Math.min(z2, z3)), + Math.max(Math.max(z0, z1), Math.max(z2, z3)) + ); + } + + public boolean matches(QuadPosBounds other_bounds) { + return !( + (min_x != max_x && (min_x >= other_bounds.max_x || max_x <= other_bounds.min_x)) + || (min_y != max_y && (min_y >= other_bounds.max_y || max_y <= other_bounds.min_y)) + || (min_z != max_z && (min_z >= other_bounds.max_z || max_z <= other_bounds.min_z)) + ); + } + + public QuadPosBounds intersection(QuadPosBounds other_bounds, Direction.Axis axis) { + return new QuadPosBounds( + axis.equals(Direction.Axis.X) ? other_bounds.min_x: Math.max(min_x, other_bounds.min_x), + axis.equals(Direction.Axis.X) ? other_bounds.max_x: Math.min(max_x, other_bounds.max_x), + axis.equals(Direction.Axis.Y) ? other_bounds.min_y: Math.max(min_y, other_bounds.min_y), + axis.equals(Direction.Axis.Y) ? other_bounds.max_y: Math.min(max_y, other_bounds.max_y), + axis.equals(Direction.Axis.Z) ? other_bounds.min_z: Math.max(min_z, other_bounds.min_z), + axis.equals(Direction.Axis.Z) ? other_bounds.max_z: Math.min(max_z, other_bounds.max_z) + ); + } + + public void apply(MutableQuadView quad, QuadPosBounds origin_bounds) { + Vector3f pos = new Vector3f(); + for (int i = 0; i < 4; i++) { + quad.copyPos(i, pos); + pos.x = MathHelper.approximatelyEquals(pos.x, origin_bounds.min_x)? min_x: max_x; + pos.y = MathHelper.approximatelyEquals(pos.y, origin_bounds.min_y)? min_y: max_y; + pos.z = MathHelper.approximatelyEquals(pos.z, origin_bounds.min_z)? min_z: max_z; + quad.pos(i, pos); + } + } +} diff --git a/src/main/java/fr/adrien1106/reframed/client/model/QuadUvBounds.java b/src/main/java/fr/adrien1106/reframed/client/model/QuadUvBounds.java index e95b3da..b06d9cf 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/QuadUvBounds.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/QuadUvBounds.java @@ -5,10 +5,10 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.minecraft.client.texture.Sprite; import net.minecraft.util.math.MathHelper; -record QuadUvBounds(float minU, float maxU, float minV, float maxV) { - static QuadUvBounds read(QuadView quad) { - float u0 = quad.u(0); float u1 = quad.u(1); float u2 = quad.u(2); float u3 = quad.u(3); - float v0 = quad.v(0); float v1 = quad.v(1); float v2 = quad.v(2); float v3 = quad.v(3); +public record QuadUvBounds(float minU, float maxU, float minV, float maxV) { + public static QuadUvBounds read(QuadView quad) { + float u0 = quad.u(0), u1 = quad.u(1), u2 = quad.u(2), u3 = quad.u(3); + float v0 = quad.v(0), v1 = quad.v(1), v2 = quad.v(2), v3 = quad.v(3); return new QuadUvBounds( Math.min(Math.min(u0, u1), Math.min(u2, u3)), Math.max(Math.max(u0, u1), Math.max(u2, u3)), @@ -36,9 +36,5 @@ record QuadUvBounds(float minU, float maxU, float minV, float maxV) { float value2 = MathHelper.clamp(value, low, high); return (value2 - low) / (high - low); } - - //static float rangeRemap(float value, float low1, float high1, float low2, float high2) { - // float value2 = MathHelper.clamp(value, low1, high1); - // return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1); - //} + } diff --git a/src/main/java/fr/adrien1106/reframed/client/model/RetexturingBakedModel.java b/src/main/java/fr/adrien1106/reframed/client/model/RetexturingBakedModel.java index d3ab3b0..8ab222a 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/RetexturingBakedModel.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/RetexturingBakedModel.java @@ -1,6 +1,7 @@ package fr.adrien1106.reframed.client.model; import fr.adrien1106.reframed.block.ReFramedEntity; +import fr.adrien1106.reframed.client.model.apperance.SpriteProperties; import fr.adrien1106.reframed.mixin.MinecraftAccessor; import fr.adrien1106.reframed.client.model.apperance.CamoAppearance; import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager; @@ -22,6 +23,7 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Supplier; @@ -59,12 +61,12 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { @Override public Sprite getParticleSprite() { - return tam.getDefaultAppearance().getSprite(Direction.UP, 0); + return tam.getDefaultAppearance().getSprites(Direction.UP, 0).get(0).sprite(); } @Override - public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - BlockState theme = (blockView.getBlockEntityRenderData(pos) instanceof BlockState s) ? s : null; + public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + BlockState theme = (world.getBlockEntityRenderData(pos) instanceof BlockState s) ? s : null; QuadEmitter quad_emitter = context.getEmitter(); if(theme == null || theme.isAir()) { getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(), 0)), 0).outputTo(quad_emitter); @@ -72,12 +74,12 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { } if(theme.getBlock() == Blocks.BARRIER) return; - CamoAppearance camo = tam.getCamoAppearance(theme); + CamoAppearance camo = tam.getCamoAppearance(world, theme, pos); long seed = theme.getRenderingSeed(pos); int model_id = 0; if (camo instanceof WeightedComputedAppearance wca) model_id = wca.getAppearanceIndex(seed); - int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, blockView, pos, 0); + int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, world, pos, 0); Mesh untintedMesh = getUntintedRetexturedMesh( new MeshCacheKey( state, @@ -105,7 +107,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { int tint; BlockState theme = ReFramedEntity.readStateFromItem(stack); if(!theme.isAir()) { - nbtAppearance = tam.getCamoAppearance(theme); + nbtAppearance = tam.getCamoAppearance(null, theme, null); tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0); } else { nbtAppearance = tam.getDefaultAppearance(); @@ -133,7 +135,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), transformer); } - protected class RetexturingTransformer implements RenderContext.QuadTransform { + public class RetexturingTransformer { private final long seed; protected RetexturingTransformer(CamoAppearance ta, long seed) { this.ta = ta; @@ -141,22 +143,49 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { } protected final CamoAppearance ta; - - @Override - public boolean transform(MutableQuadView quad) { - quad.material(ta.getRenderMaterial(ao)); - + + public int transform(QuadEmitter quad, int i) { int tag = quad.tag(); - if(tag == 0) return true; //Pass the quad through unmodified. - - //The quad tag numbers were selected so this magic trick works: + if(tag == 0) return 0; //Pass the quad through unmodified. + Direction direction = quad.nominalFace(); - quad.spriteBake(ta.getSprite(direction, seed), MutableQuadView.BAKE_NORMALIZED | ta.getBakeFlags(direction, seed) | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0)); - return true; + List sprites = ta.getSprites(direction, seed); + if (i == -1) i = sprites.size(); + + SpriteProperties properties = sprites.get(sprites.size() - i); + i--; + QuadPosBounds bounds = properties.bounds(); + + if (bounds == null) { // sprite applies anywhere e.g. default behaviour + quad.material(ta.getRenderMaterial(ao)); + quad.spriteBake( + properties.sprite(), + MutableQuadView.BAKE_NORMALIZED + | properties.flags() + | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0) + ); + quad.emit(); + return i; + } + + // verify if sprite covers the current quad and apply the new size + QuadPosBounds origin_bounds = QuadPosBounds.read(quad, false); + if (!bounds.matches(origin_bounds)) return i; + + // apply new quad shape + quad.material(ta.getRenderMaterial(ao)); + bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds); + quad.spriteBake( // TODO check if the flags are usefull because it seems to be braking the functioning of it + properties.sprite(), + MutableQuadView.BAKE_NORMALIZED + | MutableQuadView.BAKE_LOCK_UV + ); + quad.emit(); + return i; } } - protected class TintingTransformer implements RenderContext.QuadTransform { + protected static class TintingTransformer implements RenderContext.QuadTransform { private final long seed; protected TintingTransformer(CamoAppearance ta, int tint, long seed) { this.ta = ta; diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/Appearance.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/Appearance.java index 88efc63..c7be281 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/Appearance.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/Appearance.java @@ -1,5 +1,8 @@ package fr.adrien1106.reframed.client.model.apperance; -import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.Direction; -public record Appearance(Sprite[] sprites, int[] flags, byte color_mask) {} +import java.util.List; +import java.util.Map; + +public record Appearance(Map> sprites, byte color_mask) {} diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearance.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearance.java index 312c9d8..b4fe5d1 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearance.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearance.java @@ -1,13 +1,13 @@ package fr.adrien1106.reframed.client.model.apperance; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.minecraft.client.texture.Sprite; import net.minecraft.util.math.Direction; import org.jetbrains.annotations.NotNull; +import java.util.List; + public interface CamoAppearance { @NotNull RenderMaterial getRenderMaterial(boolean ao); - @NotNull Sprite getSprite(Direction dir, long seed); - int getBakeFlags(Direction dir, long seed); + @NotNull List getSprites(Direction dir, long seed); boolean hasColor(Direction dir, long seed); } diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java index 8d0384a..e16038f 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java @@ -2,6 +2,8 @@ package fr.adrien1106.reframed.client.model.apperance; import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.client.ReFramedClient; +import fr.adrien1106.reframed.client.model.DynamicBakedModel; +import fr.adrien1106.reframed.client.model.QuadPosBounds; import fr.adrien1106.reframed.mixin.model.WeightedBakedModelAccessor; import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; @@ -21,13 +23,12 @@ import net.minecraft.client.util.SpriteIdentifier; import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.util.Identifier; import net.minecraft.util.collection.Weighted; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -68,8 +69,14 @@ public class CamoAppearanceManager { return defaultAppearance; } - public CamoAppearance getCamoAppearance(BlockState state) { - return appearanceCache.computeIfAbsent(state, this::computeAppearance); + public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos) { + BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state); + + // add support for connected textures and more generally any compatible models injected so that they return baked quads + if (model instanceof DynamicBakedModel dynamic_model) { + return computeAppearance(dynamic_model.computeQuads(world, state, pos), state); + } + return appearanceCache.computeIfAbsent(state, block_state -> computeAppearance(model, block_state)); } public RenderMaterial getCachedMaterial(BlockState state, boolean ao) { @@ -81,10 +88,9 @@ public class CamoAppearanceManager { // The computeIfAbsent map update will work without corrupting the map, but there will be some "wasted effort" computing the value twice. // The results are going to be the same, apart from their serialNumbers differing (= their equals & hashCode differing). // Tiny amount of wasted space in some caches if CamoAppearances are used as a map key, then. IMO it's not a critical issue. - private CamoAppearance computeAppearance(BlockState state) { + private CamoAppearance computeAppearance(BakedModel model, BlockState state) { if(state.getBlock() == Blocks.BARRIER) return barrierItemAppearance; - BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state); if (!(model instanceof WeightedBakedModelAccessor weighted_model)) { return new ComputedAppearance( getAppearance(model), @@ -112,42 +118,32 @@ public class CamoAppearanceManager { RenderMaterial material = r.materialFinder().clear().find(); Random random = Random.create(); - Sprite[] sprites = new Sprite[6]; - int[] flags = new int[6]; + Map> sprites = new EnumMap<>(Direction.class); byte[] color_mask = {0b000000}; //Read quads off the model by their `cullface` Arrays.stream(Direction.values()).forEach(direction -> { List quads = model.getQuads(null, direction, random); - if(quads.isEmpty() || quads.get(0) == null) { - sprites[direction.ordinal()] = defaultAppearance.getSprite(direction, 0); // make sure direction has a sprite + if(quads.isEmpty()) { // add default appearance if none present + sprites.put(direction, defaultAppearance.getSprites(direction, 0)); return; } - BakedQuad quad = quads.get(0); - if(quad.hasColor()) color_mask[0] |= (byte) (1 << direction.ordinal()); + sprites.put(direction, new ArrayList<>()); + quads.forEach(quad -> { + if(quad.hasColor()) color_mask[0] |= (byte) (1 << direction.ordinal()); - Sprite sprite = quad.getSprite(); - if(sprite == null) return; - sprites[direction.ordinal()] = sprite; - - //Problem: Some models (eg. pistons, stone, glazed terracotta) have their UV coordinates permuted in - //non-standard ways. The actual png image appears sideways, but the original model's UVs rotate it right way up again. - //If I simply display the texture on my model, it will appear sideways, like it does in the png. - //If I can discover the pattern of rotations and flips on the original model, I can "un-rotate" the texture back - //into a standard orientation. - // - //Solution: Look at the first and second vertices of the orignial quad, and decide whether their UV coordinates - //are "low" or "high" compared to the middle of the sprite. The first two vertices uniquely determine the pattern - //of the other two (since UV coordinates are unique and shouldn't cross). There are 16 possibilities so this information - //is easily summarized in a bitfield, and the correct fabric rendering API "bake flags" to un-transform the sprite - //are looked up with a simple table. - quad_emitter.fromVanilla(quad, material, direction); - - flags[direction.ordinal()] = getBakeFlags(quad_emitter, sprite); + Sprite sprite = quad.getSprite(); + if(sprite == null) return; + sprites.compute(direction, (dir, pairs) -> { + quad_emitter.fromVanilla(quad, material, direction); + pairs.add(new SpriteProperties(sprite, getBakeFlags(quad_emitter, sprite), QuadPosBounds.read(quad_emitter))); + return pairs; + }); + }); }); - return new Appearance(sprites, flags, color_mask[0]); + return new Appearance(sprites, color_mask[0]); } private static int getBakeFlags(QuadEmitter emitter, Sprite sprite) { diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/ComputedAppearance.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/ComputedAppearance.java index abfd673..f7f4bef 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/ComputedAppearance.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/ComputedAppearance.java @@ -1,11 +1,10 @@ package fr.adrien1106.reframed.client.model.apperance; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.minecraft.client.texture.Sprite; import net.minecraft.util.math.Direction; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; +import java.util.List; public class ComputedAppearance implements CamoAppearance { private final Appearance appearance; @@ -27,13 +26,8 @@ public class ComputedAppearance implements CamoAppearance { } @Override - public @NotNull Sprite getSprite(Direction dir, long seed) { - return appearance.sprites()[dir.ordinal()]; - } - - @Override - public int getBakeFlags(Direction dir, long seed) { - return appearance.flags()[dir.ordinal()]; + public @NotNull List getSprites(Direction dir, long seed) { + return appearance.sprites().get(dir); } @Override @@ -53,10 +47,4 @@ public class ComputedAppearance implements CamoAppearance { public int hashCode() { return id; } - - @Override - public String toString() { - return "ComputedApperance{sprites=%s, bakeFlags=%s, hasColorMask=%s, matWithoutAo=%s, matWithAo=%s, id=%d}" - .formatted(Arrays.toString(appearance.sprites()), Arrays.toString(appearance.flags()), appearance.color_mask(), matWithoutAo, matWithAo, id); - } } diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/SingleSpriteAppearance.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/SingleSpriteAppearance.java index 73a3a5b..f2fe094 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/SingleSpriteAppearance.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/SingleSpriteAppearance.java @@ -5,6 +5,8 @@ import net.minecraft.client.texture.Sprite; import net.minecraft.util.math.Direction; import org.jetbrains.annotations.NotNull; +import java.util.List; + public class SingleSpriteAppearance implements CamoAppearance { private final @NotNull Sprite defaultSprite; private final RenderMaterial mat; @@ -22,13 +24,8 @@ public class SingleSpriteAppearance implements CamoAppearance { } @Override - public @NotNull Sprite getSprite(Direction dir, long seed) { - return defaultSprite; - } - - @Override - public int getBakeFlags(Direction dir, long seed) { - return 0; + public @NotNull List getSprites(Direction dir, long seed) { + return List.of(new SpriteProperties(defaultSprite, 0, null)); } @Override diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/SpriteProperties.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/SpriteProperties.java new file mode 100644 index 0000000..43c9e5d --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/SpriteProperties.java @@ -0,0 +1,7 @@ +package fr.adrien1106.reframed.client.model.apperance; + +import fr.adrien1106.reframed.client.model.QuadPosBounds; +import net.minecraft.client.texture.Sprite; + +public record SpriteProperties(Sprite sprite, int flags, QuadPosBounds bounds) { +} diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/WeightedComputedAppearance.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/WeightedComputedAppearance.java index 8bd4c4b..098b342 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/WeightedComputedAppearance.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/WeightedComputedAppearance.java @@ -1,14 +1,12 @@ package fr.adrien1106.reframed.client.model.apperance; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.minecraft.client.texture.Sprite; import net.minecraft.util.collection.Weighted; import net.minecraft.util.collection.Weighting; import net.minecraft.util.math.Direction; import net.minecraft.util.math.random.Random; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; import java.util.List; public class WeightedComputedAppearance implements CamoAppearance { @@ -46,14 +44,10 @@ public class WeightedComputedAppearance implements CamoAppearance { } @Override - public @NotNull Sprite getSprite(Direction dir, long seed) { - return getAppearance(seed).sprites()[dir.ordinal()]; + public @NotNull List getSprites(Direction dir, long seed) { + return getAppearance(seed).sprites().get(dir); } - @Override - public int getBakeFlags(Direction dir, long seed) { - return getAppearance(seed).flags()[dir.ordinal()]; - } @Override public boolean hasColor(Direction dir, long seed) { @@ -72,11 +66,4 @@ public class WeightedComputedAppearance implements CamoAppearance { public int hashCode() { return id; } - - @Override - public String toString() { - Appearance appearance = appearances.get(0).getData(); - return "ComputedApperance{sprites=%s, bakeFlags=%s, hasColorMask=%s, matWithoutAo=%s, matWithAo=%s, id=%d}" - .formatted(Arrays.toString(appearance.sprites()), Arrays.toString(appearance.flags()), appearance.color_mask(), matWithoutAo, matWithAo, id); - } } diff --git a/src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java b/src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java new file mode 100644 index 0000000..f0741ac --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java @@ -0,0 +1,61 @@ +package fr.adrien1106.reframed.compat; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelOverrideList; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; + +import java.util.List; +import java.util.Map; + +public class RebakedAthenaModel implements BakedModel { + protected final Map> face_quads; + + public RebakedAthenaModel(Map> face_quads) { + this.face_quads = face_quads; + } + + @Override + public List getQuads(BlockState state, Direction direction, Random random) { + return face_quads.getOrDefault(direction, List.of()); + } + + @Override + public boolean useAmbientOcclusion() { + return false; + } + + @Override + public boolean hasDepth() { + return false; + } + + @Override + public boolean isSideLit() { + return false; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getParticleSprite() { + return null; + } + + @Override + public ModelTransformation getTransformation() { + return null; + } + + @Override + public ModelOverrideList getOverrides() { + return null; + } +} diff --git a/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java b/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java new file mode 100644 index 0000000..c62fda6 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java @@ -0,0 +1,55 @@ +package fr.adrien1106.reframed.mixin; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class CompatMixinPlugin implements IMixinConfigPlugin { + + private static final Map> CONDITIONS = Map.of( + "fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> FabricLoader.getInstance().isModLoaded("athena"), + "fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> FabricLoader.getInstance().isModLoaded("athena") + ); + + + @Override + public void onLoad(String mixin_package) { + + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String target_class, String mixin_class) { + return CONDITIONS.getOrDefault(mixin_class, () -> true).get(); + } + + @Override + public void acceptTargets(Set mine, Set others) { + + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } +} diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java new file mode 100644 index 0000000..b622908 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java @@ -0,0 +1,75 @@ +package fr.adrien1106.reframed.mixin.compat; + +import earth.terrarium.athena.api.client.fabric.AthenaBakedModel; +import earth.terrarium.athena.api.client.fabric.WrappedGetter; +import earth.terrarium.athena.api.client.models.AthenaBlockModel; +import fr.adrien1106.reframed.client.ReFramedClient; +import fr.adrien1106.reframed.client.model.DynamicBakedModel; +import fr.adrien1106.reframed.compat.RebakedAthenaModel; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.fabricmc.fabric.api.renderer.v1.Renderer; +import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.*; + +@Mixin(AthenaBakedModel.class) +public abstract class AthenaBakedModelMixin implements DynamicBakedModel, BakedModel { + + @Shadow @Final private AthenaBlockModel model; + + @Shadow @Final private Int2ObjectMap textures; + + /** + * Reuses the emitQuad method to compute the quads to be used by the frame + * + * @param level - the world + * @param state - the current block camo + * @param pos - the block position + * @return - the rebakedmodel containing the computed quads + */ + @Override + public BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos) { + if (level == null || pos == null) return this; + + Map> face_quads = new HashMap<>(); + + Renderer r = ReFramedClient.HELPER.getFabricRenderer(); + QuadEmitter emitter = r.meshBuilder().getEmitter(); + + WrappedGetter getter = new WrappedGetter(level); + Arrays.stream(Direction.values()).forEach(direction -> { + face_quads.put(direction, new ArrayList<>()); + model.getQuads(getter, state, pos, direction).forEach(sprite -> face_quads.computeIfPresent(direction, (d, quads) -> { + Sprite texture = textures.get(sprite.sprite()); + if (texture == null) return quads; + emitter.square(direction, sprite.left(), sprite.bottom(), sprite.right(), sprite.top(), sprite.depth()); + + int flag = MutableQuadView.BAKE_LOCK_UV; + + switch (sprite.rotation()) { + case CLOCKWISE_90 -> flag |= MutableQuadView.BAKE_ROTATE_90; + case CLOCKWISE_180 -> flag |= MutableQuadView.BAKE_ROTATE_180; + case COUNTERCLOCKWISE_90 -> flag |= MutableQuadView.BAKE_ROTATE_270; + } + + emitter.spriteBake(texture, flag); + emitter.color(-1, -1, -1, -1); + quads.add(emitter.toBakedQuad(texture)); + return quads; + })); + }); + + return new RebakedAthenaModel(face_quads); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaConnectedBlockModelMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaConnectedBlockModelMixin.java new file mode 100644 index 0000000..54742db --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaConnectedBlockModelMixin.java @@ -0,0 +1,30 @@ +package fr.adrien1106.reframed.mixin.compat; + +import earth.terrarium.athena.api.client.utils.AppearanceAndTintGetter; +import earth.terrarium.athena.api.client.utils.CtmUtils; +import earth.terrarium.athena.impl.client.models.ConnectedBlockModel; +import fr.adrien1106.reframed.block.ReFramedEntity; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ConnectedBlockModel.class) +public class AthenaConnectedBlockModelMixin { + + @Redirect(method = "getQuads", + at = @At(value = "INVOKE", target = "Learth/terrarium/athena/api/client/utils/CtmUtils;" + + "checkRelative(Learth/terrarium/athena/api/client/utils/AppearanceAndTintGetter;" + + "Lnet/minecraft/block/BlockState;" + + "Lnet/minecraft/util/math/BlockPos;" + + "Lnet/minecraft/util/math/Direction;)Z")) + private boolean checkForCull(AppearanceAndTintGetter level, BlockState state, BlockPos pos, Direction direction) { + // Always get all the textures unless its another block then use default behaviour + if (level.getBlockEntity(pos) instanceof ReFramedEntity + || level.getBlockEntity(pos.offset(direction)) instanceof ReFramedEntity) + return false; + return CtmUtils.checkRelative(level, state, pos, direction); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java new file mode 100644 index 0000000..24632f3 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java @@ -0,0 +1,45 @@ +package fr.adrien1106.reframed.mixin.compat; + +import earth.terrarium.athena.api.client.fabric.WrappedGetter; +import fr.adrien1106.reframed.block.ReFramedEntity; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockRenderView; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(WrappedGetter.class) +public class AthenaWrappedGetterMixin { + + @Shadow @Final private BlockRenderView getter; + + @Inject(method = "getBlockState", at = @At(value = "HEAD"), cancellable = true) + private void getCamoState(BlockPos pos, CallbackInfoReturnable cir) { + if (!(getter.getBlockEntity(pos) instanceof ReFramedEntity framed_entity)) return; + cir.setReturnValue(framed_entity.getThemeState()); + } + + @Redirect(method = "getAppearance(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)" + + "Lnet/minecraft/block/BlockState;", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/BlockRenderView;" + + "getBlockState(Lnet/minecraft/util/math/BlockPos;)" + + "Lnet/minecraft/block/BlockState;")) + private BlockState appearanceCamoState(BlockRenderView world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof ReFramedEntity framed_entity) return framed_entity.getThemeState(); + return world.getBlockState(pos); + } + + @Redirect(method = "query", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/BlockRenderView;" + + "getBlockState(Lnet/minecraft/util/math/BlockPos;)" + + "Lnet/minecraft/block/BlockState;")) + private BlockState queryCamoState(BlockRenderView world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof ReFramedEntity framed_entity) return framed_entity.getThemeState(); + return world.getBlockState(pos); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6f73f88..bf1592a 100755 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -29,5 +29,8 @@ "minecraft": "${minecraft_version}", "fabricloader": "^${loader_version}", "fabric-api": "*" + }, + "suggest": { + "athena": "^${athena_version}" } } \ No newline at end of file diff --git a/src/main/resources/reframed.mixins.json b/src/main/resources/reframed.mixins.json index 5609390..6af6750 100644 --- a/src/main/resources/reframed.mixins.json +++ b/src/main/resources/reframed.mixins.json @@ -1,23 +1,26 @@ { - "required": true, - "package": "fr.adrien1106.reframed.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - "WallBlockAccessor", - "particles.MixinEntity", - "particles.MixinLivingEntity" - ], - "client": [ - "MinecraftAccessor", - "BlockRenderInfoMixin", - "BlockMixin", - "particles.AccessorParticle", - "particles.AccessorSpriteBillboardParticle", - "particles.MixinBlockDustParticle", - "particles.MixinBlockDustParticle", - "model.WeightedBakedModelAccessor" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "package": "fr.adrien1106.reframed.mixin", + "compatibilityLevel": "JAVA_17", + "plugin": "fr.adrien1106.reframed.mixin.CompatMixinPlugin", + "mixins": [ + "WallBlockAccessor", + "particles.MixinEntity", + "particles.MixinLivingEntity" + ], + "client": [ + "BlockMixin", + "BlockRenderInfoMixin", + "MinecraftAccessor", + "compat.AthenaBakedModelMixin", + "compat.AthenaWrappedGetterMixin", + "compat.AthenaConnectedBlockModelMixin", + "model.WeightedBakedModelAccessor", + "particles.AccessorParticle", + "particles.AccessorSpriteBillboardParticle", + "particles.MixinBlockDustParticle" + ], + "injectors": { + "defaultRequire": 1 } }