1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +00:00

Refactor the turtle model code a little

Should make it easier to use in a multi-loader setting. This probably
still needs a bit more work: Dinnerbone turtles are still very broken.
This commit is contained in:
Jonathan Coates 2022-11-09 17:21:29 +00:00
parent cc73fcd85d
commit 34c7fcf750
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 106 additions and 56 deletions

View File

@ -0,0 +1,38 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.model.turtle;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.TransformedBakedModel;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.model.BakedModelWrapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TurtleModel extends BakedModelWrapper<BakedModel> {
private final TurtleModelParts parts;
private final Map<TurtleModelParts.Combination, List<BakedModel>> cachedModels = new HashMap<>();
public TurtleModel(BakedModel familyModel, BakedModel colourModel) {
super(familyModel);
parts = new TurtleModelParts(familyModel, colourModel, TransformedBakedModel::new);
}
@Override
public BakedModel applyTransform(ItemTransforms.TransformType cameraTransformType, PoseStack poseStack, boolean applyLeftHandTransform) {
originalModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform);
return this;
}
@Override
public List<BakedModel> getRenderPasses(ItemStack stack, boolean fabulous) {
return cachedModels.computeIfAbsent(parts.getCombination(stack), parts::buildModel);
}
}

View File

@ -3,46 +3,51 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
package dan200.computercraft.client.model.turtle;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.items.ItemTurtle;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.model.BakedModelWrapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class TurtleSmartItemModel extends BakedModelWrapper<BakedModel> {
/**
* Combines several individual models together to form a turtle.
*/
public final class TurtleModelParts {
private static final Transformation identity, flip;
static {
var stack = new PoseStack();
stack.scale(0, -1, 0);
stack.translate(0, 0, 1);
stack.translate(0.5f, 0.5f, 0.5f);
stack.scale(1, -1, 1);
stack.translate(-0.5f, -0.5f, -0.5f);
identity = Transformation.identity();
flip = new Transformation(stack.last().pose());
}
private record TurtleModelCombination(
public record Combination(
boolean colour,
ITurtleUpgrade leftUpgrade,
ITurtleUpgrade rightUpgrade,
ResourceLocation overlay,
@Nullable ITurtleUpgrade leftUpgrade,
@Nullable ITurtleUpgrade rightUpgrade,
@Nullable ResourceLocation overlay,
boolean christmas,
boolean flip
) {
@ -50,25 +55,21 @@ private record TurtleModelCombination(
private final BakedModel familyModel;
private final BakedModel colourModel;
private final Function<TransformedModel, BakedModel> transformer;
private final Map<TurtleModelCombination, List<BakedModel>> cachedModels = new HashMap<>();
/**
* 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 = new HashMap<>();
public TurtleSmartItemModel(BakedModel familyModel, BakedModel colourModel) {
super(familyModel);
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
this.familyModel = familyModel;
this.colourModel = colourModel;
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
}
@Nonnull
@Override
public BakedModel applyTransform(@Nonnull ItemTransforms.TransformType cameraTransformType, @Nonnull PoseStack poseStack, boolean applyLeftHandTransform) {
originalModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform);
return this;
}
@Nonnull
@Override
public List<BakedModel> getRenderPasses(ItemStack stack, boolean fabulous) {
public Combination getCombination(ItemStack stack) {
var turtle = (ItemTurtle) stack.getItem();
var colour = turtle.getColour(stack);
@ -79,29 +80,39 @@ public List<BakedModel> getRenderPasses(ItemStack stack, boolean fabulous) {
var label = turtle.getLabel(stack);
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
var combo = new TurtleModelCombination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
return cachedModels.computeIfAbsent(combo, this::buildModel);
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
}
private List<BakedModel> buildModel(TurtleModelCombination combo) {
public List<BakedModel> buildModel(Combination combo) {
var mc = Minecraft.getInstance();
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
var transformation = combo.flip ? flip : identity;
var parts = new ArrayList<BakedModel>(4);
parts.add(new TransformedBakedModel(combo.colour() ? colourModel : familyModel, transformation));
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
var overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas());
if (overlayModelLocation != null) {
parts.add(new TransformedBakedModel(modelManager.getModel(overlayModelLocation), transformation));
parts.add(transform(modelManager.getModel(overlayModelLocation), transformation));
}
if (combo.leftUpgrade() != null) {
parts.add(new TransformedBakedModel(TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT)).composeWith(transformation));
var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
if (combo.rightUpgrade() != null) {
parts.add(new TransformedBakedModel(TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT)).composeWith(transformation));
var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
return parts;
}
public 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);
}
}

View File

@ -29,7 +29,6 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraftforge.client.model.data.ModelData;
import javax.annotation.Nonnull;
import java.util.List;
@ -145,9 +144,9 @@ private void renderModel(@Nonnull PoseStack transform, @Nonnull VertexConsumer r
private void renderModel(@Nonnull PoseStack transform, @Nonnull VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, int[] tints) {
random.setSeed(0);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random, ModelData.EMPTY, null), tints);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
for (var facing : DirectionUtil.FACINGS) {
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random, ModelData.EMPTY, null), tints);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
}
}

View File

@ -6,7 +6,6 @@
package dan200.computercraft.client.render;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
@ -16,39 +15,41 @@
import net.minecraftforge.client.model.BakedModelWrapper;
import net.minecraftforge.client.model.QuadTransformers;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
public class TransformedBakedModel extends BakedModelWrapper<BakedModel> {
private final Transformation transformation;
private final boolean isIdentity;
private final boolean invert;
private @Nullable TransformedQuads cache;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
super(model);
this.transformation = transformation;
isIdentity = transformation.isIdentity();
invert = transformation.getNormalMatrix().determinant() < 0;
}
public TransformedBakedModel(TransformedModel model) {
this(model.getModel(), model.getMatrix());
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull RandomSource rand) {
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) {
return getQuads(state, side, rand, ModelData.EMPTY, null);
}
@Override
public @NotNull List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData extraData, @Nullable RenderType renderType) {
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) {
var cache = this.cache;
var quads = originalModel.getQuads(state, side, rand, extraData, renderType);
return isIdentity ? quads : QuadTransformers.applying(transformation).process(quads);
if (quads.isEmpty()) return List.of();
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
// so it's not worth being smarter here.
if (cache != null && quads.equals(cache.original())) return cache.transformed();
var transformed = QuadTransformers.applying(transformation).process(quads);
this.cache = new TransformedQuads(quads, transformed);
return transformed;
}
public TransformedBakedModel composeWith(Transformation other) {
return new TransformedBakedModel(originalModel, other.compose(transformation));
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
}
}

View File

@ -9,6 +9,7 @@
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.model.turtle.TurtleModel;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
@ -24,7 +25,7 @@
import java.util.Set;
import java.util.function.Function;
public final class TurtleModelLoader implements IGeometryLoader<TurtleModelLoader.TurtleModel> {
public final class TurtleModelLoader implements IGeometryLoader<TurtleModelLoader.Unbaked> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_colour");
public static final TurtleModelLoader INSTANCE = new TurtleModelLoader();
@ -34,15 +35,15 @@ private TurtleModelLoader() {
@Nonnull
@Override
public TurtleModel read(@Nonnull JsonObject modelContents, @Nonnull JsonDeserializationContext deserializationContext) {
public Unbaked read(@Nonnull JsonObject modelContents, @Nonnull JsonDeserializationContext deserializationContext) {
var model = new ResourceLocation(GsonHelper.getAsString(modelContents, "model"));
return new TurtleModel(model);
return new Unbaked(model);
}
public static final class TurtleModel implements IUnbakedGeometry<TurtleModel> {
public static final class Unbaked implements IUnbakedGeometry<Unbaked> {
private final ResourceLocation family;
private TurtleModel(ResourceLocation family) {
private Unbaked(ResourceLocation family) {
this.family = family;
}
@ -56,7 +57,7 @@ public Collection<Material> getMaterials(IGeometryBakingContext context, Functio
@Override
public BakedModel bake(IGeometryBakingContext owner, ModelBakery bakery, Function<Material, TextureAtlasSprite> spriteGetter, ModelState transform, ItemOverrides overrides, ResourceLocation modelLocation) {
return new TurtleSmartItemModel(
return new TurtleModel(
bakery.bake(family, transform, spriteGetter),
bakery.bake(COLOUR_TURTLE_MODEL, transform, spriteGetter)
);