171 lines
6.5 KiB
Java
171 lines
6.5 KiB
Java
package io.github.cottonmc.templates.model;
|
|
|
|
import io.github.cottonmc.templates.TemplatesClient;
|
|
import io.github.cottonmc.templates.block.TemplateEntity;
|
|
import io.github.cottonmc.templates.mixin.MinecraftAccessor;
|
|
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.MutableQuadView;
|
|
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;
|
|
import net.minecraft.client.render.model.BakedModel;
|
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
|
import net.minecraft.client.texture.Sprite;
|
|
import net.minecraft.item.ItemStack;
|
|
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.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import java.util.function.Supplier;
|
|
|
|
public abstract class RetexturingBakedModel extends ForwardingBakedModel {
|
|
public RetexturingBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, BlockState itemModelState) {
|
|
this.wrapped = baseModel;
|
|
|
|
this.tam = tam;
|
|
this.facePermutation = MeshTransformUtil.facePermutation(settings);
|
|
this.uvlock = settings.isUvLocked();
|
|
this.itemModelState = itemModelState;
|
|
}
|
|
|
|
protected final TemplateAppearanceManager tam;
|
|
protected final Map<Direction, Direction> facePermutation; //immutable
|
|
protected final boolean uvlock;
|
|
protected final BlockState itemModelState;
|
|
|
|
private static record CacheKey(BlockState state, TemplateAppearance appearance) {}
|
|
private final ConcurrentMap<CacheKey, Mesh> retexturedMeshes = new ConcurrentHashMap<>(); //mutable, append-only cache
|
|
|
|
protected static final Direction[] DIRECTIONS = Direction.values();
|
|
protected static final Direction[] DIRECTIONS_AND_NULL = new Direction[DIRECTIONS.length + 1];
|
|
static { System.arraycopy(DIRECTIONS, 0, DIRECTIONS_AND_NULL, 0, DIRECTIONS.length); }
|
|
|
|
protected abstract Mesh getBaseMesh(BlockState state);
|
|
|
|
@Override
|
|
public boolean isVanillaAdapter() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Sprite getParticleSprite() {
|
|
return tam.getDefaultAppearance().getParticleSprite();
|
|
}
|
|
|
|
@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;
|
|
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
|
|
return;
|
|
}
|
|
|
|
TemplateAppearance ta = tam.getAppearance(theme);
|
|
|
|
int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, blockView, pos, 0);
|
|
Mesh untintedMesh = getUntintedRetexturedMesh(new CacheKey(state, ta));
|
|
|
|
//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.
|
|
if(tint == 0xFFFFFFFF) {
|
|
context.meshConsumer().accept(untintedMesh);
|
|
} else {
|
|
context.pushTransform(new TintingTransformer(ta, tint));
|
|
context.meshConsumer().accept(untintedMesh);
|
|
context.popTransform();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
|
//cheeky: if the item has NBT data, pluck out the blockstate from it & look up the item color provider
|
|
//none of this is accessible unless you're in creative mode doing ctrl-pick btw
|
|
TemplateAppearance nbtAppearance;
|
|
int tint;
|
|
BlockState theme = TemplateEntity.readStateFromItem(stack);
|
|
if(!theme.isAir()) {
|
|
nbtAppearance = tam.getAppearance(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));
|
|
|
|
if(tint == 0xFFFFFFFF) {
|
|
context.meshConsumer().accept(untintedMesh);
|
|
} else {
|
|
context.pushTransform(new TintingTransformer(nbtAppearance, tint));
|
|
context.meshConsumer().accept(untintedMesh);
|
|
context.popTransform();
|
|
}
|
|
}
|
|
|
|
protected Mesh getUntintedRetexturedMesh(CacheKey key) {
|
|
return retexturedMeshes.computeIfAbsent(key, this::createUntintedRetexturedMesh);
|
|
}
|
|
|
|
protected Mesh createUntintedRetexturedMesh(CacheKey key) {
|
|
return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), new RetexturingTransformer(key.appearance, 0xFFFFFFFF));
|
|
}
|
|
|
|
protected class RetexturingTransformer implements RenderContext.QuadTransform {
|
|
protected RetexturingTransformer(TemplateAppearance ta, int tint) {
|
|
this.ta = ta;
|
|
this.tint = tint;
|
|
}
|
|
|
|
protected final TemplateAppearance ta;
|
|
protected final int tint;
|
|
|
|
@Override
|
|
public boolean transform(MutableQuadView quad) {
|
|
quad.material(ta.getRenderMaterial());
|
|
|
|
int tag = quad.tag();
|
|
if(tag == 0) return true; //Pass the quad through unmodified.
|
|
|
|
//The quad tag numbers were selected so this magic trick works:
|
|
Direction dir = facePermutation.get(DIRECTIONS[quad.tag() - 1]);
|
|
if(ta.hasColor(dir)) quad.color(tint, tint, tint, tint);
|
|
|
|
quad.spriteBake(ta.getSprite(dir), MutableQuadView.BAKE_NORMALIZED | ta.getBakeFlags(dir) | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected class TintingTransformer implements RenderContext.QuadTransform {
|
|
protected TintingTransformer(TemplateAppearance ta, int tint) {
|
|
this.ta = ta;
|
|
this.tint = tint;
|
|
}
|
|
|
|
protected final TemplateAppearance ta;
|
|
protected final int tint;
|
|
|
|
@Override
|
|
public boolean transform(MutableQuadView quad) {
|
|
int tag = quad.tag();
|
|
if(tag == 0) return true;
|
|
|
|
Direction dir = facePermutation.get(DIRECTIONS[quad.tag() - 1]);
|
|
if(ta.hasColor(dir)) quad.color(tint, tint, tint, tint);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|