From 43153145937d6a135c675c997381c5a3c7c4082b Mon Sep 17 00:00:00 2001 From: Adrien1106 Date: Fri, 9 Feb 2024 18:42:16 +0100 Subject: [PATCH] started support for weighted models to support texture randomness. still W.I.P. (textures are normalized) --- .../reframedtemplates/TemplatesClient.java | 3 - .../TemplatesClientApiImpl.java | 2 +- .../TemplatesModelProvider.java | 2 +- .../api/TemplateInteractionUtil.java | 4 +- .../api/TemplatesClientApi.java | 2 +- .../model/WeightedBakedModelAccessor.java | 16 ++ .../reframedtemplates/model/QuadUvBounds.java | 10 +- .../model/RetexturingBakedModel.java | 85 +++---- .../model/RetexturingWeightedBakedModel.java | 69 ++++++ .../model/TemplateAppearance.java | 14 -- .../model/UnbakedMeshRetexturedModel.java | 1 + .../model/apperance/Appearance.java | 6 + .../model/apperance/ComputedAppearance.java | 62 +++++ .../apperance/SingleSpriteAppearance.java | 56 +++++ .../model/apperance/TemplateAppearance.java | 16 ++ .../TemplateAppearanceManager.java | 229 +++++------------- .../apperance/WeightedComputedAppearance.java | 82 +++++++ .../resources/reframedtemplates.mixins.json | 16 +- 18 files changed, 433 insertions(+), 242 deletions(-) create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/mixin/model/WeightedBakedModelAccessor.java create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingWeightedBakedModel.java delete mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearance.java create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/apperance/Appearance.java create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/apperance/ComputedAppearance.java create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/apperance/SingleSpriteAppearance.java create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearance.java rename src/main/java/fr/adrien1106/reframedtemplates/model/{ => apperance}/TemplateAppearanceManager.java (55%) create mode 100644 src/main/java/fr/adrien1106/reframedtemplates/model/apperance/WeightedComputedAppearance.java diff --git a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClient.java b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClient.java index ffba230..4aace0d 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClient.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClient.java @@ -108,9 +108,6 @@ public class TemplatesClient implements ClientModInitializer { api.assignItemModel(Templates.id("wall_inventory_special") , Templates.WALL); api.assignItemModel(Templates.id("slope_special") , Templates.SLOPE); api.assignItemModel(Templates.id("tiny_slope_special") , Templates.TINY_SLOPE); - - //TODO: i could stick some kind of entrypoint here for signalling other mods that it's ok to register now? - // Dont think it rly matters though, everything's all kept in nice hash maps } private void privateInit() { diff --git a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClientApiImpl.java b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClientApiImpl.java index 7ece4a4..e2b2cdb 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClientApiImpl.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesClientApiImpl.java @@ -1,7 +1,7 @@ package fr.adrien1106.reframedtemplates; import fr.adrien1106.reframedtemplates.api.TemplatesClientApi; -import fr.adrien1106.reframedtemplates.model.TemplateAppearanceManager; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearanceManager; import fr.adrien1106.reframedtemplates.model.UnbakedAutoRetexturedModel; import fr.adrien1106.reframedtemplates.model.UnbakedJsonRetexturedModel; import fr.adrien1106.reframedtemplates.model.UnbakedMeshRetexturedModel; diff --git a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesModelProvider.java b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesModelProvider.java index 3f95577..830c2ba 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/TemplatesModelProvider.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/TemplatesModelProvider.java @@ -1,6 +1,6 @@ package fr.adrien1106.reframedtemplates; -import fr.adrien1106.reframedtemplates.model.TemplateAppearanceManager; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearanceManager; import net.fabricmc.fabric.api.client.model.ModelProviderContext; import net.fabricmc.fabric.api.client.model.ModelResourceProvider; import net.fabricmc.fabric.api.client.model.ModelVariantProvider; diff --git a/src/main/java/fr/adrien1106/reframedtemplates/api/TemplateInteractionUtil.java b/src/main/java/fr/adrien1106/reframedtemplates/api/TemplateInteractionUtil.java index 33bf60f..2324ac9 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/api/TemplateInteractionUtil.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/api/TemplateInteractionUtil.java @@ -70,7 +70,7 @@ public class TemplateInteractionUtil { ItemStack held = player.getStackInHand(hand); TemplateInteractionUtilExt ext = state.getBlock() instanceof TemplateInteractionUtilExt e ? e : TemplateInteractionUtilExt.Default.INSTANCE; - + //Glowstone if(state.contains(LIGHT) && held.getItem() == Items.GLOWSTONE_DUST && !state.get(LIGHT) && !be.hasSpentGlowstoneDust()) { world.setBlockState(pos, state.with(LIGHT, true)); @@ -113,7 +113,7 @@ public class TemplateInteractionUtil { if(held.getItem() instanceof BlockItem bi && be.getThemeState().getBlock() == Blocks.AIR) { Block block = bi.getBlock(); ItemPlacementContext ctx = new ItemPlacementContext(new ItemUsageContext(player, hand, hit)); - BlockState placementState = block.getPlacementState(ctx); // TODO Smart + BlockState placementState = block.getPlacementState(ctx); if(placementState != null && Block.isShapeFullCube(placementState.getCollisionShape(world, pos)) && !(block instanceof BlockEntityProvider)) { if(!world.isClient) be.setRenderedState(placementState); diff --git a/src/main/java/fr/adrien1106/reframedtemplates/api/TemplatesClientApi.java b/src/main/java/fr/adrien1106/reframedtemplates/api/TemplatesClientApi.java index 3b92db7..f3c12c1 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/api/TemplatesClientApi.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/api/TemplatesClientApi.java @@ -1,6 +1,6 @@ package fr.adrien1106.reframedtemplates.api; -import fr.adrien1106.reframedtemplates.model.TemplateAppearanceManager; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearanceManager; import fr.adrien1106.reframedtemplates.TemplatesClient; import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; diff --git a/src/main/java/fr/adrien1106/reframedtemplates/mixin/model/WeightedBakedModelAccessor.java b/src/main/java/fr/adrien1106/reframedtemplates/mixin/model/WeightedBakedModelAccessor.java new file mode 100644 index 0000000..2706e0b --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/mixin/model/WeightedBakedModelAccessor.java @@ -0,0 +1,16 @@ +package fr.adrien1106.reframedtemplates.mixin.model; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.WeightedBakedModel; +import net.minecraft.util.collection.Weighted; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(WeightedBakedModel.class) +public interface WeightedBakedModelAccessor { + + @Accessor("models") List> getModels(); + +} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/QuadUvBounds.java b/src/main/java/fr/adrien1106/reframedtemplates/model/QuadUvBounds.java index 6f0cd17..473fa91 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/model/QuadUvBounds.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/QuadUvBounds.java @@ -21,11 +21,11 @@ record QuadUvBounds(float minU, float maxU, float minV, float maxV) { return sprite.getMinU() <= minU && sprite.getMaxU() >= maxU && sprite.getMinV() <= minV && sprite.getMaxV() >= maxV; } - void normalizeUv(MutableQuadView quad, Sprite specialSprite) { - float remappedMinU = norm(minU, specialSprite.getMinU(), specialSprite.getMaxU()); - float remappedMaxU = norm(maxU, specialSprite.getMinU(), specialSprite.getMaxU()); - float remappedMinV = norm(minV, specialSprite.getMinV(), specialSprite.getMaxV()); - float remappedMaxV = norm(maxV, specialSprite.getMinV(), specialSprite.getMaxV()); + void normalizeUv(MutableQuadView quad, Sprite sprite) { + float remappedMinU = norm(minU, sprite.getMinU(), sprite.getMaxU()); + float remappedMaxU = norm(maxU, sprite.getMinU(), sprite.getMaxU()); + float remappedMinV = norm(minV, sprite.getMinV(), sprite.getMaxV()); + float remappedMaxV = norm(maxV, sprite.getMinV(), sprite.getMaxV()); quad.uv(0, MathHelper.approximatelyEquals(quad.u(0), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(0), minV) ? remappedMinV : remappedMaxV); quad.uv(1, MathHelper.approximatelyEquals(quad.u(1), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(1), minV) ? remappedMinV : remappedMaxV); quad.uv(2, MathHelper.approximatelyEquals(quad.u(2), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(2), minV) ? remappedMinV : remappedMaxV); diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingBakedModel.java b/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingBakedModel.java index 8855665..751acfe 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingBakedModel.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingBakedModel.java @@ -2,11 +2,14 @@ package fr.adrien1106.reframedtemplates.model; import fr.adrien1106.reframedtemplates.block.TemplateEntity; import fr.adrien1106.reframedtemplates.mixin.MinecraftAccessor; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearance; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearanceManager; +import fr.adrien1106.reframedtemplates.model.apperance.WeightedComputedAppearance; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; @@ -41,8 +44,10 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { protected final BlockState itemModelState; protected final boolean ao; - protected record CacheKey(BlockState state, TemplateAppearance appearance) {} - protected final ConcurrentMap retexturedMeshes = new ConcurrentHashMap<>(); //mutable, append-only cache + protected record MeshCacheKey(BlockState state, TransformCacheKey transform) {} + protected final ConcurrentMap retextured_meshes = new ConcurrentHashMap<>(); //mutable, append-only cache + protected record TransformCacheKey(TemplateAppearance appearance, int model_id) {} + protected final ConcurrentMap retextured_transforms = new ConcurrentHashMap<>(); protected static final Direction[] DIRECTIONS = Direction.values(); protected static final Direction[] DIRECTIONS_AND_NULL = new Direction[DIRECTIONS.length + 1]; @@ -57,32 +62,34 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { @Override public Sprite getParticleSprite() { - return tam.getDefaultAppearance().getSprite(Direction.UP); + return tam.getDefaultAppearance().getSprite(Direction.UP, 0); } @Override public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - BlockState theme = (((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos) instanceof BlockState s) ? s : null; + BlockState theme = (blockView.getBlockEntityRenderData(pos) instanceof BlockState s) ? s : null; + QuadEmitter quad_emitter = context.getEmitter(); if(theme == null || theme.isAir()) { - context.meshConsumer().accept(getUntintedRetexturedMesh(new CacheKey(state, tam.getDefaultAppearance()))); - return; - } else if(theme.getBlock() == Blocks.BARRIER) { - //TODO i don't love putting this rare specialcase smack in the middle of the hot code path + getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(), 0)), 0).outputTo(quad_emitter); return; } + if(theme.getBlock() == Blocks.BARRIER) return; - TemplateAppearance ta = tam.getAppearance(theme); + TemplateAppearance ta = tam.getTemplateAppearance(theme); + long seed = theme.getRenderingSeed(pos); + int model_id = 0; + if (ta instanceof WeightedComputedAppearance wca) model_id = wca.getAppearanceIndex(seed); int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, blockView, pos, 0); - Mesh untintedMesh = getUntintedRetexturedMesh(new CacheKey(state, ta)); + Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(ta, model_id)), seed); //The specific tint might vary a lot; imagine grass color smoothly changing. Trying to bake the tint into - //the cached mesh will pollute it with a ton of single-use meshes with only slighly different colors. + //the cached mesh will pollute it with a ton of single-use meshes with only slightly different colors. if(tint == 0xFFFFFFFF) { - context.meshConsumer().accept(untintedMesh); + untintedMesh.outputTo(quad_emitter); } else { - context.pushTransform(new TintingTransformer(ta, tint)); - context.meshConsumer().accept(untintedMesh); + context.pushTransform(new TintingTransformer(ta, tint, seed)); + untintedMesh.outputTo(quad_emitter); context.popTransform(); } } @@ -95,40 +102,39 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { int tint; BlockState theme = TemplateEntity.readStateFromItem(stack); if(!theme.isAir()) { - nbtAppearance = tam.getAppearance(theme); + nbtAppearance = tam.getTemplateAppearance(theme); tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).templates$getItemColors().getColor(new ItemStack(theme.getBlock()), 0); } else { nbtAppearance = tam.getDefaultAppearance(); tint = 0xFFFFFFFF; } - Mesh untintedMesh = getUntintedRetexturedMesh(new CacheKey(itemModelState, nbtAppearance)); - + Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(itemModelState, new TransformCacheKey(nbtAppearance, 0)), 0); + + QuadEmitter quad_emitter = context.getEmitter(); if(tint == 0xFFFFFFFF) { - context.meshConsumer().accept(untintedMesh); + untintedMesh.outputTo(quad_emitter); } else { - context.pushTransform(new TintingTransformer(nbtAppearance, tint)); - context.meshConsumer().accept(untintedMesh); + context.pushTransform(new TintingTransformer(nbtAppearance, tint, 0)); + untintedMesh.outputTo(quad_emitter); context.popTransform(); } } - protected Mesh getUntintedRetexturedMesh(CacheKey key) { - return retexturedMeshes.computeIfAbsent(key, this::createUntintedRetexturedMesh); + protected Mesh getUntintedRetexturedMesh(MeshCacheKey key, long seed) { + return retextured_meshes.computeIfAbsent(key, (k) -> createUntintedRetexturedMesh(k, seed)); } - protected Mesh createUntintedRetexturedMesh(CacheKey key) { - return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), new RetexturingTransformer(key.appearance)); + protected Mesh createUntintedRetexturedMesh(MeshCacheKey key, long seed) { + RetexturingTransformer transformer = retextured_transforms.computeIfAbsent(key.transform, (k) -> new RetexturingTransformer(k.appearance, seed)); + return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), transformer); } protected class RetexturingTransformer implements RenderContext.QuadTransform { - protected RetexturingTransformer(TemplateAppearance ta) { + private final long seed; + protected RetexturingTransformer(TemplateAppearance ta, long seed) { this.ta = ta; - } - - @Deprecated(forRemoval = true) //TODO ABI: Deprecated in 2.2. Use TintingTransformer for retinting, it works better anyway - protected RetexturingTransformer(TemplateAppearance ta, int ignoredTint) { - this(ta); + this.seed = seed; } protected final TemplateAppearance ta; @@ -141,17 +147,18 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { if(tag == 0) return true; //Pass the quad through unmodified. //The quad tag numbers were selected so this magic trick works: - Direction dir = facePermutation.get(DIRECTIONS[quad.tag() - 1]); - quad.spriteBake(ta.getSprite(dir), MutableQuadView.BAKE_NORMALIZED | ta.getBakeFlags(dir) | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0)); - + Direction direction = facePermutation.get(DIRECTIONS[quad.tag() - 1]); + quad.spriteBake(ta.getSprite(direction, seed), MutableQuadView.BAKE_NORMALIZED | ta.getBakeFlags(direction, seed) | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0)); return true; } } protected class TintingTransformer implements RenderContext.QuadTransform { - protected TintingTransformer(TemplateAppearance ta, int tint) { + private final long seed; + protected TintingTransformer(TemplateAppearance ta, int tint, long seed) { this.ta = ta; this.tint = tint; + this.seed = seed; } protected final TemplateAppearance ta; @@ -163,15 +170,9 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel { if(tag == 0) return true; Direction dir = facePermutation.get(DIRECTIONS[quad.tag() - 1]); - if(ta.hasColor(dir)) quad.color(tint, tint, tint, tint); + if(ta.hasColor(dir, seed)) quad.color(tint, tint, tint, tint); return true; } } - - //TODO ABI: From before there was an AO boolean, <2.1.1. - @Deprecated(forRemoval = true) - public RetexturingBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, BlockState itemModelState) { - this(baseModel, tam, settings, itemModelState, true); - } } diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingWeightedBakedModel.java b/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingWeightedBakedModel.java new file mode 100644 index 0000000..87449b2 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/RetexturingWeightedBakedModel.java @@ -0,0 +1,69 @@ +package fr.adrien1106.reframedtemplates.model; + +import fr.adrien1106.reframedtemplates.api.TemplatesClientApi; +import fr.adrien1106.reframedtemplates.model.apperance.TemplateAppearanceManager; +import net.fabricmc.fabric.api.renderer.v1.Renderer; +import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; +import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; +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.render.model.ModelBakeSettings; +import net.minecraft.util.collection.Weighted; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class RetexturingWeightedBakedModel extends RetexturingBakedModel { + + final ConcurrentMap>> jsonToMesh = new ConcurrentHashMap<>(); + + public RetexturingWeightedBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, BlockState itemModelState, boolean ao) { + super(baseModel, tam, settings, itemModelState, ao); + } + + @Override + protected Mesh getBaseMesh(BlockState state) { + return null; +// random.setSeed(seed); +// List> models = jsonToMesh.computeIfAbsent(state, this::convertModel); +// return Weighting +// .getAt(models, Math.abs((int)random.nextLong()) % Weighting.getWeightSum(models)) +// .map(Weighted.Present::getData).get(); + } + + private List> convertModel(BlockState state) { + Renderer r = TemplatesClientApi.getInstance().getFabricRenderer(); + MeshBuilder builder = r.meshBuilder(); + QuadEmitter emitter = builder.getEmitter(); + RenderMaterial mat = tam.getCachedMaterial(state, false); + + Random rand = Random.create(42); + + for(Direction cullFace : DIRECTIONS_AND_NULL) { + for(BakedQuad quad : wrapped.getQuads(state, cullFace, rand)) { + emitter.fromVanilla(quad, mat, cullFace); + + QuadUvBounds bounds = QuadUvBounds.read(emitter); +// for(int i = 0; i < specialSprites.length; i++) { +// if(bounds.displaysSprite(specialSprites[i])) { +// bounds.normalizeUv(emitter, specialSprites[i]); +// emitter.tag(i + 1); +// break; +// } +// } + + emitter.emit(); + } + } + +// builder.build(); + return null; + } + +} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearance.java deleted file mode 100644 index 070c0e9..0000000 --- a/src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearance.java +++ /dev/null @@ -1,14 +0,0 @@ -package fr.adrien1106.reframedtemplates.model; - -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; - -//TODO ABI: move to the api package -public interface TemplateAppearance { - @NotNull RenderMaterial getRenderMaterial(boolean ao); - @NotNull Sprite getSprite(Direction dir); - int getBakeFlags(Direction dir); - boolean hasColor(Direction dir); -} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/UnbakedMeshRetexturedModel.java b/src/main/java/fr/adrien1106/reframedtemplates/model/UnbakedMeshRetexturedModel.java index 8fb5958..247a98e 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/model/UnbakedMeshRetexturedModel.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/UnbakedMeshRetexturedModel.java @@ -11,6 +11,7 @@ import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.texture.Sprite; import net.minecraft.client.util.SpriteIdentifier; import net.minecraft.util.Identifier; +import net.minecraft.util.math.random.Random; import java.util.Collection; import java.util.Collections; diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/Appearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/Appearance.java new file mode 100644 index 0000000..5a93ea9 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/Appearance.java @@ -0,0 +1,6 @@ +package fr.adrien1106.reframedtemplates.model.apperance; + +import net.minecraft.client.texture.Sprite; + +record Appearance(Sprite[] sprites, int[] flags, byte color_mask) { +} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/ComputedAppearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/ComputedAppearance.java new file mode 100644 index 0000000..4a6543f --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/ComputedAppearance.java @@ -0,0 +1,62 @@ +package fr.adrien1106.reframedtemplates.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; + +public class ComputedAppearance implements TemplateAppearance { + private final Appearance appearance; + private final int id; + private final RenderMaterial matWithAo; + private final RenderMaterial matWithoutAo; + + public ComputedAppearance(@NotNull Appearance appearance, RenderMaterial withAo, RenderMaterial withoutAo, int id) { + this.appearance = appearance; + this.id = id; + + this.matWithAo = withAo; + this.matWithoutAo = withoutAo; + } + + @Override + public @NotNull RenderMaterial getRenderMaterial(boolean ao) { + return ao ? matWithAo : matWithoutAo; + } + + @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()]; + } + + @Override + public boolean hasColor(Direction dir, long seed) { + return (appearance.color_mask() & (1 << dir.ordinal())) != 0; + } + + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + ComputedAppearance that = (ComputedAppearance) o; + return id == that.id; + } + + @Override + 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/reframedtemplates/model/apperance/SingleSpriteAppearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/SingleSpriteAppearance.java new file mode 100644 index 0000000..468d88d --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/SingleSpriteAppearance.java @@ -0,0 +1,56 @@ +package fr.adrien1106.reframedtemplates.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; + +public class SingleSpriteAppearance implements TemplateAppearance { + private final @NotNull Sprite defaultSprite; + private final RenderMaterial mat; + private final int id; + + public SingleSpriteAppearance(@NotNull Sprite defaultSprite, RenderMaterial mat, int id) { + this.defaultSprite = defaultSprite; + this.mat = mat; + this.id = id; + } + + @Override + public @NotNull RenderMaterial getRenderMaterial(boolean ao) { + return mat; + } + + @Override + public @NotNull Sprite getSprite(Direction dir, long seed) { + return defaultSprite; + } + + @Override + public int getBakeFlags(Direction dir, long seed) { + return 0; + } + + @Override + public boolean hasColor(Direction dir, long seed) { + return false; + } + + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + SingleSpriteAppearance that = (SingleSpriteAppearance) o; + return id == that.id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, mat, id); + } +} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearance.java new file mode 100644 index 0000000..d25aba0 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearance.java @@ -0,0 +1,16 @@ +package fr.adrien1106.reframedtemplates.model.apperance; + +import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface TemplateAppearance { + @NotNull RenderMaterial getRenderMaterial(boolean ao); + @NotNull Sprite getSprite(Direction dir, @NotNull long seed); + int getBakeFlags(Direction dir, @NotNull long seed); + boolean hasColor(Direction dir, @NotNull long seed); +} diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearanceManager.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearanceManager.java similarity index 55% rename from src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearanceManager.java rename to src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearanceManager.java index 815ec05..690066e 100644 --- a/src/main/java/fr/adrien1106/reframedtemplates/model/TemplateAppearanceManager.java +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/TemplateAppearanceManager.java @@ -1,6 +1,7 @@ -package fr.adrien1106.reframedtemplates.model; +package fr.adrien1106.reframedtemplates.model.apperance; import fr.adrien1106.reframedtemplates.api.TemplatesClientApi; +import fr.adrien1106.reframedtemplates.mixin.model.WeightedBakedModelAccessor; import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; @@ -14,25 +15,22 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderLayers; import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelTransformation; import net.minecraft.client.texture.Sprite; 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.Direction; import net.minecraft.util.math.random.Random; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -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; -//TODO: extract an API for the api package public class TemplateAppearanceManager { + public TemplateAppearanceManager(Function spriteLookup) { MaterialFinder finder = TemplatesClientApi.getInstance().getFabricRenderer().materialFinder(); for(BlendMode blend : BlendMode.values()) { @@ -50,10 +48,8 @@ public class TemplateAppearanceManager { if(barrier == null) barrier = defaultSprite; //eh this.barrierItemAppearance = new SingleSpriteAppearance(barrier, materialsWithoutAo.get(BlendMode.CUTOUT), serialNumber.getAndIncrement()); } - - //TODO ABI: Shouldn't have been made public. Noticed this in 2.2. - @ApiStatus.Internal - public static final SpriteIdentifier DEFAULT_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("reframedtemplates:block/framed_block")); + + protected static final SpriteIdentifier DEFAULT_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("reframedtemplates:block/framed_block")); private static final SpriteIdentifier BARRIER_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:item/barrier")); private final TemplateAppearance defaultAppearance; @@ -69,7 +65,7 @@ public class TemplateAppearanceManager { return defaultAppearance; } - public TemplateAppearance getAppearance(BlockState state) { + public TemplateAppearance getTemplateAppearance(BlockState state) { return appearanceCache.computeIfAbsent(state, this::computeAppearance); } @@ -95,33 +91,54 @@ public class TemplateAppearanceManager { //Tiny amount of wasted space in some caches if TemplateAppearances are used as a map key, then. IMO it's not a critical issue. private TemplateAppearance computeAppearance(BlockState state) { if(state.getBlock() == Blocks.BARRIER) return barrierItemAppearance; - - Random rand = Random.create(42); + BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state); - + if (!(model instanceof WeightedBakedModelAccessor weighted_model)) { + return new ComputedAppearance( + getAppearance(model), + getCachedMaterial(state, true), + getCachedMaterial(state, false), + serialNumber.getAndIncrement() + ); + } + List> appearances = weighted_model.getModels().stream() + .map(baked_model -> Weighted.of(getAppearance(baked_model.getData()), baked_model.getWeight().getValue())) + .toList(); + + return new WeightedComputedAppearance( + appearances, + getCachedMaterial(state, true), + getCachedMaterial(state, false), + serialNumber.getAndIncrement() + ); + } + + private Appearance getAppearance(BakedModel model) { //Only for parsing vanilla quads: Renderer r = TemplatesClientApi.getInstance().getFabricRenderer(); - QuadEmitter emitterAsParser = r.meshBuilder().getEmitter(); - RenderMaterial defaultMat = r.materialFinder().clear().find(); - + QuadEmitter quad_emitter = r.meshBuilder().getEmitter(); + RenderMaterial material = r.materialFinder().clear().find(); + Random random = Random.create(); + Sprite[] sprites = new Sprite[6]; - int[] bakeFlags = new int[6]; - byte hasColorMask = 0b000000; - + int[] flags = new int[6]; + byte[] color_mask = {0b000000}; + //Read quads off the model by their `cullface` - for(Direction dir : Direction.values()) { - List sideQuads = model.getQuads(null, dir, rand); - if(sideQuads.isEmpty()) continue; - - BakedQuad arbitraryQuad = sideQuads.get(0); //TODO: maybe pick a largest quad instead? - if(arbitraryQuad == null) continue; - - if(arbitraryQuad.hasColor()) hasColorMask |= (byte) (1 << dir.ordinal()); - - Sprite sprite = arbitraryQuad.getSprite(); - if(sprite == null) continue; - sprites[dir.ordinal()] = sprite; - + 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 + return; + } + + BakedQuad quad = quads.get(0); + 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. @@ -133,139 +150,19 @@ public class TemplateAppearanceManager { //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. - emitterAsParser.fromVanilla(arbitraryQuad, defaultMat, dir); - + quad_emitter.fromVanilla(quad, material, direction); + float spriteUAvg = (sprite.getMinU() + sprite.getMaxU()) / 2; float spriteVAvg = (sprite.getMinV() + sprite.getMaxV()) / 2; - - bakeFlags[dir.ordinal()] = MAGIC_BAKEFLAGS_SBOX[ - (emitterAsParser.u(0) < spriteUAvg ? 8 : 0) | - (emitterAsParser.v(0) < spriteVAvg ? 4 : 0) | - (emitterAsParser.u(1) < spriteUAvg ? 2 : 0) | - (emitterAsParser.v(1) < spriteVAvg ? 1 : 0) + + flags[direction.ordinal()] = MAGIC_BAKEFLAGS_SBOX[ + (quad_emitter.u(0) < spriteUAvg ? 8 : 0) | + (quad_emitter.v(0) < spriteVAvg ? 4 : 0) | + (quad_emitter.u(1) < spriteUAvg ? 2 : 0) | + (quad_emitter.v(1) < spriteVAvg ? 1 : 0) ]; - } - - //Fill out any missing values in the sprites array, since failure to pick textures shouldn't lead to NPEs later on - for(int i = 0; i < sprites.length; i++) { - if(sprites[i] == null) sprites[i] = defaultAppearance.getSprite(Direction.byId(i)); - } - - return new ComputedApperance( - sprites, - bakeFlags, - hasColorMask, - getCachedMaterial(state, true), - getCachedMaterial(state, false), - serialNumber.getAndIncrement() - ); - } - - private static final class ComputedApperance implements TemplateAppearance { - private final Sprite @NotNull[] sprites; - private final int @NotNull[] bakeFlags; - private final byte hasColorMask; - private final int id; - private final RenderMaterial matWithAo; - private final RenderMaterial matWithoutAo; - - private ComputedApperance(@NotNull Sprite @NotNull[] sprites, int @NotNull[] bakeFlags, byte hasColorMask, RenderMaterial withAo, RenderMaterial withoutAo, int id) { - this.sprites = sprites; - this.bakeFlags = bakeFlags; - this.hasColorMask = hasColorMask; - this.id = id; - - this.matWithAo = withAo; - this.matWithoutAo = withoutAo; - } - - @Override - public @NotNull RenderMaterial getRenderMaterial(boolean ao) { - return ao ? matWithAo : matWithoutAo; - } - - @Override - public @NotNull Sprite getSprite(Direction dir) { - return sprites[dir.ordinal()]; - } - - @Override - public int getBakeFlags(Direction dir) { - return bakeFlags[dir.ordinal()]; - } - - @Override - public boolean hasColor(Direction dir) { - return (hasColorMask & (1 << dir.ordinal())) != 0; - } - - @Override - public boolean equals(Object o) { - if(this == o) return true; - if(o == null || getClass() != o.getClass()) return false; - ComputedApperance that = (ComputedApperance) o; - return id == that.id; - } - - @Override - 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(sprites), Arrays.toString(bakeFlags), hasColorMask, matWithoutAo, matWithAo, id); - } - } - - @SuppressWarnings("ClassCanBeRecord") - private static final class SingleSpriteAppearance implements TemplateAppearance { - private final @NotNull Sprite defaultSprite; - private final RenderMaterial mat; - private final int id; - - private SingleSpriteAppearance(@NotNull Sprite defaultSprite, RenderMaterial mat, int id) { - this.defaultSprite = defaultSprite; - this.mat = mat; - this.id = id; - } - - @Override - public @NotNull RenderMaterial getRenderMaterial(boolean ao) { - return mat; - } - - @Override - public @NotNull Sprite getSprite(Direction dir) { - return defaultSprite; - } - - @Override - public int getBakeFlags(Direction dir) { - return 0; - } - - @Override - public boolean hasColor(Direction dir) { - return false; - } - - @Override - public boolean equals(Object o) { - if(this == o) return true; - if(o == null || getClass() != o.getClass()) return false; - SingleSpriteAppearance that = (SingleSpriteAppearance) o; - return id == that.id; - } - - @Override - public int hashCode() { - return id; - } - - @Override - public String toString() { - return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, mat, id); - } + }); + + return new Appearance(sprites, flags, color_mask[0]); } } diff --git a/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/WeightedComputedAppearance.java b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/WeightedComputedAppearance.java new file mode 100644 index 0000000..f8f1f93 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframedtemplates/model/apperance/WeightedComputedAppearance.java @@ -0,0 +1,82 @@ +package fr.adrien1106.reframedtemplates.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 TemplateAppearance { + private final List> appearances; + private final int total_weight; + private final int id; + private final RenderMaterial matWithAo; + private final RenderMaterial matWithoutAo; + + public WeightedComputedAppearance(@NotNull List> appearances, RenderMaterial withAo, RenderMaterial withoutAo, int id) { + this.appearances = appearances; + this.total_weight = Weighting.getWeightSum(appearances); + this.id = id; + + this.matWithAo = withAo; + this.matWithoutAo = withoutAo; + } + + @Override + public @NotNull RenderMaterial getRenderMaterial(boolean ao) { + return ao ? matWithAo : matWithoutAo; + } + + + public int getAppearanceIndex(long seed) { + Random random = Random.create(seed); + return Weighting.getAt(appearances, Math.abs((int)random.nextLong()) % total_weight) + .map(appearances::indexOf).get(); + } + + private Appearance getAppearance(long seed) { + Random random = Random.create(seed); + return Weighting.getAt(appearances, Math.abs((int)random.nextLong()) % total_weight) + .map(Weighted.Present::getData).get(); + } + + @Override + public @NotNull Sprite getSprite(Direction dir, long seed) { + return getAppearance(seed).sprites()[dir.ordinal()]; + } + + @Override + public int getBakeFlags(Direction dir, long seed) { + return getAppearance(seed).flags()[dir.ordinal()]; + } + + @Override + public boolean hasColor(Direction dir, long seed) { + return (getAppearance(seed).color_mask() & (1 << dir.ordinal())) != 0; + } + + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + WeightedComputedAppearance that = (WeightedComputedAppearance) o; + return id == that.id; + } + + @Override + 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/resources/reframedtemplates.mixins.json b/src/main/resources/reframedtemplates.mixins.json index 26045a3..aa2e76b 100644 --- a/src/main/resources/reframedtemplates.mixins.json +++ b/src/main/resources/reframedtemplates.mixins.json @@ -3,15 +3,17 @@ "package": "fr.adrien1106.reframedtemplates.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ - "MinecraftAccessor", - "WallBlockAccessor", - "particles.MixinEntity", - "particles.MixinLivingEntity" + "MinecraftAccessor", + "WallBlockAccessor", + "particles.MixinEntity", + "particles.MixinLivingEntity" ], "client": [ - "particles.AccessorParticle", - "particles.AccessorSpriteBillboardParticle", - "particles.MixinBlockDustParticle" + "particles.AccessorParticle", + "particles.AccessorSpriteBillboardParticle", + "particles.MixinBlockDustParticle", + "particles.MixinBlockDustParticle", + "model.WeightedBakedModelAccessor" ], "injectors": { "defaultRequire": 1