diff --git a/README.md b/README.md index 8fd4645..1ff5b65 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Template blocks can be placed in the world, then right-clicked with a full-size ## 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) * 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... diff --git a/src/main/java/io/github/cottonmc/templates/model/AffineQuadTransformer.java b/src/main/java/io/github/cottonmc/templates/model/AffineQuadTransformer.java index 4cde530..816e604 100644 --- a/src/main/java/io/github/cottonmc/templates/model/AffineQuadTransformer.java +++ b/src/main/java/io/github/cottonmc/templates/model/AffineQuadTransformer.java @@ -15,35 +15,23 @@ public record AffineQuadTransformer(Matrix4f affineMatrix) implements RenderCont @Override public boolean transform(MutableQuadView quad) { Vector3f pos3 = new Vector3f(); - Vector3f norm3 = new Vector3f(); - - //ugh - Vector4f pos4 = new Vector4f(); - Vector4f norm4 = new Vector4f(); + Vector4f pos4 = new Vector4f(); //ugh 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.copyNormal(i, norm3); - - pos3.add(-0.5f, -0.5f, -0.5f); //TODO, kinda a hack to center the affine transformation not at 0,0,0 - - //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 :) + //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); pos4.set(pos3, 0); - norm4.set(norm3, 1); //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 affineMatrix.transformAffine(pos4); - affineMatrix.transformAffine(norm4); //Manually copy the data back onto the vertex 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; diff --git a/src/main/java/io/github/cottonmc/templates/model/SlopeBakedModel.java b/src/main/java/io/github/cottonmc/templates/model/SlopeBakedModel.java index 36386c7..6ef0e69 100644 --- a/src/main/java/io/github/cottonmc/templates/model/SlopeBakedModel.java +++ b/src/main/java/io/github/cottonmc/templates/model/SlopeBakedModel.java @@ -1,31 +1,29 @@ 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.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.block.BlockState; 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.util.math.AffineTransformation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; -import java.util.function.Function; import java.util.function.Supplier; public final class SlopeBakedModel extends ForwardingBakedModel { - public SlopeBakedModel(BakedModel baseModel, Function spriteLookup, AffineTransformation aff) { + public SlopeBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, AffineTransformation aff) { this.wrapped = baseModel; - this.preparer = new SlopeMeshTransformPreparer(spriteLookup); + this.preparer = new SlopeQuadTransformFactory(tam); this.affineTransformer = new AffineQuadTransformer(aff); this.baseMesh = SlopeBaseMesh.make(); } - private final TemplateQuadTransformPreparer preparer; + private final TemplateQuadTransformFactory preparer; private final RenderContext.QuadTransform affineTransformer; private final Mesh baseMesh; diff --git a/src/main/java/io/github/cottonmc/templates/model/SlopeBaseMesh.java b/src/main/java/io/github/cottonmc/templates/model/SlopeBaseMesh.java index 4dc6fef..94ef801 100644 --- a/src/main/java/io/github/cottonmc/templates/model/SlopeBaseMesh.java +++ b/src/main/java/io/github/cottonmc/templates/model/SlopeBaseMesh.java @@ -19,11 +19,27 @@ public class SlopeBaseMesh { MeshBuilder builder = renderer.meshBuilder(); 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() - .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() - .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).tag(TAG_BOTTOM).pos(0, 0f, 0f, 0f).pos(1, 1f, 0f, 0f).pos(2, 1f, 0f, 1f).pos(3, 0f, 0f, 1f).emit(); + + qu.tag(TAG_SLOPE) + .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) + .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(); } } diff --git a/src/main/java/io/github/cottonmc/templates/model/SlopeMeshTransformPreparer.java b/src/main/java/io/github/cottonmc/templates/model/SlopeMeshTransformPreparer.java deleted file mode 100644 index d13bf11..0000000 --- a/src/main/java/io/github/cottonmc/templates/model/SlopeMeshTransformPreparer.java +++ /dev/null @@ -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 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 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 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()); - } -} diff --git a/src/main/java/io/github/cottonmc/templates/model/SlopeQuadTransformFactory.java b/src/main/java/io/github/cottonmc/templates/model/SlopeQuadTransformFactory.java new file mode 100644 index 0000000..f5ab33e --- /dev/null +++ b/src/main/java/io/github/cottonmc/templates/model/SlopeQuadTransformFactory.java @@ -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 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 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()); + } + } +} diff --git a/src/main/java/io/github/cottonmc/templates/model/SlopeUnbakedModel.java b/src/main/java/io/github/cottonmc/templates/model/SlopeUnbakedModel.java index 32c25c2..f20862f 100644 --- a/src/main/java/io/github/cottonmc/templates/model/SlopeUnbakedModel.java +++ b/src/main/java/io/github/cottonmc/templates/model/SlopeUnbakedModel.java @@ -26,10 +26,13 @@ public class SlopeUnbakedModel implements UnbakedModel { } @Override - public BakedModel bake(Baker baker, Function function, ModelBakeSettings modelBakeSettings, Identifier identifier) { + public BakedModel bake(Baker baker, Function spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) { //TODO: this is weird, should use my own model instead 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()); } } diff --git a/src/main/java/io/github/cottonmc/templates/model/SpriteSet.java b/src/main/java/io/github/cottonmc/templates/model/SpriteSet.java deleted file mode 100644 index 37b99dc..0000000 --- a/src/main/java/io/github/cottonmc/templates/model/SpriteSet.java +++ /dev/null @@ -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 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 sprites = new EnumMap<>(Direction.class); - private final EnumSet 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 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); - } -} diff --git a/src/main/java/io/github/cottonmc/templates/model/TemplateAppearance.java b/src/main/java/io/github/cottonmc/templates/model/TemplateAppearance.java new file mode 100644 index 0000000..47e725f --- /dev/null +++ b/src/main/java/io/github/cottonmc/templates/model/TemplateAppearance.java @@ -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; + } + } +} diff --git a/src/main/java/io/github/cottonmc/templates/model/TemplateAppearanceManager.java b/src/main/java/io/github/cottonmc/templates/model/TemplateAppearanceManager.java new file mode 100644 index 0000000..da47708 --- /dev/null +++ b/src/main/java/io/github/cottonmc/templates/model/TemplateAppearanceManager.java @@ -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 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 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 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; + } + } +} diff --git a/src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformPreparer.java b/src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformFactory.java similarity index 79% rename from src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformPreparer.java rename to src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformFactory.java index 64f5c10..f586d7d 100644 --- a/src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformPreparer.java +++ b/src/main/java/io/github/cottonmc/templates/model/TemplateQuadTransformFactory.java @@ -11,9 +11,9 @@ import org.jetbrains.annotations.NotNull; 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 randomSupplier); @NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier randomSupplier); }