diff --git a/build.gradle b/build.gradle index 1fcd3303..ee90f77d 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { } plugins { id 'net.franz-becker.gradle-lombok' version '1.5' - id 'com.matthewprenger.cursegradle' version '1.0.9' + id 'com.matthewprenger.cursegradle' version '1.4.0' } apply plugin: 'net.minecraftforge.gradle.forge' @@ -75,6 +75,9 @@ eclipse.classpath.file { } } +compileJava.options.encoding = 'UTF-8' +javadoc.options.encoding = 'UTF-8' + tasks.eclipse.dependsOn installLombok processResources { @@ -170,11 +173,9 @@ curseforge { changelog = System.getenv('CHANGELOG') == null || System.getenv('CHANGELOG').equals('none') ? getChangelogText() : System.getenv('CHANGELOG') changelogType = 'html' releaseType = project.curse_type - addGameVersion '1.12.1' addGameVersion '1.12.2' mainArtifact(jar) { displayName = "CTM - ${version}" } - addArtifact(apiJar) } } diff --git a/changelog.txt b/changelog.txt index 9d34dc7d..92988bff 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,27 @@ +1.0.2: +Fixes +- #115 Fix CTM models using the no-layer cache on the incorrect model, should fix the iChisel's preview mode + +1.0.1: +Changes +- If no layer is specified, the default will now be null (same as textures with no metadata) rather than SOLID +- Now requires forge 2807 at a minimum (for item lighting fixes) +Fixes +- Eliminated unnecessary memory overhead from model loading (asiekierka) +- Fix ctm models having duplicate quads when rendered as an item and containing null-layer quads +- Cache quad subsets for no-layer cases a bit, should speed up item model rendering marginally + +1.0.0: +New +- Added 'use_actual_state' flag to allow connected textures to compare contextual states (such as glass panes) +Changes +- Remove deprecated APIs +Fixes +- Fix models with CTM overrides not inheriting from texture data +- Fix disableCTM config not doing anything +- Fix CTM models not working properly with forge blockstate features (retexture, uvlock, etc.) +- #89 Fix CTM models not considering item overrides + 0.3.3: New - Models with light data will now render properly in item form. NOTE: This requires Forge 2781 or higher! diff --git a/gradle.properties b/gradle.properties index 3329bd98..f46c0022 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ -mod_version=0.4.0 +mod_version=1.0.2 minecraft_version=1.12.2 -forge_version=14.23.4.2705 +forge_version=14.23.5.2807 -curse_type=beta +curse_type=release projectId=267602 github_project=Chisel-Team/ConnectedTexturesMod \ No newline at end of file diff --git a/src/main/java/team/chisel/ctm/CTM.java b/src/main/java/team/chisel/ctm/CTM.java index 22bc6807..371af557 100644 --- a/src/main/java/team/chisel/ctm/CTM.java +++ b/src/main/java/team/chisel/ctm/CTM.java @@ -20,7 +20,7 @@ import team.chisel.ctm.client.util.CTMPackReloadListener; import team.chisel.ctm.client.util.TextureMetadataHandler; -@Mod(name = MOD_NAME, modid = MOD_ID, version = VERSION, dependencies = "before:chisel;after:forge@[14.23,)", clientSideOnly = true) +@Mod(name = MOD_NAME, modid = MOD_ID, version = VERSION, dependencies = "before:chisel;after:forge@[14.23.5.2807,)", clientSideOnly = true) public class CTM { public static final String MOD_ID = "ctm"; @@ -38,6 +38,7 @@ public void preInit(FMLPreInitializationEvent event) { TextureTypeRegistry.preInit(event); ModelLoaderRegistry.registerLoader(ModelLoaderCTM.INSTANCE); + MinecraftForge.EVENT_BUS.register(ModelLoaderCTM.INSTANCE); Minecraft.getMinecraft().metadataSerializer_.registerMetadataSectionType(new IMetadataSectionCTM.Serializer(), IMetadataSectionCTM.class); MinecraftForge.EVENT_BUS.register(TextureMetadataHandler.INSTANCE); diff --git a/src/main/java/team/chisel/ctm/Configurations.java b/src/main/java/team/chisel/ctm/Configurations.java index 4364f3d4..530a36cc 100644 --- a/src/main/java/team/chisel/ctm/Configurations.java +++ b/src/main/java/team/chisel/ctm/Configurations.java @@ -1,8 +1,16 @@ package team.chisel.ctm; +import net.minecraft.client.Minecraft; import net.minecraftforge.common.config.Config; +import net.minecraftforge.common.config.Config.Type; +import net.minecraftforge.common.config.ConfigManager; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import team.chisel.ctm.client.model.AbstractCTMBakedModel; @Config(modid = CTM.MOD_ID) +@EventBusSubscriber(modid = CTM.MOD_ID) public class Configurations { @Config.Comment("Disable connected textures entirely.") @@ -11,4 +19,12 @@ public class Configurations { @Config.Comment("Choose whether the inside corner is disconnected on a CTM block - http://imgur.com/eUywLZ4") public static boolean connectInsideCTM = false; + @SubscribeEvent + public static void onConfigChange(ConfigChangedEvent event) { + if (event.getModID().equals(CTM.MOD_ID)) { + ConfigManager.sync(CTM.MOD_ID, Type.INSTANCE); + AbstractCTMBakedModel.invalidateCaches(); + Minecraft.getMinecraft().renderGlobal.loadRenderers(); + } + } } diff --git a/src/main/java/team/chisel/ctm/api/IFacade.java b/src/main/java/team/chisel/ctm/api/IFacade.java index b1aa17d5..8e098e51 100644 --- a/src/main/java/team/chisel/ctm/api/IFacade.java +++ b/src/main/java/team/chisel/ctm/api/IFacade.java @@ -30,7 +30,7 @@ public interface IFacade { * The Blocks position * @param side * The side being rendered, NOT the side being connected from. - *

+ *

* This value can be null if no side is specified. Please handle this appropriately. * @param connection * The position of the block being connected to. diff --git a/src/main/java/team/chisel/ctm/api/model/IModelCTM.java b/src/main/java/team/chisel/ctm/api/model/IModelCTM.java index 5d23baab..3918e51b 100644 --- a/src/main/java/team/chisel/ctm/api/model/IModelCTM.java +++ b/src/main/java/team/chisel/ctm/api/model/IModelCTM.java @@ -7,8 +7,10 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; import net.minecraftforge.client.model.IModel; import team.chisel.ctm.api.texture.ICTMTexture; +import team.chisel.ctm.api.texture.IChiselFace; public interface IModelCTM extends IModel { @@ -16,9 +18,24 @@ public interface IModelCTM extends IModel { void load(); - Collection> getCTMTextures(); + @Deprecated + Collection> getChiselTextures(); + + default Collection> getCTMTextures() { + return getChiselTextures(); + } ICTMTexture getTexture(String iconName); + + @Deprecated + default IChiselFace getFace(EnumFacing facing) { + return null; + } + + @Deprecated + default IChiselFace getDefaultFace() { + return null; + } boolean canRenderInLayer(IBlockState state, BlockRenderLayer layer); diff --git a/src/main/java/team/chisel/ctm/api/texture/ICTMTexture.java b/src/main/java/team/chisel/ctm/api/texture/ICTMTexture.java index 2b913473..3daadf4b 100644 --- a/src/main/java/team/chisel/ctm/api/texture/ICTMTexture.java +++ b/src/main/java/team/chisel/ctm/api/texture/ICTMTexture.java @@ -48,11 +48,12 @@ public interface ICTMTexture { /** * The layer this texture requires. The layers will be prioritized for a face in the order: - *

- * {@link BlockRenderLayer#TRANSLUCENT}
- * {@link BlockRenderLayer#CUTOUT}
- * {@link BlockRenderLayer#SOLID}
- * + *

+ * * @return The layer of this texture. */ @Nullable diff --git a/src/main/java/team/chisel/ctm/api/texture/IChiselFace.java b/src/main/java/team/chisel/ctm/api/texture/IChiselFace.java new file mode 100644 index 00000000..25e933ae --- /dev/null +++ b/src/main/java/team/chisel/ctm/api/texture/IChiselFace.java @@ -0,0 +1,15 @@ +package team.chisel.ctm.api.texture; + +import java.util.List; + +import javax.annotation.Nonnull; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +@Deprecated +public interface IChiselFace { + + List> getTextureList(); + + @Nonnull TextureAtlasSprite getParticle(); +} \ No newline at end of file diff --git a/src/main/java/team/chisel/ctm/client/model/AbstractCTMBakedModel.java b/src/main/java/team/chisel/ctm/client/model/AbstractCTMBakedModel.java index bb299193..66110d6c 100644 --- a/src/main/java/team/chisel/ctm/client/model/AbstractCTMBakedModel.java +++ b/src/main/java/team/chisel/ctm/client/model/AbstractCTMBakedModel.java @@ -1,8 +1,11 @@ package team.chisel.ctm.client.model; +import java.util.Collection; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -15,6 +18,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; @@ -44,11 +48,14 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.client.MinecraftForgeClient; +import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.common.model.TRSRTransformation; import team.chisel.ctm.api.model.IModelCTM; import team.chisel.ctm.api.texture.ICTMTexture; +import team.chisel.ctm.api.texture.IChiselFace; import team.chisel.ctm.api.util.RenderContextList; import team.chisel.ctm.client.asm.CTMCoreMethods; import team.chisel.ctm.client.state.CTMExtendedState; @@ -81,6 +88,16 @@ public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, W block = ((ItemBlock) stack.getItem()).getBlock(); } final IBlockState state = block == null ? null : block.getDefaultState(); + if (!stack.isEmpty() && stack.getItem().hasCustomProperties()) { // Handle parent model's overrides + @SuppressWarnings("deprecation") // Duplicate super logic, but called on the parent model overrides + ResourceLocation location = parent.getOverrides().applyOverride(stack, world, entity); + if (location != null) { + // Use the override's location as cache key + ModelResourceLocation overrideLoc = ModelLoader.getInventoryVariant(location.toString()); + IBakedModel newParent = Minecraft.getMinecraft().getRenderItem().getItemModelMesher().getModelManager().getModel(overrideLoc); + return itemcache.get(overrideLoc, () -> withNewParent(newParent).createModel(state, model, null, 0)); + } + } ModelResourceLocation mrl = ModelUtil.getMesh(stack); if (mrl == null) { // this must be a missing/invalid model @@ -145,6 +162,9 @@ public int hashCode() { protected final ListMultimap genQuads = MultimapBuilder.enumKeys(BlockRenderLayer.class).arrayListValues().build(); protected final Table> faceQuads = Tables.newCustomTable(Maps.newEnumMap(BlockRenderLayer.class), () -> Maps.newEnumMap(EnumFacing.class)); + + private final EnumMap> noLayerCache = new EnumMap<>(EnumFacing.class); + private ImmutableList noSideNoLayerCache; @Override @SneakyThrows @@ -163,7 +183,7 @@ public int hashCode() { if (Minecraft.getMinecraft().world != null && state instanceof CTMExtendedState) { ProfileUtil.start("state_creation"); CTMExtendedState ext = (CTMExtendedState) state; - RenderContextList ctxList = ext.getContextList(ext.getClean(), model); + RenderContextList ctxList = ext.getContextList(ext.getClean(), baked); Object2LongMap> serialized = ctxList.serialized(); ProfileUtil.endAndStart("model_creation"); @@ -180,11 +200,22 @@ public int hashCode() { if (side != null && layer != null) { ret = baked.faceQuads.get(layer, side); } else if (side != null) { - ret = baked.faceQuads.column(side).values().stream().flatMap(List::stream).collect(Collectors.toList()); + final AbstractCTMBakedModel _baked = baked; + ret = baked.noLayerCache.computeIfAbsent(side, f -> ImmutableList.copyOf(_baked.faceQuads.column(f).values() + .stream() + .flatMap(List::stream) + .distinct() + .collect(Collectors.toList()))); } else if (layer != null) { ret = baked.genQuads.get(layer); } else { - ret = Lists.newArrayList(baked.genQuads.values()); + ret = baked.noSideNoLayerCache; + if (ret == null) { + ret = baked.noSideNoLayerCache = ImmutableList.copyOf(baked.genQuads.values() + .stream() + .distinct() + .collect(Collectors.toList())); + } } ProfileUtil.end(); @@ -223,9 +254,11 @@ public boolean isBuiltInRenderer() { return false; } + @SuppressWarnings("deprecation") @Override public @Nonnull TextureAtlasSprite getParticleTexture() { - return this.parent.getParticleTexture(); + IChiselFace face = this.model.getDefaultFace(); + return face != null ? face.getParticle() : this.parent.getParticleTexture(); } @Override @@ -261,5 +294,49 @@ public Pair handlePerspective(ItemCameraTransfo protected static final BlockRenderLayer[] LAYERS = BlockRenderLayer.values(); protected abstract AbstractCTMBakedModel createModel(IBlockState state, @Nonnull IModelCTM model, RenderContextList ctx, long rand); + + protected /* abstract */ AbstractCTMBakedModel withNewParent(@Nonnull IBakedModel parent) { + return new ModelBakedCTM(getModel(), parent); + } + + private T applyToParent(long rand, Function func) { + IBakedModel parent = getParent(rand); + if (parent instanceof AbstractCTMBakedModel) { + return func.apply((AbstractCTMBakedModel) parent); + } + return null; + } + + protected ICTMTexture getOverrideTexture(long rand, int tintIndex, String iconName) { + ICTMTexture ret = getModel().getOverrideTexture(tintIndex, iconName); + if (ret == null) { + ret = applyToParent(rand, parent -> parent.getOverrideTexture(rand, tintIndex, iconName)); + } + return ret; + } + + protected ICTMTexture getTexture(long rand, String iconName) { + ICTMTexture ret = getModel().getTexture(iconName); + if (ret == null) { + ret = applyToParent(rand, parent -> parent.getTexture(rand, iconName)); + } + return ret; + } + + protected TextureAtlasSprite getOverrideSprite(long rand, int tintIndex) { + TextureAtlasSprite ret = getModel().getOverrideSprite(tintIndex); + if (ret == null) { + ret = applyToParent(rand, parent -> parent.getOverrideSprite(rand, tintIndex)); + } + return ret; + } + public Collection> getCTMTextures() { + ImmutableList.Builder> builder = ImmutableList.builder(); + builder.addAll(getModel().getCTMTextures()); + if (getParent() instanceof AbstractCTMBakedModel) { + builder.addAll(((AbstractCTMBakedModel)getParent()).getCTMTextures()); + } + return builder.build(); + } } diff --git a/src/main/java/team/chisel/ctm/client/model/ModelBakedCTM.java b/src/main/java/team/chisel/ctm/client/model/ModelBakedCTM.java index fa60bbf4..a2b82dc0 100644 --- a/src/main/java/team/chisel/ctm/client/model/ModelBakedCTM.java +++ b/src/main/java/team/chisel/ctm/client/model/ModelBakedCTM.java @@ -61,13 +61,13 @@ protected AbstractCTMBakedModel createModel(@Nullable IBlockState state, IModelC // Gather all quads and map them to their textures // All quads should have an associated ICTMTexture, so ignore any that do not for (BakedQuad q : parentQuads) { - ICTMTexture tex = this.getModel().getOverrideTexture(q.getTintIndex(), q.getSprite().getIconName()); + ICTMTexture tex = this.getOverrideTexture(rand, q.getTintIndex(), q.getSprite().getIconName()); if (tex == null) { - tex = this.getModel().getTexture(q.getSprite().getIconName()); + tex = this.getTexture(rand, q.getSprite().getIconName()); } if (tex != null) { - TextureAtlasSprite spriteReplacement = getModel().getOverrideSprite(q.getTintIndex()); + TextureAtlasSprite spriteReplacement = this.getOverrideSprite(rand, q.getTintIndex()); if (spriteReplacement != null) { q = new BakedQuadRetextured(q, spriteReplacement); } @@ -91,7 +91,7 @@ protected AbstractCTMBakedModel createModel(@Nullable IBlockState state, IModelC } return ret; } - + @Override public @Nonnull TextureAtlasSprite getParticleTexture() { return Optional.ofNullable(getModel().getTexture(getParent().getParticleTexture().getIconName())) diff --git a/src/main/java/team/chisel/ctm/client/model/ModelCTM.java b/src/main/java/team/chisel/ctm/client/model/ModelCTM.java index 35d03fff..20debdce 100644 --- a/src/main/java/team/chisel/ctm/client/model/ModelCTM.java +++ b/src/main/java/team/chisel/ctm/client/model/ModelCTM.java @@ -47,7 +47,7 @@ import net.minecraftforge.client.model.IModel; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.common.model.IModelState; -import net.minecraftforge.common.model.TRSRTransformation; +import net.minecraftforge.common.model.animation.IClip; import team.chisel.ctm.api.model.IModelCTM; import team.chisel.ctm.api.texture.ICTMTexture; import team.chisel.ctm.api.util.TextureInfo; @@ -62,6 +62,7 @@ public class ModelCTM implements IModelCTM { private final ModelBlock modelinfo; private IModel vanillamodel; + private Boolean uvlock; // Populated from overrides data during construction private final Int2ObjectMap overrides; @@ -96,6 +97,10 @@ public ModelCTM(ModelBlock modelinfo, IModel vanillamodel, Int2ObjectMap getClip(String name) { + return getVanillaParent().getClip(name); } @Override public void load() {} - + @Override - public Collection> getCTMTextures() { + public Collection> getChiselTextures() { return ImmutableList.>builder().addAll(textures.values()).addAll(textureOverrides.values()).build(); } @@ -249,50 +263,101 @@ public ICTMTexture getOverrideTexture(int tintIndex, String sprite) { @Override public IModel retexture(ImmutableMap textures) { try { - return retexture(this, textures); + ModelCTM ret = deepCopy(getVanillaParent().retexture(textures), null, null); + + ret.modelinfo.textures.putAll(textures); + for (Entry e : ret.metaOverrides.entrySet()) { + ResourceLocation[] additionals = e.getValue().getAdditionalTextures(); + for (int i = 0; i < additionals.length; i++) { + ResourceLocation res = additionals[i]; + if (res.getResourcePath().startsWith("#")) { + String newTexture = textures.get(res.getResourcePath().substring(1)); + if (newTexture != null) { + additionals[i] = new ResourceLocation(newTexture); + ret.textureDependencies.add(additionals[i]); + } + } + } + } + for (int i : ret.overrides.keySet()) { + ret.overrides.compute(i, (idx, ele) -> { + if (ele.isJsonPrimitive() && ele.getAsJsonPrimitive().isString()) { + String newTexture = textures.get(ele.getAsString().substring(1)); + if (newTexture != null) { + ele = new JsonPrimitive(newTexture); + ret.textureDependencies.add(new ResourceLocation(ele.getAsString())); + } + } + return ele; + }); + } + return ret; } catch (IOException e) { e.printStackTrace(); return ModelLoaderRegistry.getMissingModel(); } } + + @Override + public IModel uvlock(boolean value) { + if (uvlock == null || uvlock.booleanValue() != value) { + IModel newParent = getVanillaParent().uvlock(value); + if (newParent != getVanillaParent()) { + IModel ret = deepCopyOrMissing(newParent, null, null); + if (ret instanceof ModelCTM) { + ((ModelCTM) ret).uvlock = value; + } + return ret; + } + } + return this; + } - private static ModelCTM retexture(ModelCTM current, ImmutableMap textures) throws IOException { - IModel vanillamodel = current.getVanillaParent().retexture(textures); + /** + * Allows the model to process custom data from the variant definition. + * If unknown data is encountered it should be skipped. + * @return a new model, with data applied. + */ + public IModel process(ImmutableMap customData) { + return deepCopyOrMissing(getVanillaParent().process(customData), null, null); + } + public IModel smoothLighting(boolean value) { + if (modelinfo.isAmbientOcclusion() != value) { + return deepCopyOrMissing(getVanillaParent().smoothLighting(value), value, null); + } + return this; + } + + public IModel gui3d(boolean value) { + if (modelinfo.isGui3d() != value) { + return deepCopyOrMissing(getVanillaParent().gui3d(value), null, value); + } + return this; + } + + private IModel deepCopyOrMissing(IModel newParent, Boolean ao, Boolean gui3d) { + try { + return deepCopy(newParent, ao, gui3d); + } catch (IOException e) { + e.printStackTrace(); + return ModelLoaderRegistry.getMissingModel(); + } + } + + private ModelCTM deepCopy(IModel newParent, Boolean ao, Boolean gui3d) throws IOException { // Deep copy logic taken from ModelLoader$VanillaModelWrapper List parts = new ArrayList<>(); - for (BlockPart part : current.modelinfo.getElements()) { + for (BlockPart part : modelinfo.getElements()) { parts.add(new BlockPart(part.positionFrom, part.positionTo, Maps.newHashMap(part.mapFaces), part.partRotation, part.shade)); } - ModelBlock newModel = new ModelBlock(current.modelinfo.getParentLocation(), parts, - Maps.newHashMap(current.modelinfo.textures), current.modelinfo.isAmbientOcclusion(), current.modelinfo.isGui3d(), - current.modelinfo.getAllTransforms(), Lists.newArrayList(current.modelinfo.getOverrides())); + ModelBlock newModel = new ModelBlock(modelinfo.getParentLocation(), parts, + Maps.newHashMap(modelinfo.textures), ao == null ? modelinfo.isAmbientOcclusion() : ao, gui3d == null ? modelinfo.isGui3d() : gui3d, + modelinfo.getAllTransforms(), Lists.newArrayList(modelinfo.getOverrides())); - newModel.name = current.modelinfo.name; - newModel.parent = current.modelinfo.parent; - ModelCTM ret = new ModelCTM(newModel, vanillamodel, new Int2ObjectArrayMap<>(current.overrides)); - - ret.modelinfo.textures.putAll(textures); - for (Entry e : ret.metaOverrides.entrySet()) { - ResourceLocation[] additionals = e.getValue().getAdditionalTextures(); - for (int i = 0; i < additionals.length; i++) { - ResourceLocation res = additionals[i]; - if (res.getResourcePath().startsWith("#")) { - additionals[i] = new ResourceLocation(textures.get(res.getResourcePath().substring(1))); - ret.textureDependencies.add(additionals[i]); - } - } - } - for (int i : ret.overrides.keySet()) { - ret.overrides.compute(i, (idx, ele) -> { - if (ele.isJsonPrimitive() && ele.getAsJsonPrimitive().isString()) { - ele = new JsonPrimitive(textures.get(ele.getAsString().substring(1))); - ret.textureDependencies.add(new ResourceLocation(ele.getAsString())); - } - return ele; - }); - } - return ret; + newModel.name = modelinfo.name; + newModel.parent = modelinfo.parent; + return new ModelCTM(newModel, newParent, new Int2ObjectArrayMap<>(overrides)); } } diff --git a/src/main/java/team/chisel/ctm/client/model/parsing/ModelLoaderCTM.java b/src/main/java/team/chisel/ctm/client/model/parsing/ModelLoaderCTM.java index 7ade6a1e..aa9de02d 100644 --- a/src/main/java/team/chisel/ctm/client/model/parsing/ModelLoaderCTM.java +++ b/src/main/java/team/chisel/ctm/client/model/parsing/ModelLoaderCTM.java @@ -1,6 +1,7 @@ package team.chisel.ctm.client.model.parsing; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; import java.util.Map; @@ -8,7 +9,12 @@ import javax.annotation.Nonnull; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -19,8 +25,11 @@ import net.minecraft.client.resources.IResource; import net.minecraft.client.resources.IResourceManager; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.ModelBakeEvent; import net.minecraftforge.client.model.ICustomModelLoader; import net.minecraftforge.client.model.IModel; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import team.chisel.ctm.api.model.IModelCTM; import team.chisel.ctm.api.model.IModelParser; @@ -31,13 +40,42 @@ public enum ModelLoaderCTM implements ICustomModelLoader { private static final Map parserVersions = ImmutableMap.of(1, new ModelParserV1()); private IResourceManager manager; - private Map jsonCache = Maps.newHashMap(); private Map loadedModels = Maps.newHashMap(); - + + private LoadingCache jsonCache = CacheBuilder.newBuilder().maximumSize(128).build( + new CacheLoader() { + @Override + @SuppressWarnings("null") + public JsonElement load(ResourceLocation modelLocation) throws Exception { + String path = modelLocation.getResourcePath() + ".json"; + if (!path.startsWith("models/")) { + path = "models/" + path; + } + ResourceLocation absolute = new ResourceLocation(modelLocation.getResourceDomain(), path); + + try (IResource resource = manager.getResource(absolute); + InputStream resourceInputStream = resource.getInputStream(); + InputStreamReader resourceInputStreamReader = new InputStreamReader(resourceInputStream)) { + JsonElement ele = new JsonParser().parse(resourceInputStreamReader); + if (ele != null) { + return ele; + } + } catch (Exception e) {} + + return JsonNull.INSTANCE; + } + } + ); + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void afterModelBaking(ModelBakeEvent event) { + jsonCache.invalidateAll(); + } + @Override public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { this.manager = resourceManager; - jsonCache.clear(); + jsonCache.invalidateAll(); loadedModels.clear(); } @@ -47,7 +85,7 @@ public boolean accepts(ResourceLocation modelLocation) { modelLocation = new ResourceLocation(modelLocation.getResourceDomain(), modelLocation.getResourcePath()); } - JsonElement json = getJSON(modelLocation); + JsonElement json = jsonCache.getUnchecked(modelLocation); return json.isJsonObject() && json.getAsJsonObject().has("ctm_version"); } @@ -60,27 +98,7 @@ public IModel loadModel(ResourceLocation modelLocation) throws IOException { } return model; } - - @SuppressWarnings("null") - public @Nonnull JsonElement getJSON(ResourceLocation modelLocation) { - return jsonCache.computeIfAbsent(modelLocation, res -> { - String path = modelLocation.getResourcePath() + ".json"; - if (!path.startsWith("models/")) { - path = "models/" + path; - } - ResourceLocation absolute = new ResourceLocation(modelLocation.getResourceDomain(), path); - try (IResource resource = manager.getResource(absolute)) { - JsonElement ele = new JsonParser().parse(new InputStreamReader(resource.getInputStream())); - if (ele != null) { - return ele; - } - } catch (Exception e) {} - - return JsonNull.INSTANCE; - }); - } - public static final Set parsedLocations = new HashSet<>(); private IModelCTM loadFromFile(ResourceLocation res, boolean forLoad) { @@ -88,7 +106,7 @@ private IModelCTM loadFromFile(ResourceLocation res, boolean forLoad) { parsedLocations.add(new ResourceLocation(res.getResourceDomain(), res.getResourcePath().replace("models/", ""))); } - JsonObject json = getJSON(res).getAsJsonObject(); + JsonObject json = jsonCache.getUnchecked(res).getAsJsonObject(); IModelParser parser = parserVersions.get(json.get("ctm_version").getAsInt()); if (parser == null) { diff --git a/src/main/java/team/chisel/ctm/client/state/CTMExtendedState.java b/src/main/java/team/chisel/ctm/client/state/CTMExtendedState.java index 609c79f1..e7603f91 100644 --- a/src/main/java/team/chisel/ctm/client/state/CTMExtendedState.java +++ b/src/main/java/team/chisel/ctm/client/state/CTMExtendedState.java @@ -20,6 +20,7 @@ import net.minecraftforge.common.property.IUnlistedProperty; import team.chisel.ctm.api.model.IModelCTM; import team.chisel.ctm.api.util.RenderContextList; +import team.chisel.ctm.client.model.AbstractCTMBakedModel; import team.chisel.ctm.client.util.ProfileUtil; @ParametersAreNonnullByDefault @@ -69,7 +70,7 @@ public CTMExtendedState(IBlockState state, CTMExtendedState parent) { this(state, parent.world, parent.pos); } - public RenderContextList getContextList(IBlockState state, IModelCTM model) { + public RenderContextList getContextList(IBlockState state, AbstractCTMBakedModel model) { if (ctxCache == null) { ctxCache = new RenderContextList(state, model.getCTMTextures(), world, pos); } diff --git a/src/main/java/team/chisel/ctm/client/texture/IMetadataSectionCTM.java b/src/main/java/team/chisel/ctm/client/texture/IMetadataSectionCTM.java index 37f62806..c3bd824a 100644 --- a/src/main/java/team/chisel/ctm/client/texture/IMetadataSectionCTM.java +++ b/src/main/java/team/chisel/ctm/client/texture/IMetadataSectionCTM.java @@ -75,7 +75,7 @@ default ICTMTexture makeTexture(TextureAtlasSprite sprite, Function textureCoords = new EnumMap<>(EnumFacing.class); private final long serialized; + protected BlockPos origin; @SuppressWarnings("null") public TextureContextGrid(BlockPos pos, TextureMap tex, boolean applyOffset) { + this(pos, pos, tex, applyOffset); + } + + @SuppressWarnings("null") + public TextureContextGrid(BlockPos pos, BlockPos origin, TextureMap tex, boolean applyOffset) { super(pos); + this.origin = origin; // Since we can only return a long, we must limit to 10 bits of data per face = 60 bits Preconditions.checkArgument(tex.getXSize() * tex.getYSize() < 1024, "V* Texture size too large for texture %s", tex.getParticle()); if (applyOffset) { - applyOffset(); + BlockPos offset = OffsetProviderRegistry.INSTANCE.getOffset(Minecraft.getMinecraft().world, position); + this.position = position.add(offset); + this.origin = origin.add(offset); } long serialized = 0; diff --git a/src/main/java/team/chisel/ctm/client/texture/render/TextureCTM.java b/src/main/java/team/chisel/ctm/client/texture/render/TextureCTM.java index 9e387457..aa412dbb 100644 --- a/src/main/java/team/chisel/ctm/client/texture/render/TextureCTM.java +++ b/src/main/java/team/chisel/ctm/client/texture/render/TextureCTM.java @@ -23,6 +23,7 @@ import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.util.EnumFacing; import net.minecraft.util.JsonUtils; +import team.chisel.ctm.Configurations; import team.chisel.ctm.api.texture.ITextureContext; import team.chisel.ctm.api.util.TextureInfo; import team.chisel.ctm.client.texture.ctx.TextureContextCTM; @@ -110,7 +111,7 @@ public boolean connectTo(CTMLogic ctm, IBlockState from, IBlockState to, EnumFac @Override public List transformQuad(BakedQuad bq, ITextureContext context, int quadGoal) { Quad quad = makeQuad(bq, context); - if (context == null) { + if (context == null || Configurations.disableCTM) { return Collections.singletonList(quad.transformUVs(sprites[0]).rebake()); } diff --git a/src/main/java/team/chisel/ctm/client/texture/render/TextureEdges.java b/src/main/java/team/chisel/ctm/client/texture/render/TextureEdges.java index eb9f827b..f48831be 100644 --- a/src/main/java/team/chisel/ctm/client/texture/render/TextureEdges.java +++ b/src/main/java/team/chisel/ctm/client/texture/render/TextureEdges.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import net.minecraft.client.renderer.block.model.BakedQuad; +import team.chisel.ctm.Configurations; import team.chisel.ctm.api.texture.ITextureContext; import team.chisel.ctm.api.util.TextureInfo; import team.chisel.ctm.client.texture.ctx.TextureContextCTM; @@ -23,7 +24,7 @@ public TextureEdges(TextureTypeEdges type, TextureInfo info) { @Override public List transformQuad(BakedQuad bq, ITextureContext context, int quadGoal) { Quad quad = makeQuad(bq, context); - if (context == null) { + if (context == null || Configurations.disableCTM) { return Collections.singletonList(quad.transformUVs(sprites[0]).rebake()); } diff --git a/src/main/java/team/chisel/ctm/client/texture/render/TextureMap.java b/src/main/java/team/chisel/ctm/client/texture/render/TextureMap.java index 98f8312c..9fc142a7 100644 --- a/src/main/java/team/chisel/ctm/client/texture/render/TextureMap.java +++ b/src/main/java/team/chisel/ctm/client/texture/render/TextureMap.java @@ -3,8 +3,11 @@ import com.google.common.base.Preconditions; import com.google.gson.JsonObject; import lombok.Getter; +import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; import team.chisel.ctm.api.texture.ISubmap; import team.chisel.ctm.api.texture.ITextureContext; import team.chisel.ctm.api.util.TextureInfo; @@ -58,7 +61,7 @@ protected List transformQuad(TextureMap tex, BakedQuad quad, @Nullabl } @Override - public ITextureContext getContext(@Nonnull BlockPos pos, @Nonnull TextureMap tex) { + public ITextureContext getContext(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull TextureMap tex) { return new TextureContextGrid.Random(pos, tex, true); } }, @@ -94,19 +97,60 @@ protected List transformQuad(TextureMap tex, BakedQuad quad, @Nullabl } @Override - public ITextureContext getContext(@Nonnull BlockPos pos, @Nonnull TextureMap tex) { - return new TextureContextGrid.Patterned(pos, tex, true); + public ITextureContext getContext(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull TextureMap tex) { + return new TextureContextGrid.Patterned(pos, findPatternOrigin(state, world, pos), tex, true); } }; protected abstract List transformQuad(TextureMap tex, BakedQuad quad, @Nullable ITextureContext context, int quadGoal); @Nonnull - public ITextureContext getContext(@Nonnull BlockPos pos, @Nonnull TextureMap tex) { + public ITextureContext getContext(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull TextureMap tex) { return new TextureContextPosition(pos); } } + private static BlockPos findPatternOrigin(IBlockState state, IBlockAccess world, BlockPos start) { + java.util.ArrayDeque pending = new java.util.ArrayDeque<>(); + java.util.HashSet visited = new java.util.HashSet<>(); + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + BlockPos origin = start; + + pending.add(start); + visited.add(start); + + while (!pending.isEmpty()) { + BlockPos current = pending.removeFirst(); + if (compareForPatternOrigin(current, origin) < 0) { + origin = current; + } + + for (EnumFacing facing : EnumFacing.VALUES) { + cursor.setPos(current).move(facing); + BlockPos next = cursor.toImmutable(); + if (visited.add(next) && world.getBlockState(next) == state) { + pending.addLast(next); + } + } + } + + return origin; + } + + private static int compareForPatternOrigin(BlockPos left, BlockPos right) { + int yCompare = Integer.compare(left.getY(), right.getY()); + if (yCompare != 0) { + return yCompare; + } + + int zCompare = Integer.compare(left.getZ(), right.getZ()); + if (zCompare != 0) { + return zCompare; + } + + return Integer.compare(left.getX(), right.getX()); + } + @Getter private final int xSize; @Getter diff --git a/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeCTM.java b/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeCTM.java index c22fb924..8b16df79 100644 --- a/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeCTM.java +++ b/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeCTM.java @@ -3,6 +3,7 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; +import team.chisel.ctm.Configurations; import team.chisel.ctm.api.texture.ICTMTexture; import team.chisel.ctm.api.texture.ITextureContext; import team.chisel.ctm.api.texture.ITextureType; @@ -26,7 +27,7 @@ public TextureContextCTM getBlockRenderContext(IBlockState state, IBlockAccess w @Override public int getQuadsPerSide() { - return 4; + return Configurations.disableCTM ? 1 : 4; } @Override diff --git a/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeMap.java b/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeMap.java index 85f9ce4e..73ff38b1 100644 --- a/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeMap.java +++ b/src/main/java/team/chisel/ctm/client/texture/type/TextureTypeMap.java @@ -28,7 +28,7 @@ public TextureMap makeTexture(TextureInfo info) { @Override public ITextureContext getBlockRenderContext(IBlockState state, IBlockAccess world, @Nonnull BlockPos pos, ICTMTexture tex) { - return type.getContext(pos, (TextureMap) tex); + return type.getContext(state, world, pos, (TextureMap) tex); } @Override diff --git a/src/main/java/team/chisel/ctm/client/util/CTMLogic.java b/src/main/java/team/chisel/ctm/client/util/CTMLogic.java index 7fd29eca..2d68f0d2 100644 --- a/src/main/java/team/chisel/ctm/client/util/CTMLogic.java +++ b/src/main/java/team/chisel/ctm/client/util/CTMLogic.java @@ -335,7 +335,7 @@ public final boolean isConnected(IBlockAccess world, BlockPos current, BlockPos * @param world * @param current * The position of your block. - * @param y + * @param connection * The position of the block to check against. * @param dir * The {@link EnumFacing side} of the block to check for connection status. This is not the direction to check in.