package net.minecraft.client.renderer.block.model.multipart; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import java.util.BitSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.block.model.BlockModelPart; import net.minecraft.client.renderer.block.model.BlockStateModel; import net.minecraft.client.renderer.block.model.multipart.MultiPartModel.Unbaked.1; import net.minecraft.client.renderer.block.model.multipart.MultiPartModel.Unbaked.1Key; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ResolvableModel; import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class MultiPartModel implements BlockStateModel { private final MultiPartModel.SharedBakedState shared; private final BlockState blockState; @Nullable private List models; MultiPartModel(MultiPartModel.SharedBakedState shared, BlockState blockState) { this.shared = shared; this.blockState = blockState; } @Override public TextureAtlasSprite particleIcon() { return this.shared.particleIcon; } @Override public void collectParts(RandomSource random, List output) { if (this.models == null) { this.models = this.shared.selectModels(this.blockState); } long l = random.nextLong(); for (BlockStateModel blockStateModel : this.models) { random.setSeed(l); blockStateModel.collectParts(random, output); } } @Environment(EnvType.CLIENT) public record Selector(Predicate condition, T model) { public MultiPartModel.Selector with(S model) { return new MultiPartModel.Selector<>(this.condition, model); } } @Environment(EnvType.CLIENT) static final class SharedBakedState { private final List> selectors; final TextureAtlasSprite particleIcon; private final Map> subsets = new ConcurrentHashMap(); private static BlockStateModel getFirstModel(List> selectors) { if (selectors.isEmpty()) { throw new IllegalArgumentException("Model must have at least one selector"); } else { return (BlockStateModel)((MultiPartModel.Selector)selectors.getFirst()).model(); } } public SharedBakedState(List> selectors) { this.selectors = selectors; BlockStateModel blockStateModel = getFirstModel(selectors); this.particleIcon = blockStateModel.particleIcon(); } public List selectModels(BlockState state) { BitSet bitSet = new BitSet(); for (int i = 0; i < this.selectors.size(); i++) { if (((MultiPartModel.Selector)this.selectors.get(i)).condition.test(state)) { bitSet.set(i); } } return (List)this.subsets.computeIfAbsent(bitSet, bitSetx -> { Builder builder = ImmutableList.builder(); for (int ix = 0; ix < this.selectors.size(); ix++) { if (bitSetx.get(ix)) { builder.add((BlockStateModel)((MultiPartModel.Selector)this.selectors.get(ix)).model); } } return builder.build(); }); } } @Environment(EnvType.CLIENT) public static class Unbaked implements BlockStateModel.UnbakedRoot { final List> selectors; private final ModelBaker.SharedOperationKey sharedStateKey = new 1(this); public Unbaked(List> selectors) { this.selectors = selectors; } @Override public Object visualEqualityGroup(BlockState state) { IntList intList = new IntArrayList(); for (int i = 0; i < this.selectors.size(); i++) { if (((MultiPartModel.Selector)this.selectors.get(i)).condition.test(state)) { intList.add(i); } } return new 1Key(this, intList); } @Override public void resolveDependencies(ResolvableModel.Resolver resolver) { this.selectors.forEach(selector -> ((BlockStateModel.Unbaked)selector.model).resolveDependencies(resolver)); } @Override public BlockStateModel bake(BlockState state, ModelBaker baker) { MultiPartModel.SharedBakedState sharedBakedState = baker.compute(this.sharedStateKey); return new MultiPartModel(sharedBakedState, state); } } }