SlopeBakedModel is now threadsafe - but at what cost

This commit is contained in:
quat1024 2023-07-02 04:26:37 -04:00
parent 338e45d016
commit 24c8c97f00
11 changed files with 337 additions and 295 deletions

View File

@ -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...

View File

@ -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;

View File

@ -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;

View File

@ -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();
} }
} }

View File

@ -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());
}
}

View File

@ -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());
}
}
}

View File

@ -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());
} }
} }

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
} }