We're much the same, you and I
This commit is contained in:
parent
ea476662d4
commit
e373b8b933
@ -1,13 +1,11 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
import io.github.cottonmc.templates.TemplatesClient;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
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.MeshBuilder;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.util.math.Direction;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
@ -17,51 +15,16 @@ import java.util.EnumMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class MeshTransformUtil {
|
public class MeshTransformUtil {
|
||||||
public static Mesh aroundCenter(Mesh oldMesh, ModelBakeSettings settings) {
|
public static Mesh pretransformMesh(Mesh mesh, RenderContext.QuadTransform transform) {
|
||||||
return aroundCenter(oldMesh, settings.getRotation().getMatrix());
|
MeshBuilder builder = TemplatesClient.getFabricRenderer().meshBuilder();
|
||||||
}
|
QuadEmitter emitter = builder.getEmitter();
|
||||||
|
|
||||||
public static Mesh aroundCenter(Mesh oldMesh, Matrix4f mat) {
|
|
||||||
Map<Direction, Direction> facePermutation = facePermutation(mat);
|
|
||||||
|
|
||||||
Renderer r = TemplatesClient.getFabricRenderer();
|
mesh.forEach(quad -> {
|
||||||
MeshBuilder newMesh = r.meshBuilder();
|
emitter.copyFrom(quad);
|
||||||
QuadEmitter emitter = newMesh.getEmitter();
|
if(transform.transform(emitter)) emitter.emit();
|
||||||
|
|
||||||
//re-used buffers
|
|
||||||
Vector3f pos3 = new Vector3f();
|
|
||||||
Vector4f pos4 = new Vector4f();
|
|
||||||
|
|
||||||
oldMesh.forEach(oldQuad -> {
|
|
||||||
//Initialize the new quad
|
|
||||||
emitter.copyFrom(oldQuad);
|
|
||||||
|
|
||||||
//For each vertex:
|
|
||||||
for(int i = 0; i < 4; i++) {
|
|
||||||
//Copy pos into a vec3, then a vec4. the w component is set to 0 since this is a point, not a normal
|
|
||||||
emitter.copyPos(i, pos3);
|
|
||||||
pos3.add(-0.5f, -0.5f, -0.5f);
|
|
||||||
pos4.set(pos3, 0);
|
|
||||||
|
|
||||||
//Compute the matrix-vector product. This function mutates the vec4 in-place.
|
|
||||||
//Note that `transformAffine` has the same purpose as `transform`; the difference is it
|
|
||||||
//assumes (without checking) that the last row of the matrix is 0,0,0,1, as an optimization
|
|
||||||
mat.transform(pos4);
|
|
||||||
|
|
||||||
//Manually copy the data back onto the vertex
|
|
||||||
emitter.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitter.nominalFace(facePermutation.get(emitter.lightFace()));
|
|
||||||
|
|
||||||
Direction cull = emitter.cullFace();
|
|
||||||
if(cull != null) emitter.cullFace(facePermutation.get(cull));
|
|
||||||
|
|
||||||
//Output the quad
|
|
||||||
emitter.emit();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return newMesh.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Hard to explain what this is for...
|
//Hard to explain what this is for...
|
||||||
@ -86,15 +49,39 @@ public class MeshTransformUtil {
|
|||||||
return facePermutation;
|
return facePermutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Mesh pretransformMesh(Mesh mesh, RenderContext.QuadTransform transform) {
|
public static RenderContext.QuadTransform applyAffine(ModelBakeSettings settings) {
|
||||||
MeshBuilder builder = TemplatesClient.getFabricRenderer().meshBuilder();
|
return applyMatrix(settings.getRotation().getMatrix());
|
||||||
QuadEmitter emitter = builder.getEmitter();
|
}
|
||||||
|
|
||||||
|
public static RenderContext.QuadTransform applyMatrix(Matrix4f mat) {
|
||||||
|
Map<Direction, Direction> facePermutation = facePermutation(mat);
|
||||||
|
Vector3f pos3 = new Vector3f();
|
||||||
|
Vector4f pos4 = new Vector4f();
|
||||||
|
|
||||||
mesh.forEach(quad -> {
|
return quad -> {
|
||||||
emitter.copyFrom(quad);
|
//For each vertex:
|
||||||
if(transform.transform(emitter)) emitter.emit();
|
for(int i = 0; i < 4; i++) {
|
||||||
});
|
//Copy pos into a vec3, then a vec4. the w component is set to 0 since this is a point, not a normal
|
||||||
|
quad.copyPos(i, pos3);
|
||||||
return builder.build();
|
pos3.add(-0.5f, -0.5f, -0.5f);
|
||||||
|
pos4.set(pos3, 0);
|
||||||
|
|
||||||
|
//Compute the matrix-vector product. This function mutates the vec4 in-place.
|
||||||
|
//Note that `transformAffine` has the same purpose as `transform`; the difference is it
|
||||||
|
//assumes (without checking) that the last row of the matrix is 0,0,0,1, as an optimization
|
||||||
|
mat.transform(pos4);
|
||||||
|
|
||||||
|
//Manually copy the data back onto the vertex
|
||||||
|
quad.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
quad.nominalFace(facePermutation.get(quad.lightFace()));
|
||||||
|
|
||||||
|
Direction cull = quad.cullFace();
|
||||||
|
if(cull != null) quad.cullFace(facePermutation.get(cull));
|
||||||
|
|
||||||
|
//Output the quad
|
||||||
|
return true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
|
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);
|
||||||
|
return new QuadUvBounds(
|
||||||
|
Math.min(Math.min(u0, u1), Math.min(u2, u3)),
|
||||||
|
Math.max(Math.max(u0, u1), Math.max(u2, u3)),
|
||||||
|
Math.min(Math.min(v0, v1), Math.min(v2, v3)),
|
||||||
|
Math.max(Math.max(v0, v1), Math.max(v2, v3))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean displaysSprite(Sprite sprite) {
|
||||||
|
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());
|
||||||
|
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);
|
||||||
|
quad.uv(3, MathHelper.approximatelyEquals(quad.u(3), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(3), minV) ? remappedMinV : remappedMaxV);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float norm(float value, float low, float high) {
|
||||||
|
float value2 = MathHelper.clamp(value, low, high);
|
||||||
|
return (value2 - low) / (high - low);
|
||||||
|
}
|
||||||
|
|
||||||
|
//static float rangeRemap(float value, float low1, float high1, float low2, float high2) {
|
||||||
|
// float value2 = MathHelper.clamp(value, low1, high1);
|
||||||
|
// return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1);
|
||||||
|
//}
|
||||||
|
}
|
@ -1,174 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import io.github.cottonmc.templates.Templates;
|
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
|
||||||
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.MutableQuadView;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
|
|
||||||
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.client.render.model.BakedModel;
|
|
||||||
import net.minecraft.client.render.model.BakedQuad;
|
|
||||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.util.SpriteIdentifier;
|
|
||||||
import net.minecraft.item.BlockItem;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.nbt.NbtCompound;
|
|
||||||
import net.minecraft.nbt.NbtHelper;
|
|
||||||
import net.minecraft.registry.Registries;
|
|
||||||
import net.minecraft.screen.PlayerScreenHandler;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
import net.minecraft.world.BlockRenderView;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class RetexturedJsonModelBakedModel extends ForwardingBakedModel {
|
|
||||||
public RetexturedJsonModelBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, Function<SpriteIdentifier, Sprite> spriteLookup, BlockState itemModelState) {
|
|
||||||
this.wrapped = baseModel;
|
|
||||||
this.tam = tam;
|
|
||||||
this.facePermutation = MeshTransformUtil.facePermutation(settings);
|
|
||||||
this.itemModelState = itemModelState;
|
|
||||||
|
|
||||||
for(int i = 0; i < DIRECTIONS.length; i++) {
|
|
||||||
SpriteIdentifier id = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Templates.id("templates_special/" + DIRECTIONS[i].getName()));
|
|
||||||
this.specialSprites[i] = Objects.requireNonNull(spriteLookup.apply(id), () -> "Couldn't find sprite " + id + " !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TemplateAppearanceManager tam;
|
|
||||||
private final Map<Direction, Direction> facePermutation;
|
|
||||||
private final Sprite[] specialSprites = new Sprite[DIRECTIONS.length];
|
|
||||||
private final BlockState itemModelState;
|
|
||||||
|
|
||||||
private record CacheKey(BlockState state, TemplateAppearance appearance) {}
|
|
||||||
private final ConcurrentHashMap<CacheKey, Mesh> meshCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isVanillaAdapter() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
TemplateAppearance ta = theme == null || theme.isAir() ? tam.getDefaultAppearance() : tam.getAppearance(theme);
|
|
||||||
|
|
||||||
CacheKey key = new CacheKey(state, ta);
|
|
||||||
context.meshConsumer().accept(meshCache.computeIfAbsent(key, this::makeMesh));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
|
||||||
TemplateAppearance nbtAppearance = null;
|
|
||||||
|
|
||||||
//cheeky: if the item has NBT data, pluck out the blockstate from it
|
|
||||||
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
|
|
||||||
if(tag != null && tag.contains("BlockState")) {
|
|
||||||
BlockState theme = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
|
||||||
if(!theme.isAir()) nbtAppearance = tam.getAppearance(theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheKey key = new CacheKey(itemModelState, nbtAppearance == null ? tam.getDefaultAppearance() : nbtAppearance);
|
|
||||||
context.meshConsumer().accept(meshCache.computeIfAbsent(key, this::makeMesh));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sprite getParticleSprite() {
|
|
||||||
return tam.getDefaultAppearance().getParticleSprite();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Mesh makeMesh(CacheKey key) {
|
|
||||||
Renderer r = TemplatesClient.getFabricRenderer();
|
|
||||||
MeshBuilder builder = r.meshBuilder();
|
|
||||||
QuadEmitter emitter = builder.getEmitter();
|
|
||||||
RenderMaterial mat = key.appearance().getRenderMaterial();
|
|
||||||
|
|
||||||
Random rand = Random.create(42);
|
|
||||||
|
|
||||||
for(Direction cullFace : DIRECTIONS_AND_NULL) {
|
|
||||||
for(BakedQuad quad : wrapped.getQuads(key.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.remap(
|
|
||||||
emitter,
|
|
||||||
specialSprites[i],
|
|
||||||
key.appearance().getSprite(facePermutation.get(DIRECTIONS[i])),
|
|
||||||
key.appearance().getBakeFlags(facePermutation.get(DIRECTIONS[i]))
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitter.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
return new QuadUvBounds(
|
|
||||||
Math.min(Math.min(u0, u1), Math.min(u2, u3)),
|
|
||||||
Math.max(Math.max(u0, u1), Math.max(u2, u3)),
|
|
||||||
Math.min(Math.min(v0, v1), Math.min(v2, v3)),
|
|
||||||
Math.max(Math.max(v0, v1), Math.max(v2, v3))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean displaysSprite(Sprite sprite) {
|
|
||||||
return sprite.getMinU() <= minU && sprite.getMaxU() >= maxU && sprite.getMinV() <= minV && sprite.getMaxV() >= maxV;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remap(MutableQuadView quad, Sprite specialSprite, Sprite newSprite, int bakeFlags) {
|
|
||||||
//move the UVs into 0..1 range
|
|
||||||
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());
|
|
||||||
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);
|
|
||||||
quad.uv(3, MathHelper.approximatelyEquals(quad.u(3), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(3), minV) ? remappedMinV : remappedMaxV);
|
|
||||||
|
|
||||||
//call spriteBake
|
|
||||||
//done this way (instead of directly setting UV coordinates to their final values) so that I can use the convenient bakeFlags option
|
|
||||||
quad.spriteBake(newSprite, MutableQuadView.BAKE_NORMALIZED | bakeFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static float norm(float value, float low, float high) {
|
|
||||||
float value2 = MathHelper.clamp(value, low, high);
|
|
||||||
return (value2 - low) / (high - low);
|
|
||||||
}
|
|
||||||
|
|
||||||
//static float rangeRemap(float value, float low1, float high1, float low2, float high2) {
|
|
||||||
// float value2 = MathHelper.clamp(value, low1, high1);
|
|
||||||
// return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
|
||||||
private static final Direction[] DIRECTIONS_AND_NULL = new Direction[DIRECTIONS.length + 1];
|
|
||||||
static {
|
|
||||||
System.arraycopy(DIRECTIONS, 0, DIRECTIONS_AND_NULL, 0, DIRECTIONS.length);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,32 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import io.github.cottonmc.templates.Templates;
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
import io.github.cottonmc.templates.TemplatesClient;
|
||||||
|
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.block.BlockState;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.block.Blocks;
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.client.render.model.BakedQuad;
|
||||||
import net.minecraft.client.render.model.Baker;
|
import net.minecraft.client.render.model.Baker;
|
||||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
import net.minecraft.client.render.model.UnbakedModel;
|
import net.minecraft.client.render.model.UnbakedModel;
|
||||||
import net.minecraft.client.texture.Sprite;
|
import net.minecraft.client.texture.Sprite;
|
||||||
import net.minecraft.client.util.SpriteIdentifier;
|
import net.minecraft.client.util.SpriteIdentifier;
|
||||||
|
import net.minecraft.screen.PlayerScreenHandler;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.random.Random;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class RetexturedJsonModelUnbakedModel implements UnbakedModel {
|
public class RetexturedJsonModelUnbakedModel implements UnbakedModel {
|
||||||
@ -42,12 +55,55 @@ public class RetexturedJsonModelUnbakedModel implements UnbakedModel {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
||||||
return new RetexturedJsonModelBakedModel(
|
Direction[] DIRECTIONS = RetexturingBakedModel.DIRECTIONS;
|
||||||
|
|
||||||
|
Sprite[] specialSprites = new Sprite[DIRECTIONS.length];
|
||||||
|
for(int i = 0; i < DIRECTIONS.length; i++) {
|
||||||
|
SpriteIdentifier id = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Templates.id("templates_special/" + DIRECTIONS[i].getName()));
|
||||||
|
specialSprites[i] = Objects.requireNonNull(spriteLookup.apply(id), () -> "Couldn't find sprite " + id + " !");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
return new RetexturingBakedModel(
|
||||||
baker.bake(parent, modelBakeSettings),
|
baker.bake(parent, modelBakeSettings),
|
||||||
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
||||||
modelBakeSettings,
|
modelBakeSettings,
|
||||||
spriteLookup,
|
|
||||||
itemModelState
|
itemModelState
|
||||||
);
|
) {
|
||||||
|
@Override
|
||||||
|
protected Mesh getBaseMesh(BlockState state) {
|
||||||
|
//Convert models to retexturable Meshes lazily, the first time we encounter each blockstate
|
||||||
|
return jsonToMesh.computeIfAbsent(state, this::convertModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mesh convertModel(BlockState state) {
|
||||||
|
Renderer r = TemplatesClient.getFabricRenderer();
|
||||||
|
MeshBuilder builder = r.meshBuilder();
|
||||||
|
QuadEmitter emitter = builder.getEmitter();
|
||||||
|
RenderMaterial mat = tam.getCachedMaterial(state);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
|
||||||
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.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.client.color.block.BlockColorProvider;
|
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
|
||||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.item.BlockItem;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.nbt.NbtCompound;
|
|
||||||
import net.minecraft.nbt.NbtHelper;
|
|
||||||
import net.minecraft.registry.Registries;
|
|
||||||
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.function.Supplier;
|
|
||||||
|
|
||||||
public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
|
||||||
public RetexturedMeshBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, Mesh baseMesh) {
|
|
||||||
this.wrapped = baseModel;
|
|
||||||
this.tam = tam;
|
|
||||||
this.baseMesh = MeshTransformUtil.aroundCenter(baseMesh, settings);
|
|
||||||
this.facePermutation = MeshTransformUtil.facePermutation(settings);
|
|
||||||
this.uvLock = settings.isUvLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TemplateAppearanceManager tam;
|
|
||||||
private final Mesh baseMesh;
|
|
||||||
private final Map<Direction, Direction> facePermutation;
|
|
||||||
private final boolean uvLock;
|
|
||||||
|
|
||||||
private final ConcurrentHashMap<TemplateAppearance, Mesh> meshCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isVanillaAdapter() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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(getUntintedMesh(tam.getDefaultAppearance()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TemplateAppearance ta = tam.getAppearance(theme);
|
|
||||||
|
|
||||||
BlockColorProvider prov = ColorProviderRegistry.BLOCK.get(theme.getBlock());
|
|
||||||
int tint = prov == null ? 0xFFFFFFFF : (0xFF000000 | prov.getColor(theme, blockView, pos, 1));
|
|
||||||
|
|
||||||
if(tint == 0xFFFFFFFF) {
|
|
||||||
//Cache this mesh indefinitely.
|
|
||||||
context.meshConsumer().accept(getUntintedMesh(ta));
|
|
||||||
} else {
|
|
||||||
//The specific tint might vary a lot; imagine grass color smoothly changing. Baking the tint into the cached mesh
|
|
||||||
//is likely unnecessary and will fill the cache with a ton of single-use meshes with only slighly different colors.
|
|
||||||
//We'd also have to percolate that tint color into the cache key, which is an allocation, blah blah blah.
|
|
||||||
//Let's fall back to a quad transform. In practice this is still nice and quick.
|
|
||||||
context.pushTransform(new RetexturingTransformer(ta, tint, facePermutation, uvLock));
|
|
||||||
context.meshConsumer().accept(baseMesh);
|
|
||||||
context.popTransform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
|
||||||
TemplateAppearance ta = tam.getDefaultAppearance();
|
|
||||||
|
|
||||||
//cheeky: if the item has NBT data, pluck out the blockstate from it
|
|
||||||
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
|
|
||||||
if(tag != null && tag.contains("BlockState")) {
|
|
||||||
BlockState state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
|
||||||
if(state != null && !state.isAir()) ta = tam.getAppearance(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.meshConsumer().accept(getUntintedMesh(ta));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sprite getParticleSprite() {
|
|
||||||
return tam.getDefaultAppearance().getParticleSprite();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Mesh getUntintedMesh(TemplateAppearance ta) {
|
|
||||||
return meshCache.computeIfAbsent(ta, this::makeUntintedMesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Mesh makeUntintedMesh(TemplateAppearance appearance) {
|
|
||||||
return MeshTransformUtil.pretransformMesh(baseMesh, new RetexturingTransformer(appearance, 0xFFFFFFFF, facePermutation, uvLock));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static record RetexturingTransformer(TemplateAppearance appearance, int color, Map<Direction, Direction> facePermutation, boolean uvLock) implements RenderContext.QuadTransform {
|
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean transform(MutableQuadView quad) {
|
|
||||||
quad.material(appearance.getRenderMaterial());
|
|
||||||
|
|
||||||
//The quad tag numbers were selected so this magic trick works:
|
|
||||||
Direction dir = facePermutation.get(DIRECTIONS[quad.tag() - 1]);
|
|
||||||
//TODO: this newly-simplified direction passing to hasColor is almost certainly incorrect
|
|
||||||
// I think hasColor was kinda incorrect in the first place tho
|
|
||||||
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
|
||||||
|
|
||||||
Sprite sprite = appearance.getSprite(dir);
|
|
||||||
|
|
||||||
int flags = MutableQuadView.BAKE_NORMALIZED;
|
|
||||||
flags |= appearance.getBakeFlags(dir);
|
|
||||||
if(uvLock) flags |= MutableQuadView.BAKE_LOCK_UV;
|
|
||||||
|
|
||||||
quad.spriteBake(sprite, flags);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,8 @@ package io.github.cottonmc.templates.model;
|
|||||||
|
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
import io.github.cottonmc.templates.TemplatesClient;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
import net.minecraft.client.render.model.Baker;
|
import net.minecraft.client.render.model.Baker;
|
||||||
import net.minecraft.client.render.model.ModelBakeSettings;
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
@ -15,15 +17,18 @@ import java.util.Collections;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@SuppressWarnings("ClassCanBeRecord")
|
|
||||||
public class RetexturedMeshUnbakedModel implements UnbakedModel {
|
public class RetexturedMeshUnbakedModel implements UnbakedModel {
|
||||||
public RetexturedMeshUnbakedModel(Identifier parent, Supplier<Mesh> baseMeshFactory) {
|
public RetexturedMeshUnbakedModel(Identifier parent, Supplier<Mesh> baseMeshFactory) {
|
||||||
|
this(parent, __ -> baseMeshFactory.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetexturedMeshUnbakedModel(Identifier parent, Function<Function<SpriteIdentifier, Sprite>, Mesh> baseMeshFactory) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.baseMeshFactory = baseMeshFactory;
|
this.baseMeshFactory = baseMeshFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Identifier parent;
|
protected final Identifier parent;
|
||||||
protected final Supplier<Mesh> baseMeshFactory;
|
protected final Function<Function<SpriteIdentifier, Sprite>, Mesh> baseMeshFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Identifier> getModelDependencies() {
|
public Collection<Identifier> getModelDependencies() {
|
||||||
@ -32,16 +37,23 @@ public class RetexturedMeshUnbakedModel implements UnbakedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParents(Function<Identifier, UnbakedModel> function) {
|
public void setParents(Function<Identifier, UnbakedModel> function) {
|
||||||
function.apply(parent).setParents(function); //Still not sure what this function does lol
|
function.apply(parent).setParents(function);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
||||||
return new RetexturedMeshBakedModel(
|
Mesh transformedBaseMesh = MeshTransformUtil.pretransformMesh(baseMeshFactory.apply(spriteLookup), MeshTransformUtil.applyAffine(modelBakeSettings));
|
||||||
|
|
||||||
|
return new RetexturingBakedModel(
|
||||||
baker.bake(parent, modelBakeSettings),
|
baker.bake(parent, modelBakeSettings),
|
||||||
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
||||||
modelBakeSettings,
|
modelBakeSettings,
|
||||||
baseMeshFactory.get()
|
Blocks.AIR.getDefaultState()
|
||||||
);
|
) {
|
||||||
|
@Override
|
||||||
|
protected Mesh getBaseMesh(BlockState state) {
|
||||||
|
return transformedBaseMesh;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
||||||
|
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.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.client.color.block.BlockColorProvider;
|
||||||
|
import net.minecraft.client.color.item.ItemColorProvider;
|
||||||
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.item.BlockItem;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.nbt.NbtCompound;
|
||||||
|
import net.minecraft.nbt.NbtHelper;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
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<>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplateAppearance ta = tam.getAppearance(theme);
|
||||||
|
|
||||||
|
BlockColorProvider prov = ColorProviderRegistry.BLOCK.get(theme.getBlock());
|
||||||
|
int tint = prov == null ? 0xFFFFFFFF : (0xFF000000 | prov.getColor(theme, blockView, pos, 1));
|
||||||
|
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) {
|
||||||
|
TemplateAppearance nbtAppearance = tam.getDefaultAppearance();
|
||||||
|
int tint = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
//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
|
||||||
|
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
|
||||||
|
if(tag != null && tag.contains("BlockState")) {
|
||||||
|
BlockState theme = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
||||||
|
if(!theme.isAir()) {
|
||||||
|
nbtAppearance = tam.getAppearance(theme);
|
||||||
|
|
||||||
|
ItemColorProvider prov = ColorProviderRegistry.ITEM.get(theme.getBlock());
|
||||||
|
if(prov != null) tint = prov.getColor(new ItemStack(theme.getBlock()), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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); //TODO: still doesn't cover stuff like grass blocks, leaf blocks, etc
|
||||||
|
|
||||||
|
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); //TODO: still doesn't cover stuff like grass blocks, leaf blocks, etc
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,15 @@ import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
|||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
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.MeshBuilder;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.client.util.SpriteIdentifier;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.util.math.Direction;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class SlopeBaseMesh {
|
public class SlopeBaseMesh {
|
||||||
/**
|
/**
|
||||||
* @see RetexturedMeshBakedModel.RetexturingTransformer for why these values were chosen
|
* @see RetexturingBakedModel for why these values were chosen
|
||||||
*/
|
*/
|
||||||
public static final int TAG_SLOPE = Direction.UP.ordinal() + 1;
|
public static final int TAG_SLOPE = Direction.UP.ordinal() + 1;
|
||||||
public static final int TAG_LEFT = Direction.EAST.ordinal() + 1;
|
public static final int TAG_LEFT = Direction.EAST.ordinal() + 1;
|
||||||
|
@ -6,7 +6,7 @@ import net.minecraft.util.math.Direction;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public interface TemplateAppearance {
|
public interface TemplateAppearance {
|
||||||
@NotNull Sprite getParticleSprite(); //TODO: plug this in
|
@NotNull Sprite getParticleSprite(); //TODO: plug this in (particle mixins don't use it atm)
|
||||||
|
|
||||||
@NotNull RenderMaterial getRenderMaterial();
|
@NotNull RenderMaterial getRenderMaterial();
|
||||||
@NotNull Sprite getSprite(Direction dir);
|
@NotNull Sprite getSprite(Direction dir);
|
||||||
|
@ -55,6 +55,10 @@ public class TemplateAppearanceManager {
|
|||||||
return appearanceCache.computeIfAbsent(state, this::computeAppearance);
|
return appearanceCache.computeIfAbsent(state, this::computeAppearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RenderMaterial getCachedMaterial(BlockState state) {
|
||||||
|
return blockMaterials.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)));
|
||||||
|
}
|
||||||
|
|
||||||
//I'm pretty sure ConcurrentHashMap semantics allow for this function to be called multiple times on the same key, on different threads.
|
//I'm pretty sure ConcurrentHashMap semantics allow for this function to be called multiple times on the same key, on different threads.
|
||||||
//The computeIfAbsent map update will work without corrupting the map, but there will be some "wasted effort" computing the value twice.
|
//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).
|
//The results are going to be the same, apart from their serialNumbers differing (= their equals & hashCode differing).
|
||||||
@ -136,7 +140,7 @@ public class TemplateAppearanceManager {
|
|||||||
sprites,
|
sprites,
|
||||||
bakeFlags,
|
bakeFlags,
|
||||||
hasColorMask,
|
hasColorMask,
|
||||||
blockMaterials.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state))),
|
getCachedMaterial(state),
|
||||||
serialNumber.getAndIncrement()
|
serialNumber.getAndIncrement()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user