package net.minecraft.client.resources.model; import com.mojang.logging.LogUtils; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.block.model.BlockStateModel; import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.block.model.SimpleModelWrapper; import net.minecraft.client.renderer.block.model.SingleVariant; import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.item.MissingItemModel; import net.minecraft.client.renderer.item.ModelRenderProperties; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.thread.ParallelMapTransform; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.slf4j.Logger; @OnlyIn(Dist.CLIENT) public class ModelBakery { public static final Material FIRE_0 = new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.withDefaultNamespace("block/fire_0")); public static final Material FIRE_1 = new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.withDefaultNamespace("block/fire_1")); public static final Material LAVA_FLOW = new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.withDefaultNamespace("block/lava_flow")); public static final Material WATER_FLOW = new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.withDefaultNamespace("block/water_flow")); public static final Material WATER_OVERLAY = new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.withDefaultNamespace("block/water_overlay")); public static final Material BANNER_BASE = new Material(Sheets.BANNER_SHEET, ResourceLocation.withDefaultNamespace("entity/banner_base")); public static final Material SHIELD_BASE = new Material(Sheets.SHIELD_SHEET, ResourceLocation.withDefaultNamespace("entity/shield_base")); public static final Material NO_PATTERN_SHIELD = new Material(Sheets.SHIELD_SHEET, ResourceLocation.withDefaultNamespace("entity/shield_base_nopattern")); public static final int DESTROY_STAGE_COUNT = 10; public static final List DESTROY_STAGES = IntStream.range(0, 10) .mapToObj(p_340955_ -> ResourceLocation.withDefaultNamespace("block/destroy_stage_" + p_340955_)) .collect(Collectors.toList()); public static final List BREAKING_LOCATIONS = DESTROY_STAGES.stream() .map(p_340960_ -> p_340960_.withPath(p_340956_ -> "textures/" + p_340956_ + ".png")) .collect(Collectors.toList()); public static final List DESTROY_TYPES = BREAKING_LOCATIONS.stream().map(RenderType::crumbling).collect(Collectors.toList()); static final Logger LOGGER = LogUtils.getLogger(); private final EntityModelSet entityModelSet; private final Map unbakedBlockStateModels; private final Map clientInfos; final Map resolvedModels; final ResolvedModel missingModel; public ModelBakery( EntityModelSet p_376026_, Map p_251087_, Map p_250416_, Map p_375852_, ResolvedModel p_393546_ ) { this.entityModelSet = p_376026_; this.unbakedBlockStateModels = p_251087_; this.clientInfos = p_250416_; this.resolvedModels = p_375852_; this.missingModel = p_393546_; } public CompletableFuture bakeModels(SpriteGetter p_393789_, Executor p_392289_) { ModelBakery.MissingModels modelbakery$missingmodels = ModelBakery.MissingModels.bake(this.missingModel, p_393789_); ModelBakery.ModelBakerImpl modelbakery$modelbakerimpl = new ModelBakery.ModelBakerImpl(p_393789_); CompletableFuture> completablefuture = ParallelMapTransform.schedule(this.unbakedBlockStateModels, (p_389589_, p_389590_) -> { try { return p_389590_.bake(p_389589_, modelbakery$modelbakerimpl); } catch (Exception exception) { LOGGER.warn("Unable to bake model: '{}': {}", p_389589_, exception); return null; } }, p_392289_); CompletableFuture> completablefuture1 = ParallelMapTransform.schedule( this.clientInfos, (p_389600_, p_389601_) -> { try { return p_389601_.model() .bake( new ItemModel.BakingContext(modelbakery$modelbakerimpl, this.entityModelSet, modelbakery$missingmodels.item, p_389601_.registrySwapper()) ); } catch (Exception exception) { LOGGER.warn("Unable to bake item model: '{}'", p_389600_, exception); return null; } }, p_392289_ ); Map map = new HashMap<>(this.clientInfos.size()); this.clientInfos.forEach((p_389592_, p_389593_) -> { ClientItem.Properties clientitem$properties = p_389593_.properties(); if (!clientitem$properties.equals(ClientItem.Properties.DEFAULT)) { map.put(p_389592_, clientitem$properties); } }); return completablefuture.thenCombine( completablefuture1, (p_389596_, p_389597_) -> new ModelBakery.BakingResult( modelbakery$missingmodels, (Map)p_389596_, (Map)p_389597_, map ) ); } @OnlyIn(Dist.CLIENT) public record BakingResult( ModelBakery.MissingModels missingModels, Map blockStateModels, Map itemStackModels, Map itemProperties ) { } @OnlyIn(Dist.CLIENT) public record MissingModels(BlockStateModel block, ItemModel item) { public static ModelBakery.MissingModels bake(ResolvedModel p_395506_, final SpriteGetter p_393671_) { ModelBaker modelbaker = new ModelBaker() { @Override public ResolvedModel getModel(ResourceLocation p_394244_) { throw new IllegalStateException("Missing model can't have dependencies, but asked for " + p_394244_); } @Override public T compute(ModelBaker.SharedOperationKey p_396793_) { return p_396793_.compute(this); } @Override public SpriteGetter sprites() { return p_393671_; } }; TextureSlots textureslots = p_395506_.getTopTextureSlots(); boolean flag = p_395506_.getTopAmbientOcclusion(); boolean flag1 = p_395506_.getTopGuiLight().lightLikeBlock(); ItemTransforms itemtransforms = p_395506_.getTopTransforms(); QuadCollection quadcollection = p_395506_.bakeTopGeometry(textureslots, modelbaker, BlockModelRotation.X0_Y0); TextureAtlasSprite textureatlassprite = p_395506_.resolveParticleSprite(textureslots, modelbaker); BlockStateModel blockstatemodel = new SingleVariant(new SimpleModelWrapper(quadcollection, flag, textureatlassprite)); ItemModel itemmodel = new MissingItemModel(quadcollection.getAll(), new ModelRenderProperties(flag1, textureatlassprite, itemtransforms)); return new ModelBakery.MissingModels(blockstatemodel, itemmodel); } } @OnlyIn(Dist.CLIENT) class ModelBakerImpl implements ModelBaker { private final SpriteGetter sprites; private final Map, Object> operationCache = new ConcurrentHashMap<>(); private final Function, Object> cacheComputeFunction = p_395291_ -> p_395291_.compute(this); ModelBakerImpl(final SpriteGetter p_393058_) { this.sprites = p_393058_; } @Override public SpriteGetter sprites() { return this.sprites; } @Override public ResolvedModel getModel(ResourceLocation p_248568_) { ResolvedModel resolvedmodel = ModelBakery.this.resolvedModels.get(p_248568_); if (resolvedmodel == null) { ModelBakery.LOGGER.warn("Requested a model that was not discovered previously: {}", p_248568_); return ModelBakery.this.missingModel; } else { return resolvedmodel; } } @Override public T compute(ModelBaker.SharedOperationKey p_393371_) { return (T)this.operationCache.computeIfAbsent((ModelBaker.SharedOperationKey)p_393371_, this.cacheComputeFunction); } } }