Athena support + stair improvements + coloration #5

Merged
Adrien1106 merged 4 commits from dev into master 2024-02-26 17:05:24 +01:00
21 changed files with 510 additions and 126 deletions
Showing only changes of commit 58d9687ff8 - Show all commits

View File

@ -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") {

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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)),
@ -37,8 +37,4 @@ record QuadUvBounds(float minU, float maxU, float minV, float maxV) {
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);
//}
}

View File

@ -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<Random> randomSupplier, RenderContext context) {
BlockState theme = (blockView.getBlockEntityRenderData(pos) instanceof BlockState s) ? s : null;
public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> 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;
@ -142,21 +144,48 @@ 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.
if(tag == 0) return 0; //Pass the quad through unmodified.
//The quad tag numbers were selected so this magic trick works:
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<SpriteProperties> 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;

View File

@ -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<Direction, List<SpriteProperties>> sprites, byte color_mask) {}

View File

@ -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<SpriteProperties> getSprites(Direction dir, long seed);
boolean hasColor(Direction dir, long seed);
}

View File

@ -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<Direction, List<SpriteProperties>> sprites = new EnumMap<>(Direction.class);
byte[] color_mask = {0b000000};
//Read quads off the model by their `cullface`
Arrays.stream(Direction.values()).forEach(direction -> {
List<BakedQuad> 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) {

View File

@ -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<SpriteProperties> 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);
}
}

View File

@ -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<SpriteProperties> getSprites(Direction dir, long seed) {
return List.of(new SpriteProperties(defaultSprite, 0, null));
}
@Override

View File

@ -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) {
}

View File

@ -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<SpriteProperties> 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);
}
}

View File

@ -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<Direction, List<BakedQuad>> face_quads;
public RebakedAthenaModel(Map<Direction, List<BakedQuad>> face_quads) {
this.face_quads = face_quads;
}
@Override
public List<BakedQuad> 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;
}
}

View File

@ -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<String, Supplier<Boolean>> 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<String> mine, Set<String> others) {
}
@Override
public List<String> 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) {
}
}

View File

@ -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<Sprite> 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<Direction, List<BakedQuad>> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<BlockState> 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);
}
}

View File

@ -29,5 +29,8 @@
"minecraft": "${minecraft_version}",
"fabricloader": "^${loader_version}",
"fabric-api": "*"
},
"suggest": {
"athena": "^${athena_version}"
}
}

View File

@ -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
}
}