Support dyed shulker box upgrades

- Allow TransformedModel to also hold a list of tints for the model.

 - Update our turtle item colour provider to fetch tints from the
   upgrade's TransformedModel.

   This requires rewriting the quads in the upgrade model, mapping the
   existing tint indices to (2 * tintIndex) + (left ? 1 : 2). We already
   have to rewrite quads to apply transformations, so fortunately this
   isn't too bad.

   I have no clue how this is going to work on 26.3 though 😬.

 - Add a custom turtle upgrade modeller for the storage upgrade that
   applies the shulker tint colour.
This commit is contained in:
Jonathan Coates
2026-10-12 16:46:40 +01:00
parent 8730963ea8
commit 7b5fae17f3
39 changed files with 232 additions and 55 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ modDevGradle = "2.0.147"
nullAway = "0.14.0"
shadow = "9.6.1"
spotless = "8.10.0"
teavm = "0.15.0"
teavm = "0.16.0"
vanillaExtract = "0.3.2"
versionCatalogUpdate = "1.1.1"
@@ -12,8 +12,10 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
import java.util.function.IntUnaryOperator;
/**
* A model to render, combined with a transformation matrix to apply.
@@ -21,15 +23,20 @@ import java.util.Objects;
public final class TransformedModel {
private final BakedModel model;
private final Transformation matrix;
private final @Nullable IntUnaryOperator tints;
public TransformedModel(BakedModel model, Transformation matrix) {
public TransformedModel(BakedModel model, Transformation matrix, @Nullable IntUnaryOperator tints) {
this.model = Objects.requireNonNull(model);
this.matrix = Objects.requireNonNull(matrix);
this.tints = tints;
}
public TransformedModel(BakedModel model, Transformation matrix) {
this(model, matrix, null);
}
public TransformedModel(BakedModel model) {
this.model = Objects.requireNonNull(model);
matrix = Transformation.identity();
this(model, Transformation.identity(), null);
}
public static TransformedModel of(ModelResourceLocation location) {
@@ -54,4 +61,12 @@ public final class TransformedModel {
public Transformation getMatrix() {
return matrix;
}
public @Nullable IntUnaryOperator getTints() {
return tints;
}
public int getTint(int index) {
return tints == null ? -1 : tints.applyAsInt(index);
}
}
@@ -11,6 +11,7 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.CustomLecternRenderer;
@@ -18,6 +19,7 @@ import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleModemModeller;
import dan200.computercraft.client.turtle.TurtleStorageModeller;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
@@ -28,6 +30,7 @@ import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
@@ -121,10 +124,7 @@ public final class ClientRegistry {
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
register.register(ModRegistry.TurtleSerialisers.STORAGE.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_left"),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_right")
));
register.register(ModRegistry.TurtleSerialisers.STORAGE.get(), new TurtleStorageModeller());
}
@SafeVarargs
@@ -187,7 +187,15 @@ public final class ClientRegistry {
}
private static int getTurtleColour(ItemStack stack, int layer) {
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
var item = (TurtleItem) stack.getItem();
if (layer == 0) return item.getColour(stack);
var side = (layer % 2) == 1 ? TurtleSide.LEFT : TurtleSide.RIGHT;
var upgrade = item.getUpgradeWithData(stack, side);
if (upgrade == null) return -1;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
return model.getTint((layer - 1) / 2);
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
@@ -8,6 +8,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import com.mojang.math.Transformation;
import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
@@ -35,10 +36,12 @@ public class ModelTransformer {
protected final Matrix4f transformation;
protected final boolean invert;
private @Nullable TransformedQuads cache;
private final @Nullable TurtleSide side;
public ModelTransformer(Transformation transformation) {
public ModelTransformer(Transformation transformation, @Nullable TurtleSide side) {
this.transformation = transformation.getMatrix();
invert = transformation.getMatrix().determinant() < 0;
this.side = side;
}
public List<BakedQuad> transform(List<BakedQuad> quads) {
@@ -82,7 +85,14 @@ public class ModelTransformer {
}
var direction = Direction.rotate(transformation, quad.getDirection());
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
var tintIndex = getTintIndex(quad.getTintIndex());
return new BakedQuad(outputData, tintIndex, direction, quad.getSprite(), quad.isShade());
}
protected final int getTintIndex(int index) {
if (index == -1 || side == null) return index;
var offset = side == TurtleSide.LEFT ? 1 : 2;
return index * 2 + offset;
}
public static int getVertexOffset(int vertex, boolean invert) {
@@ -63,19 +63,22 @@ public final class TurtleModelParts<T> {
}
}
public record TurtleModelPart(BakedModel model, Transformation matrix, @Nullable TurtleSide side) {
}
private final BakedModel familyModel;
private final BakedModel colourModel;
private final Function<TransformedModel, BakedModel> transformer;
private final Function<TurtleModelPart, BakedModel> transformer;
private final Function<Combination, T> buildModel;
/**
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
*/
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
private final Map<TurtleModelPart, BakedModel> transformCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(30, TimeUnit.SECONDS)
.<TransformedModel, BakedModel>build()
.<TurtleModelPart, BakedModel>build()
.asMap();
/**
@@ -87,10 +90,10 @@ public final class TurtleModelParts<T> {
.<Combination, T>build()
.asMap();
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, Function<TurtleModelPart, BakedModel> transformer, Function<List<BakedModel>, T> combineModel) {
this.familyModel = familyModel;
this.colourModel = colourModel;
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
this.transformer = transformer;
buildModel = x -> combineModel.apply(buildModel(x));
}
@@ -129,11 +132,11 @@ public final class TurtleModelParts<T> {
var transformation = combo.flip ? flip : identity;
var parts = new ArrayList<BakedModel>(4);
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation, null));
var overlayModelLocation = TurtleBlockEntityRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas());
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation, null));
}
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
@@ -145,15 +148,11 @@ public final class TurtleModelParts<T> {
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix()), side));
}
private BakedModel transform(BakedModel model, Transformation transformation) {
if (transformation.equals(Transformation.identity())) return model;
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
}
public interface ModelTransformer {
BakedModel transform(BakedModel model, Transformation transformation);
private BakedModel transform(BakedModel model, Transformation transformation, @Nullable TurtleSide side) {
if (transformation.equals(Transformation.identity()) && side == null) return model;
return transformCache.computeIfAbsent(new TurtleModelPart(model, transformation, side), transformer);
}
}
@@ -15,6 +15,8 @@ import net.minecraft.network.protocol.game.ServerGamePacketListener;
import net.minecraft.sounds.SoundEvent;
import org.jspecify.annotations.Nullable;
import java.util.function.IntUnaryOperator;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
return (ClientPlatformHelper) dan200.computercraft.impl.client.ClientPlatformHelper.get();
@@ -38,7 +40,7 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints);
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable IntUnaryOperator tints);
/**
* Play a record at a particular position.
@@ -17,6 +17,7 @@ import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.function.IntUnaryOperator;
/**
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
@@ -28,7 +29,7 @@ public final class ModelRenderer {
/**
* Render a list of {@linkplain BakedQuad quads} to a buffer.
* <p>
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, IntUnaryOperator)}. The
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
* but supports inverted quads (i.e. those with a negative scale).
*
@@ -39,16 +40,12 @@ public final class ModelRenderer {
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable IntUnaryOperator tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var tint = tints != null && bakedquad.isTinted() ? tints.applyAsInt(bakedquad.getTintIndex()) : -1;
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
@@ -25,6 +25,8 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
import java.util.function.IntUnaryOperator;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
@@ -93,7 +95,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
} else {
// Otherwise render it using the colour item.
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, x -> x == 0 ? colour : -1);
}
// Render the overlay
@@ -121,12 +123,12 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
applyTransformation(transform, model.getMatrix());
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), model.getTints());
transform.popPose();
}
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, int @Nullable [] tints) {
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable IntUnaryOperator tints) {
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
@@ -139,10 +141,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
* @param lightmapCoord The current lightmap coordinate.
* @param overlayLight The overlay light.
* @param model The model to render.
* @param tints Tints for the quads, as an array of RGB values.
* @param tints Tints for the quads.
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
*/
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, int @Nullable [] tints) {
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable IntUnaryOperator tints) {
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
}
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.turtle;
import com.mojang.math.Transformation;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import dan200.computercraft.shared.turtle.upgrades.TurtleStorage;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.function.IntUnaryOperator;
/**
* {@link TurtleUpgradeModeller} implementation for {@link TurtleStorage}. This provides a custom tinted model for dyed
* shulker boxes.
*/
public class TurtleStorageModeller implements TurtleUpgradeModeller<TurtleStorage> {
private static final ResourceLocation LEFT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_left");
private static final ResourceLocation RIGHT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_right");
private static final ResourceLocation LEFT_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_colour_left");
private static final ResourceLocation RIGHT_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker_colour_right");
private static final EnumMap<DyeColor, IntUnaryOperator> SHULKER_COLOURS = Util.make(new EnumMap<>(DyeColor.class), m -> {
m.put(DyeColor.WHITE, colorGetter(0xFFE6EAEA));
m.put(DyeColor.ORANGE, colorGetter(0xFFF57410));
m.put(DyeColor.MAGENTA, colorGetter(0xFFBA3FAF));
m.put(DyeColor.LIGHT_BLUE, colorGetter(0xFF3BB4DB));
m.put(DyeColor.YELLOW, colorGetter(0xFFFCC724));
m.put(DyeColor.LIME, colorGetter(0xFF71BC18));
m.put(DyeColor.PINK, colorGetter(0xFFF38BAA));
m.put(DyeColor.GRAY, colorGetter(0xFF3E4246));
m.put(DyeColor.LIGHT_GRAY, colorGetter(0xFF8C8C83));
m.put(DyeColor.CYAN, colorGetter(0xFF168792));
m.put(DyeColor.PURPLE, colorGetter(0xFF7426A9));
m.put(DyeColor.BLUE, colorGetter(0xFF33359B));
m.put(DyeColor.BROWN, colorGetter(0xFF734828));
m.put(DyeColor.GREEN, colorGetter(0xFF546D1C));
m.put(DyeColor.RED, colorGetter(0xFF9C2522));
m.put(DyeColor.BLACK, colorGetter(0xFF1F1F23));
});
private static IntUnaryOperator colorGetter(int color) {
return layer -> layer == 0 ? color : -1;
}
@Override
public TransformedModel getModel(TurtleStorage upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
var item = upgrade.getCraftingItem().getItem();
if (item instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock shulkerBoxBlock) {
var color = shulkerBoxBlock.getColor();
if (color == null) {
return TransformedModel.of(side == TurtleSide.LEFT ? LEFT : RIGHT);
} else {
var modelManager = Minecraft.getInstance().getModelManager();
var model = ClientPlatformHelper.get().getModel(modelManager, side == TurtleSide.LEFT ? LEFT_COLOUR : RIGHT_COLOUR);
return new TransformedModel(model, Transformation.identity(), SHULKER_COLOURS.get(color));
}
} else {
return TurtleUpgradeModeller.flatItem().getModel(upgrade, turtle, side);
}
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(LEFT, RIGHT, LEFT_COLOUR, RIGHT_COLOUR);
}
}
@@ -100,7 +100,7 @@ class BlockModelProvider {
registerRedstoneControl(generators);
registerTurtleUpgrade(generators, "block/turtle_crafting_table", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafty_face"));
registerTurtleUpgrade(generators, "block/turtle_shulker", new ResourceLocation(ResourceLocation.DEFAULT_NAMESPACE, "block/shulker_box"));
registerTurtleUpgrade(generators, "block/turtle_shulker", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_shulker"));
registerTurtleUpgrade(generators, "block/turtle_speaker", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_face"));
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
@@ -101,8 +101,16 @@ public final class DataProviders {
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
for (var item : BuiltInRegistries.ITEM) {
if (item instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock) {
// Shuffle the name about, so blue_shulker_box becomes shulker/blue. This is a bit of a hack to
// ensure they're all grouped together in the creative tab.
var name = PlatformHelper.get().getRegistryKey(Registries.ITEM, item).getPath();
simpleWithCustomItem(new ResourceLocation(ComputerCraftAPI.MOD_ID, name), ModRegistry.TurtleSerialisers.STORAGE.get(), item).add(addUpgrade);
if (name.endsWith("shulker_box")) {
name = name.substring(0, name.length() - "shulker_box".length());
}
if (name.endsWith("_")) name = name.substring(0, name.length() - 1);
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name.isEmpty() ? "shulker" : "shulker/" + name);
simpleWithCustomItem(id, ModRegistry.TurtleSerialisers.STORAGE.get(), item).add(addUpgrade);
}
}
}
@@ -1 +1,4 @@
{"parent": "computercraft:block/turtle_upgrade_base_left", "textures": {"texture": "minecraft:block/shulker_box"}}
{
"parent": "computercraft:block/turtle_upgrade_base_left",
"textures": {"texture": "computercraft:block/turtle_shulker"}
}
@@ -1 +1,4 @@
{"parent": "computercraft:block/turtle_upgrade_base_right", "textures": {"texture": "minecraft:block/shulker_box"}}
{
"parent": "computercraft:block/turtle_upgrade_base_right",
"textures": {"texture": "computercraft:block/turtle_shulker"}
}
@@ -0,0 +1,20 @@
{
"parent": "block/block",
"textures": {
"texture": "computercraft:block/turtle_shulker_colour",
"particle": "#texture"
},
"elements": [
{
"from": [ 0.5, 4.5, 3.5 ],
"to": [ 2, 12.5, 11.5 ],
"faces": {
"down": { "uv": [ 2, 14, 14, 16 ], "texture": "#texture", "rotation": 270, "tintindex": 0 },
"up": { "uv": [ 2, 0, 14, 3 ], "texture": "#texture", "rotation": 90, "tintindex": 0 },
"north": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture", "tintindex": 0 },
"south": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture", "tintindex": 0 },
"west": { "uv": [ 2, 2, 14, 14 ], "texture": "#texture", "tintindex": 0 }
}
}
]
}
@@ -0,0 +1,20 @@
{
"parent": "block/block",
"textures": {
"texture": "computercraft:block/turtle_shulker_colour",
"particle": "#texture"
},
"elements": [
{
"from": [ 14, 4.5, 3.5 ],
"to": [ 15.5, 12.5, 11.5 ],
"faces": {
"down": { "uv": [ 2, 14, 14, 16 ], "texture": "#texture", "rotation": 90, "tintindex": 0 },
"up": { "uv": [ 2, 0, 14, 3 ], "texture": "#texture", "rotation": 270, "tintindex": 0 },
"north": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture", "tintindex": 0 },
"south": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture", "tintindex": 0 },
"east": { "uv": [ 2, 2, 14, 14 ], "texture": "#texture", "tintindex": 0 }
}
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

@@ -5,18 +5,20 @@
package dan200.computercraft.client.model;
import com.mojang.math.Transformation;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.core.Direction;
import org.joml.Vector3f;
import org.jspecify.annotations.Nullable;
/**
* Extends {@link ModelTransformer} to also work as a {@link RenderContext.QuadTransform}.
*/
public class FabricModelTransformer extends ModelTransformer implements RenderContext.QuadTransform {
public FabricModelTransformer(Transformation transformation) {
super(transformation);
public FabricModelTransformer(Transformation transformation, @Nullable TurtleSide side) {
super(transformation, side);
}
@Override
@@ -36,6 +38,8 @@ public class FabricModelTransformer extends ModelTransformer implements RenderCo
var face = quad.nominalFace();
if (face != null) quad.nominalFace(Direction.rotate(transformation, face));
quad.colorIndex(getTintIndex(quad.colorIndex()));
return true;
}
@@ -4,8 +4,8 @@
package dan200.computercraft.client.model;
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.model.turtle.TurtleModelParts;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BakedQuad;
@@ -29,9 +29,9 @@ import java.util.function.Supplier;
public class TransformedBakedModel extends ForwardingBakedModel {
private final FabricModelTransformer transformation;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
wrapped = model;
this.transformation = new FabricModelTransformer(transformation);
public TransformedBakedModel(TurtleModelParts.TurtleModelPart part) {
wrapped = part.model();
this.transformation = new FabricModelTransformer(part.matrix(), part.side());
}
@Override
@@ -28,6 +28,8 @@ import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.RandomSource;
import org.jspecify.annotations.Nullable;
import java.util.function.IntUnaryOperator;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
@@ -51,7 +53,7 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable IntUnaryOperator tints) {
// Unfortunately we can't call Fabric's emitItemQuads here, as there's no way to obtain a RenderContext via the
// API. Instead, we special case our FoiledModel, and just render everything else normally.
var buffer = ItemRenderer.getFoilBuffer(buffers, Sheets.translucentCullBlockSheet(), true, model instanceof FoiledModel);
@@ -4,8 +4,8 @@
package dan200.computercraft.client.model;
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.model.turtle.TurtleModelParts;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
@@ -26,9 +26,9 @@ import java.util.List;
public class TransformedBakedModel extends BakedModelWrapper<BakedModel> {
private final ModelTransformer transformation;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
super(model);
this.transformation = new ModelTransformer(transformation);
public TransformedBakedModel(TurtleModelParts.TurtleModelPart part) {
super(part.model());
this.transformation = new ModelTransformer(part.matrix(), part.side());
}
@Override
@@ -27,6 +27,7 @@ import net.minecraftforge.client.model.data.ModelData;
import org.jspecify.annotations.Nullable;
import java.util.Arrays;
import java.util.function.IntUnaryOperator;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@@ -49,7 +50,7 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable IntUnaryOperator tints) {
for (var renderType : model.getRenderTypes(ItemStack.EMPTY, true)) {
var buffer = buffers.getBuffer(renderType);
for (var face : directions) {