SlopeBakedModel is now threadsafe - but at what cost
This commit is contained in:
parent
338e45d016
commit
24c8c97f00
@ -16,6 +16,7 @@ Template blocks can be placed in the world, then right-clicked with a full-size
|
|||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
|
* The conceit used in SlopeBakedModel (update mutable state, then bake the mesh) might be inherently flawed - it's not threadsafe
|
||||||
* Fix the item model lol, broke it with the new system (might need a ModelLoadingRegistry registerVariantProvider as a last resort)
|
* Fix the item model lol, broke it with the new system (might need a ModelLoadingRegistry registerVariantProvider as a last resort)
|
||||||
* Re-generalize the model system (I removed a layer of indirection while rewriting it, so it's just slopes now)
|
* Re-generalize the model system (I removed a layer of indirection while rewriting it, so it's just slopes now)
|
||||||
* Upside-down slopes would be nice...
|
* Upside-down slopes would be nice...
|
||||||
|
@ -15,35 +15,23 @@ public record AffineQuadTransformer(Matrix4f affineMatrix) implements RenderCont
|
|||||||
@Override
|
@Override
|
||||||
public boolean transform(MutableQuadView quad) {
|
public boolean transform(MutableQuadView quad) {
|
||||||
Vector3f pos3 = new Vector3f();
|
Vector3f pos3 = new Vector3f();
|
||||||
Vector3f norm3 = new Vector3f();
|
Vector4f pos4 = new Vector4f(); //ugh
|
||||||
|
|
||||||
//ugh
|
|
||||||
Vector4f pos4 = new Vector4f();
|
|
||||||
Vector4f norm4 = new Vector4f();
|
|
||||||
|
|
||||||
for(int i = 0; i < 4; i++) {
|
for(int i = 0; i < 4; i++) {
|
||||||
//Copy quad data into vec3's
|
//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);
|
quad.copyPos(i, pos3);
|
||||||
quad.copyNormal(i, norm3);
|
//kinda a hack to center the affine transformation not at 0,0,0
|
||||||
|
//it's an *affine* transformation, they can rotate around the not-origin... should modify the transformation instead?
|
||||||
pos3.add(-0.5f, -0.5f, -0.5f); //TODO, kinda a hack to center the affine transformation not at 0,0,0
|
pos3.add(-0.5f, -0.5f, -0.5f);
|
||||||
|
|
||||||
//Initialize the x/y/z components of vec4s using that data
|
|
||||||
//W component of normal vector is set to 1, normal vectors transform differently from points :)
|
|
||||||
pos4.set(pos3, 0);
|
pos4.set(pos3, 0);
|
||||||
norm4.set(norm3, 1);
|
|
||||||
|
|
||||||
//Compute the matrix-vector product. This function mutates the vec4 in-place.
|
//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
|
//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
|
//assumes (without checking) that the last row of the matrix is 0,0,0,1, as an optimization
|
||||||
affineMatrix.transformAffine(pos4);
|
affineMatrix.transformAffine(pos4);
|
||||||
affineMatrix.transformAffine(norm4);
|
|
||||||
|
|
||||||
//Manually copy the data back onto the vertex
|
//Manually copy the data back onto the vertex
|
||||||
quad.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
quad.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
||||||
|
|
||||||
//TODO: makes everything turn black for some reason (norm vec is (0, 0, 0))
|
|
||||||
//quad.normal(i, norm4.x, norm4.y, norm4.z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,31 +1,29 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
||||||
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.model.ForwardingBakedModel;
|
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.util.SpriteIdentifier;
|
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
import net.minecraft.util.math.AffineTransformation;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.random.Random;
|
import net.minecraft.util.math.random.Random;
|
||||||
import net.minecraft.world.BlockRenderView;
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public final class SlopeBakedModel extends ForwardingBakedModel {
|
public final class SlopeBakedModel extends ForwardingBakedModel {
|
||||||
public SlopeBakedModel(BakedModel baseModel, Function<SpriteIdentifier, Sprite> spriteLookup, AffineTransformation aff) {
|
public SlopeBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, AffineTransformation aff) {
|
||||||
this.wrapped = baseModel;
|
this.wrapped = baseModel;
|
||||||
|
|
||||||
this.preparer = new SlopeMeshTransformPreparer(spriteLookup);
|
this.preparer = new SlopeQuadTransformFactory(tam);
|
||||||
this.affineTransformer = new AffineQuadTransformer(aff);
|
this.affineTransformer = new AffineQuadTransformer(aff);
|
||||||
this.baseMesh = SlopeBaseMesh.make();
|
this.baseMesh = SlopeBaseMesh.make();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final TemplateQuadTransformPreparer preparer;
|
private final TemplateQuadTransformFactory preparer;
|
||||||
private final RenderContext.QuadTransform affineTransformer;
|
private final RenderContext.QuadTransform affineTransformer;
|
||||||
private final Mesh baseMesh;
|
private final Mesh baseMesh;
|
||||||
|
|
||||||
|
@ -19,11 +19,27 @@ public class SlopeBaseMesh {
|
|||||||
|
|
||||||
MeshBuilder builder = renderer.meshBuilder();
|
MeshBuilder builder = renderer.meshBuilder();
|
||||||
QuadEmitter qu = builder.getEmitter();
|
QuadEmitter qu = builder.getEmitter();
|
||||||
qu.color(-1, -1, -1, -1).tag(TAG_SLOPE) .pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 0f).emit()
|
|
||||||
.color(-1, -1, -1, -1).tag(TAG_LEFT) .pos(0, 1f, 0f, 0f).pos(1, 1f, 1f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 0f, 1f).emit()
|
qu.tag(TAG_SLOPE)
|
||||||
.color(-1, -1, -1, -1).tag(TAG_RIGHT) .pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 0f).pos(2, 0f, 0f, 1f).pos(3, 0f, 1f, 1f).emit()
|
.pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 0f)
|
||||||
.color(-1, -1, -1, -1).tag(TAG_BACK) .pos(0, 0f, 0f, 1f).pos(1, 1f, 0f, 1f).pos(2, 1f, 1f, 1f).pos(3, 0f, 1f, 1f).emit()
|
.color(-1, -1, -1, -1)
|
||||||
.color(-1, -1, -1, -1).tag(TAG_BOTTOM).pos(0, 0f, 0f, 0f).pos(1, 1f, 0f, 0f).pos(2, 1f, 0f, 1f).pos(3, 0f, 0f, 1f).emit();
|
.emit()
|
||||||
|
.tag(TAG_LEFT)
|
||||||
|
.pos(0, 1f, 0f, 0f).pos(1, 1f, 1f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 0f, 1f)
|
||||||
|
.color(-1, -1, -1, -1)
|
||||||
|
.emit()
|
||||||
|
.tag(TAG_RIGHT)
|
||||||
|
.pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 0f).pos(2, 0f, 0f, 1f).pos(3, 0f, 1f, 1f)
|
||||||
|
.color(-1, -1, -1, -1)
|
||||||
|
.emit()
|
||||||
|
.tag(TAG_BACK)
|
||||||
|
.pos(0, 0f, 0f, 1f).pos(1, 1f, 0f, 1f).pos(2, 1f, 1f, 1f).pos(3, 0f, 1f, 1f)
|
||||||
|
.color(-1, -1, -1, -1)
|
||||||
|
.emit()
|
||||||
|
.tag(TAG_BOTTOM)
|
||||||
|
.pos(0, 0f, 0f, 0f).pos(1, 1f, 0f, 0f).pos(2, 1f, 0f, 1f).pos(3, 0f, 0f, 1f)
|
||||||
|
.color(-1, -1, -1, -1)
|
||||||
|
.emit();
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
|
||||||
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
|
||||||
import net.fabricmc.fabric.api.util.TriState;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.block.Blocks;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.color.block.BlockColorProvider;
|
|
||||||
import net.minecraft.client.render.RenderLayers;
|
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.util.SpriteIdentifier;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.state.property.Properties;
|
|
||||||
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.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class SlopeMeshTransformPreparer implements TemplateQuadTransformPreparer, RenderContext.QuadTransform {
|
|
||||||
public SlopeMeshTransformPreparer(Function<SpriteIdentifier, Sprite> spriteLookup) {
|
|
||||||
this.sprites = new SpriteSet(spriteLookup);
|
|
||||||
|
|
||||||
Renderer r = RendererAccess.INSTANCE.getRenderer();
|
|
||||||
if(r == null) throw new IllegalStateException("A Fabric Rendering API implementation is required");
|
|
||||||
this.finder = r.materialFinder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SpriteSet sprites;
|
|
||||||
private final MaterialFinder finder;
|
|
||||||
|
|
||||||
private int color;
|
|
||||||
private Direction dir;
|
|
||||||
private RenderMaterial material;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier) {
|
|
||||||
dir = state.get(Properties.HORIZONTAL_FACING);
|
|
||||||
color = 0xffffff;
|
|
||||||
|
|
||||||
Object renderAttach = ((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos);
|
|
||||||
BlockState template = (renderAttach instanceof BlockState s) ? s : Blocks.AIR.getDefaultState();
|
|
||||||
final Block block = template.getBlock();
|
|
||||||
|
|
||||||
if(block == Blocks.AIR) {
|
|
||||||
sprites.clear();
|
|
||||||
material = finder.clear().blendMode(BlendMode.CUTOUT).find();
|
|
||||||
} else {
|
|
||||||
material = finder.clear()
|
|
||||||
.disableDiffuse(false)
|
|
||||||
.ambientOcclusion(TriState.FALSE)
|
|
||||||
.blendMode(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)))
|
|
||||||
.find();
|
|
||||||
|
|
||||||
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(template);
|
|
||||||
sprites.inspect(model, randomSupplier.get());
|
|
||||||
BlockColorProvider blockColor = ColorProviderRegistry.BLOCK.get(block);
|
|
||||||
if(blockColor != null) color = 0xff000000 | blockColor.getColor(template, blockView, pos, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier) {
|
|
||||||
dir = Direction.NORTH;
|
|
||||||
color = 0xffffff;
|
|
||||||
sprites.clear();
|
|
||||||
material = finder.clear().find();
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean transform(MutableQuadView quad) {
|
|
||||||
quad.material(material);
|
|
||||||
|
|
||||||
final SpriteSet sprites = this.sprites;
|
|
||||||
switch(quad.tag()) {
|
|
||||||
case SlopeBaseMesh.TAG_SLOPE -> {
|
|
||||||
if(sprites.hasColor(Direction.UP)) quad.color(color, color, color, color);
|
|
||||||
paintSlope(quad, dir, sprites.getSprite(Direction.UP));
|
|
||||||
}
|
|
||||||
case SlopeBaseMesh.TAG_LEFT -> {
|
|
||||||
final Direction leftDir = this.dir.rotateYCounterclockwise();
|
|
||||||
if(sprites.hasColor(leftDir)) quad.color(color, color, color, color);
|
|
||||||
paintLeftSide(quad, dir, sprites.getSprite(leftDir));
|
|
||||||
}
|
|
||||||
case SlopeBaseMesh.TAG_RIGHT -> {
|
|
||||||
final Direction rightDir = this.dir.rotateYClockwise();
|
|
||||||
if(sprites.hasColor(rightDir)) quad.color(color, color, color, color);
|
|
||||||
paintRightSide(quad, dir, sprites.getSprite(rightDir));
|
|
||||||
}
|
|
||||||
case SlopeBaseMesh.TAG_BACK -> {
|
|
||||||
if(sprites.hasColor(dir)) quad.color(color, color, color, color);
|
|
||||||
paintBack(quad, dir, sprites.getSprite(dir));
|
|
||||||
}
|
|
||||||
case SlopeBaseMesh.TAG_BOTTOM -> {
|
|
||||||
if(sprites.hasColor(Direction.DOWN)) quad.color(color, color, color, color);
|
|
||||||
paintBottom(quad, sprites.getSprite(Direction.DOWN));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void paintSlope(MutableQuadView quad, Direction dir, Sprite sprite) {
|
|
||||||
switch(dir) {
|
|
||||||
case NORTH -> quad.uv(0, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(1, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(3, sprite.getMaxU(), sprite.getMinV());
|
|
||||||
case SOUTH -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(2, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
|
||||||
case EAST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMinV());
|
|
||||||
case WEST -> quad.uv(0, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(1, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(2, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(3, sprite.getMaxU(), sprite.getMaxV());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void paintLeftSide(MutableQuadView quad, Direction dir, Sprite sprite) {
|
|
||||||
switch(dir) {
|
|
||||||
case NORTH, EAST, WEST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMinV());
|
|
||||||
case SOUTH -> quad.uv(0, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(1, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(2, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(3, sprite.getMaxU(), sprite.getMaxV());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void paintRightSide(MutableQuadView quad, Direction dir, Sprite sprite) {
|
|
||||||
switch(dir) {
|
|
||||||
case NORTH, WEST -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(2, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
|
||||||
case SOUTH, EAST -> quad.uv(0, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(1, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(3, sprite.getMaxU(), sprite.getMinV());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void paintBack(MutableQuadView quad, Direction dir, Sprite sprite) {
|
|
||||||
switch(dir) {
|
|
||||||
case NORTH, EAST -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(2, sprite.getMinU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
|
||||||
case SOUTH, WEST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMinV());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void paintBottom(MutableQuadView quad, Sprite sprite) {
|
|
||||||
quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
|
||||||
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
|
||||||
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
|
||||||
.uv(3, sprite.getMinU(), sprite.getMinV());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,175 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
|
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
||||||
|
import net.fabricmc.fabric.api.util.TriState;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.client.color.block.BlockColorProvider;
|
||||||
|
import net.minecraft.client.render.RenderLayers;
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.state.property.Properties;
|
||||||
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class SlopeQuadTransformFactory implements TemplateQuadTransformFactory {
|
||||||
|
public SlopeQuadTransformFactory(TemplateAppearanceManager tam) {
|
||||||
|
this.tam = tam;
|
||||||
|
this.r = Objects.requireNonNull(RendererAccess.INSTANCE.getRenderer(), "A Fabric Rendering API implementation is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TemplateAppearanceManager tam;
|
||||||
|
private final Renderer r;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier) {
|
||||||
|
Direction dir = state.get(Properties.HORIZONTAL_FACING);
|
||||||
|
|
||||||
|
Object renderAttach = ((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos);
|
||||||
|
BlockState template = (renderAttach instanceof BlockState s) ? s : Blocks.AIR.getDefaultState();
|
||||||
|
Block block = template.getBlock();
|
||||||
|
|
||||||
|
TemplateAppearance appearance;
|
||||||
|
RenderMaterial material;
|
||||||
|
int globalTint;
|
||||||
|
|
||||||
|
if(block == null || block == Blocks.AIR) {
|
||||||
|
appearance = tam.getDefaultAppearance();
|
||||||
|
material = r.materialFinder().clear().blendMode(BlendMode.CUTOUT).find();
|
||||||
|
globalTint = 0xFFFFFF;
|
||||||
|
} else {
|
||||||
|
appearance = tam.getAppearance(template);
|
||||||
|
material = r.materialFinder().clear()
|
||||||
|
.disableDiffuse(false)
|
||||||
|
.ambientOcclusion(TriState.FALSE)
|
||||||
|
.blendMode(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(template)))
|
||||||
|
.find();
|
||||||
|
|
||||||
|
BlockColorProvider tint = ColorProviderRegistry.BLOCK.get(block);
|
||||||
|
if(tint != null) globalTint = 0xFF000000 | tint.getColor(template, blockView, pos, 1);
|
||||||
|
else globalTint = 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Transformer(dir, appearance, material, globalTint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier) {
|
||||||
|
return new Transformer(Direction.EAST, tam.getDefaultAppearance(), r.materialFinder().clear().find(), 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static record Transformer(Direction dir, TemplateAppearance appearance, RenderMaterial material, int color) implements RenderContext.QuadTransform {
|
||||||
|
@Override
|
||||||
|
public boolean transform(MutableQuadView quad) {
|
||||||
|
quad.material(material);
|
||||||
|
|
||||||
|
switch(quad.tag()) {
|
||||||
|
case SlopeBaseMesh.TAG_SLOPE -> {
|
||||||
|
if(appearance.hasColor(Direction.UP)) quad.color(color, color, color, color);
|
||||||
|
paintSlope(quad, appearance.getSprite(Direction.UP));
|
||||||
|
}
|
||||||
|
case SlopeBaseMesh.TAG_LEFT -> {
|
||||||
|
final Direction leftDir = this.dir.rotateYCounterclockwise();
|
||||||
|
if(appearance.hasColor(leftDir)) quad.color(color, color, color, color);
|
||||||
|
paintLeftSide(quad, appearance.getSprite(leftDir));
|
||||||
|
}
|
||||||
|
case SlopeBaseMesh.TAG_RIGHT -> {
|
||||||
|
final Direction rightDir = this.dir.rotateYClockwise();
|
||||||
|
if(appearance.hasColor(rightDir)) quad.color(color, color, color, color);
|
||||||
|
paintRightSide(quad, appearance.getSprite(rightDir));
|
||||||
|
}
|
||||||
|
case SlopeBaseMesh.TAG_BACK -> {
|
||||||
|
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
||||||
|
paintBack(quad, appearance.getSprite(dir));
|
||||||
|
}
|
||||||
|
case SlopeBaseMesh.TAG_BOTTOM -> {
|
||||||
|
if(appearance.hasColor(Direction.DOWN)) quad.color(color, color, color, color);
|
||||||
|
paintBottom(quad, appearance.getSprite(Direction.DOWN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintSlope(MutableQuadView quad, Sprite sprite) {
|
||||||
|
switch(dir) {
|
||||||
|
case NORTH -> quad.uv(0, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(1, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(3, sprite.getMaxU(), sprite.getMinV());
|
||||||
|
case SOUTH -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(2, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
||||||
|
case EAST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMinV());
|
||||||
|
case WEST -> quad.uv(0, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(1, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(2, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(3, sprite.getMaxU(), sprite.getMaxV());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintLeftSide(MutableQuadView quad, Sprite sprite) {
|
||||||
|
switch(dir) {
|
||||||
|
case NORTH, EAST, WEST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMinV());
|
||||||
|
case SOUTH -> quad.uv(0, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(1, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(2, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(3, sprite.getMaxU(), sprite.getMaxV());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintRightSide(MutableQuadView quad, Sprite sprite) {
|
||||||
|
switch(dir) {
|
||||||
|
case NORTH, WEST -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(2, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
||||||
|
case SOUTH, EAST -> quad.uv(0, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(1, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(3, sprite.getMaxU(), sprite.getMinV());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintBack(MutableQuadView quad, Sprite sprite) {
|
||||||
|
switch(dir) {
|
||||||
|
case NORTH, EAST -> quad.uv(0, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(2, sprite.getMinU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMaxV());
|
||||||
|
case SOUTH, WEST -> quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMinV());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintBottom(MutableQuadView quad, Sprite sprite) {
|
||||||
|
quad.uv(0, sprite.getMinU(), sprite.getMaxV())
|
||||||
|
.uv(1, sprite.getMaxU(), sprite.getMaxV())
|
||||||
|
.uv(2, sprite.getMaxU(), sprite.getMinV())
|
||||||
|
.uv(3, sprite.getMinU(), sprite.getMinV());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,10 +26,13 @@ public class SlopeUnbakedModel implements UnbakedModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> function, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
||||||
//TODO: this is weird, should use my own model instead
|
//TODO: this is weird, should use my own model instead
|
||||||
BakedModel baseModel = baker.bake(BlockModels.getModelId(Blocks.SANDSTONE_STAIRS.getDefaultState()), modelBakeSettings);
|
BakedModel baseModel = baker.bake(BlockModels.getModelId(Blocks.SANDSTONE_STAIRS.getDefaultState()), modelBakeSettings);
|
||||||
|
|
||||||
return new SlopeBakedModel(baseModel, function, modelBakeSettings.getRotation());
|
//TODO: push this up (it's just a cache of data sourced from blockmodels, and can be cached until resource-reload)
|
||||||
|
TemplateAppearanceManager tam = new TemplateAppearanceManager(spriteLookup);
|
||||||
|
|
||||||
|
return new SlopeBakedModel(baseModel, tam, modelBakeSettings.getRotation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
|
||||||
import net.minecraft.client.render.model.BakedQuad;
|
|
||||||
import net.minecraft.client.texture.MissingSprite;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.client.texture.SpriteAtlasTexture;
|
|
||||||
import net.minecraft.client.util.SpriteIdentifier;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class SpriteSet {
|
|
||||||
public SpriteSet(Function<SpriteIdentifier, Sprite> spriteLookup) {
|
|
||||||
//TODO: I can probably find these from a public static location
|
|
||||||
// I think I tried that, though, and they were null. But I might have been doing it too early
|
|
||||||
// Regardless, they should not be stored in static fields (resource-reload could invalidate them)
|
|
||||||
this.defaultSprite = spriteLookup.apply(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/scaffolding_top")));
|
|
||||||
this.missingSprite = spriteLookup.apply(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, MissingSprite.getMissingSpriteId()));
|
|
||||||
|
|
||||||
if(defaultSprite == null) throw new IllegalStateException("defaultSprite == null!");
|
|
||||||
if(missingSprite == null) throw new IllegalStateException("missingSprite == null!");
|
|
||||||
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
|
||||||
|
|
||||||
private final Sprite defaultSprite;
|
|
||||||
private final Sprite missingSprite;
|
|
||||||
|
|
||||||
private final EnumMap<Direction, Sprite> sprites = new EnumMap<>(Direction.class);
|
|
||||||
private final EnumSet<Direction> hasColor = EnumSet.noneOf(Direction.class);
|
|
||||||
|
|
||||||
private boolean isDefault = true;
|
|
||||||
|
|
||||||
/** Allow re-use of instances to avoid allocation in render loop */
|
|
||||||
public void clear() {
|
|
||||||
isDefault = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Allow re-use of instances to avoid allocation in render loop */
|
|
||||||
//TODO: pass in block state?
|
|
||||||
public void inspect(BakedModel model, Random rand) {
|
|
||||||
sprites.clear();
|
|
||||||
hasColor.clear();
|
|
||||||
isDefault = false;
|
|
||||||
|
|
||||||
for(Direction dir : DIRECTIONS) {
|
|
||||||
List<BakedQuad> sideQuads = model.getQuads(null, dir, rand);
|
|
||||||
if(sideQuads.isEmpty()) continue;
|
|
||||||
|
|
||||||
BakedQuad arbitraryQuad = sideQuads.get(0); //maybe pick a largest quad instead?
|
|
||||||
if(arbitraryQuad == null) continue;
|
|
||||||
|
|
||||||
if(arbitraryQuad.hasColor()) hasColor.add(dir);
|
|
||||||
|
|
||||||
Sprite sprite = arbitraryQuad.getSprite();
|
|
||||||
if(sprite == null) continue;
|
|
||||||
|
|
||||||
sprites.put(dir, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sprite getSprite(Direction dir) {
|
|
||||||
if(isDefault) return defaultSprite;
|
|
||||||
else return sprites.getOrDefault(dir, missingSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasColor(Direction dir) {
|
|
||||||
if(isDefault) return false;
|
|
||||||
else return hasColor.contains(dir);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,34 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public interface TemplateAppearance {
|
||||||
|
@NotNull Sprite getParticleSprite(); //TODO: plug this in
|
||||||
|
@NotNull Sprite getSprite(Direction dir);
|
||||||
|
boolean hasColor(Direction dir);
|
||||||
|
|
||||||
|
record SingleSprite(@NotNull Sprite defaultSprite) implements TemplateAppearance {
|
||||||
|
public SingleSprite(Sprite defaultSprite) {
|
||||||
|
this.defaultSprite = Objects.requireNonNull(defaultSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Sprite getParticleSprite() {
|
||||||
|
return defaultSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Sprite getSprite(Direction dir) {
|
||||||
|
return defaultSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasColor(Direction dir) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.client.render.model.BakedQuad;
|
||||||
|
import net.minecraft.client.texture.MissingSprite;
|
||||||
|
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.math.Direction;
|
||||||
|
import net.minecraft.util.math.random.Random;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class TemplateAppearanceManager {
|
||||||
|
public TemplateAppearanceManager(Function<SpriteIdentifier, Sprite> spriteLookup) {
|
||||||
|
Sprite defaultSprite = spriteLookup.apply(DEFAULT_SPRITE_ID);
|
||||||
|
if(defaultSprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_ID + " !");
|
||||||
|
defaultAppearance = new TemplateAppearance.SingleSprite(defaultSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final SpriteIdentifier DEFAULT_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/scaffolding_top"));
|
||||||
|
private final TemplateAppearance defaultAppearance;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<BlockState, TemplateAppearance> appearanceCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public TemplateAppearance getDefaultAppearance() {
|
||||||
|
return defaultAppearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TemplateAppearance getAppearance(BlockState state) {
|
||||||
|
return appearanceCache.computeIfAbsent(state, this::computeAppearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplateAppearance computeAppearance(BlockState state) {
|
||||||
|
Random rand = Random.create();
|
||||||
|
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
|
||||||
|
|
||||||
|
Sprite[] sprites = new Sprite[7];
|
||||||
|
byte hasColorMask = 0b000000;
|
||||||
|
|
||||||
|
//Read quads off the model.
|
||||||
|
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 |= (1 << dir.ordinal());
|
||||||
|
|
||||||
|
Sprite sprite = arbitraryQuad.getSprite();
|
||||||
|
if(sprite == null) continue;
|
||||||
|
|
||||||
|
sprites[dir.ordinal()] = sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Just for space-usage purposes, we store the particle in sprites[6] instead of using another field.
|
||||||
|
sprites[6] = model.getParticleSprite();
|
||||||
|
|
||||||
|
//Fill out any missing values in the sprites array
|
||||||
|
for(int i = 0; i < sprites.length; i++) {
|
||||||
|
if(sprites[i] == null) sprites[i] = defaultAppearance.getParticleSprite();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComputedApperance(sprites, hasColorMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static record ComputedApperance(@NotNull Sprite[] sprites, byte hasColorMask) implements TemplateAppearance {
|
||||||
|
@Override
|
||||||
|
public @NotNull Sprite getParticleSprite() {
|
||||||
|
return sprites[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Sprite getSprite(Direction dir) {
|
||||||
|
return sprites[dir.ordinal()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasColor(Direction dir) {
|
||||||
|
return (hasColorMask & (1 << dir.ordinal())) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,9 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You're allowed (and encouraged) to also implement `QuadTransform` and return `this` from both of these methods.
|
* Please keep thread-safety in mind - `getQuads`/`emitBlockQuads` can be called concurrently from multiple worker threads.
|
||||||
*/
|
*/
|
||||||
public interface TemplateQuadTransformPreparer {
|
public interface TemplateQuadTransformFactory {
|
||||||
@NotNull RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier);
|
@NotNull RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier);
|
||||||
@NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier);
|
@NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user