started support for weighted models to support texture randomness. still W.I.P. (textures are normalized)
This commit is contained in:
parent
7ae4858bb4
commit
4315314593
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<Weighted.Present<BakedModel>> getModels();
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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<CacheKey, Mesh> retexturedMeshes = new ConcurrentHashMap<>(); //mutable, append-only cache
|
||||
protected record MeshCacheKey(BlockState state, TransformCacheKey transform) {}
|
||||
protected final ConcurrentMap<MeshCacheKey, Mesh> retextured_meshes = new ConcurrentHashMap<>(); //mutable, append-only cache
|
||||
protected record TransformCacheKey(TemplateAppearance appearance, int model_id) {}
|
||||
protected final ConcurrentMap<TransformCacheKey, RetexturingTransformer> 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<Random> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<BlockState, List<Weighted.Present<Mesh>>> 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<Weighted.Present<Mesh>> 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<Weighted.Present<Mesh>> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<SpriteIdentifier, Sprite> 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<Weighted.Present<Appearance>> 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<BakedQuad> 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<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
|
||||
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]);
|
||||
}
|
||||
}
|
@ -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<Weighted.Present<Appearance>> appearances;
|
||||
private final int total_weight;
|
||||
private final int id;
|
||||
private final RenderMaterial matWithAo;
|
||||
private final RenderMaterial matWithoutAo;
|
||||
|
||||
public WeightedComputedAppearance(@NotNull List<Weighted.Present<Appearance>> 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user