/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>. */ package appeng.client.render.model; import java.io.Closeable; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.util.vector.Vector3f; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockFaceUV; import net.minecraft.client.renderer.block.model.BlockPart; import net.minecraft.client.renderer.block.model.BlockPartFace; import net.minecraft.client.renderer.block.model.BlockPartRotation; import net.minecraft.client.renderer.block.model.FaceBakery; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemOverride; import net.minecraft.client.renderer.block.model.ItemTransformVec3f; import net.minecraft.client.renderer.block.model.ModelBakery; import net.minecraft.client.renderer.block.model.ModelBlock; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.resources.IResource; import net.minecraft.client.resources.IResourceManager; import net.minecraft.util.EnumFacing; import net.minecraft.util.JsonUtils; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.ICustomModelLoader; import net.minecraftforge.client.model.IModel; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.animation.ModelBlockAnimation; import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; import net.minecraftforge.client.model.pipeline.VertexLighterFlat; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.ITransformation; import net.minecraftforge.fml.relauncher.ReflectionHelper; import appeng.client.render.VertexFormats; public enum UVLModelLoader implements ICustomModelLoader { INSTANCE; private static final Gson gson = new Gson(); private static final Constructor<? extends IModel> vanillaModelWrapper; private static final Field faceBakery; private static final Object vanillaLoader; private static final MethodHandle loaderGetter; static { try { Field modifiers = Field.class.getDeclaredField( "modifiers" ); modifiers.setAccessible( true ); faceBakery = ReflectionHelper.findField( ModelBakery.class, "faceBakery", "field_177607_l" ); modifiers.set( faceBakery, faceBakery.getModifiers() & ( ~Modifier.FINAL ) ); Class clas = Class.forName( ModelLoader.class.getName() + "$VanillaModelWrapper" ); vanillaModelWrapper = clas.getDeclaredConstructor( ModelLoader.class, ResourceLocation.class, ModelBlock.class, boolean.class, ModelBlockAnimation.class ); vanillaModelWrapper.setAccessible( true ); Class<?> vanillaLoaderClass = Class.forName( ModelLoader.class.getName() + "$VanillaLoader" ); Field instanceField = vanillaLoaderClass.getField( "INSTANCE" ); // Static field vanillaLoader = instanceField.get( null ); Field loaderField = vanillaLoaderClass.getDeclaredField( "loader" ); loaderField.setAccessible( true ); loaderGetter = MethodHandles.lookup().unreflectGetter( loaderField ); } catch( Exception e ) { throw Throwables.propagate( e ); } } private static Object deserializer( Class clas ) { try { clas = Class.forName( clas.getName() + "$Deserializer" ); Constructor constr = clas.getDeclaredConstructor(); constr.setAccessible( true ); return constr.newInstance(); } catch( Exception e ) { throw Throwables.propagate( e ); } } private static <M extends IModel> M vanillaModelWrapper( ModelLoader loader, ResourceLocation location, ModelBlock model, boolean uvlock, ModelBlockAnimation animation ) { try { return (M) vanillaModelWrapper.newInstance( loader, location, model, uvlock, animation ); } catch( Exception e ) { throw Throwables.propagate( e ); } } private static void setFaceBakery( ModelBakery modelBakery, FaceBakery faceBakery ) { try { UVLModelLoader.faceBakery.set( modelBakery, faceBakery ); } catch( Exception e ) { throw Throwables.propagate( e ); } } private IResourceManager resourceManager; public ModelLoader getLoader() { try { return (ModelLoader) loaderGetter.invoke( vanillaLoader ); } catch( Throwable throwable ) { throw new RuntimeException( throwable ); } } @Override public void onResourceManagerReload( IResourceManager resourceManager ) { this.resourceManager = resourceManager; } @Override public boolean accepts( ResourceLocation modelLocation ) { String modelPath = modelLocation.getResourcePath(); if( modelLocation.getResourcePath().startsWith( "models/" ) ) { modelPath = modelPath.substring( "models/".length() ); } try( InputStreamReader io = new InputStreamReader( Minecraft.getMinecraft().getResourceManager().getResource( new ResourceLocation( modelLocation.getResourceDomain(), "models/" + modelPath + ".json" ) ).getInputStream() ) ) { return gson.fromJson( io, UVLMarker.class ).uvlMarker; } catch( IOException e ) { } return false; } @Override public IModel loadModel( ResourceLocation modelLocation ) throws Exception { return new UVLModelWrapper( modelLocation ); } public class UVLModelWrapper implements IModel { final Gson UVLSERIALIZER = ( new GsonBuilder() ).registerTypeAdapter( ModelBlock.class, deserializer( ModelBlock.class ) ).registerTypeAdapter( BlockPart.class, deserializer( BlockPart.class ) ).registerTypeAdapter( BlockPartFace.class, new BlockPartFaceOverrideSerializer() ).registerTypeAdapter( BlockFaceUV.class, deserializer( BlockFaceUV.class ) ).registerTypeAdapter( ItemTransformVec3f.class, deserializer( ItemTransformVec3f.class ) ).registerTypeAdapter( ItemCameraTransforms.class, deserializer( ItemCameraTransforms.class ) ).registerTypeAdapter( ItemOverride.class, deserializer( ItemOverride.class ) ).create(); private Map<BlockPartFace, Pair<Float, Float>> uvlightmap = new HashMap<>(); private final IModel parent; public UVLModelWrapper( ResourceLocation modelLocation ) { String modelPath = modelLocation.getResourcePath(); if( modelLocation.getResourcePath().startsWith( "models/" ) ) { modelPath = modelPath.substring( "models/".length() ); } ResourceLocation armatureLocation = new ResourceLocation( modelLocation.getResourceDomain(), "armatures/" + modelPath + ".json" ); ModelBlockAnimation animation = ModelBlockAnimation.loadVanillaAnimation( resourceManager, armatureLocation ); ModelBlock model; { Reader reader = null; IResource iresource = null; ModelBlock lvt_5_1_ = null; try { String s = modelLocation.getResourcePath(); iresource = Minecraft.getMinecraft().getResourceManager().getResource( new ResourceLocation( modelLocation.getResourceDomain(), "models/" + modelPath + ".json" ) ); reader = new InputStreamReader( iresource.getInputStream(), Charsets.UTF_8 ); lvt_5_1_ = JsonUtils.gsonDeserialize( UVLSERIALIZER, reader, ModelBlock.class, false ); lvt_5_1_.name = modelLocation.toString(); } catch( IOException e ) { e.printStackTrace(); } finally { IOUtils.closeQuietly( reader ); IOUtils.closeQuietly( (Closeable) iresource ); } model = lvt_5_1_; } this.parent = vanillaModelWrapper( getLoader(), modelLocation, model, false, animation ); } @Override public Collection<ResourceLocation> getDependencies() { return parent.getDependencies(); } @Override public Collection<ResourceLocation> getTextures() { return parent.getTextures(); } @Override public IBakedModel bake( IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter ) { setFaceBakery( getLoader(), new FaceBakeryOverride() ); IBakedModel model = parent.bake( state, format, bakedTextureGetter ); setFaceBakery( getLoader(), new FaceBakery() ); return model; } @Override public IModelState getDefaultState() { return parent.getDefaultState(); } public class BlockPartFaceOverrideSerializer implements JsonDeserializer<BlockPartFace> { @Override public BlockPartFace deserialize( JsonElement p_deserialize_1_, Type p_deserialize_2_, JsonDeserializationContext p_deserialize_3_ ) throws JsonParseException { JsonObject jsonobject = p_deserialize_1_.getAsJsonObject(); EnumFacing enumfacing = this.parseCullFace( jsonobject ); int i = this.parseTintIndex( jsonobject ); String s = this.parseTexture( jsonobject ); BlockFaceUV blockfaceuv = (BlockFaceUV) p_deserialize_3_.deserialize( jsonobject, BlockFaceUV.class ); BlockPartFace blockFace = new BlockPartFace( enumfacing, i, s, blockfaceuv ); uvlightmap.put( blockFace, parseUVL( jsonobject ) ); return blockFace; } protected int parseTintIndex( JsonObject object ) { return JsonUtils.getInt( object, "tintindex", -1 ); } private String parseTexture( JsonObject object ) { return JsonUtils.getString( object, "texture" ); } @Nullable private EnumFacing parseCullFace( JsonObject object ) { String s = JsonUtils.getString( object, "cullface", "" ); return EnumFacing.byName( s ); } protected Pair<Float, Float> parseUVL( JsonObject object ) { if( !object.has( "uvlightmap" ) ) { return null; } object = object.get( "uvlightmap" ).getAsJsonObject(); return new ImmutablePair<Float, Float>( JsonUtils.getFloat( object, "sky", 0 ), JsonUtils.getFloat( object, "block", 0 ) ); } } public class FaceBakeryOverride extends FaceBakery { @Override public BakedQuad makeBakedQuad( Vector3f posFrom, Vector3f posTo, BlockPartFace face, TextureAtlasSprite sprite, EnumFacing facing, ITransformation modelRotationIn, BlockPartRotation partRotation, boolean uvLocked, boolean shade ) { BakedQuad quad = super.makeBakedQuad( posFrom, posTo, face, sprite, facing, modelRotationIn, partRotation, uvLocked, shade ); Pair<Float, Float> brightness = uvlightmap.get( face ); if( brightness != null ) { VertexFormat newFormat = VertexFormats.getFormatWithLightMap( quad.getFormat() ); UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder( newFormat ); VertexLighterFlat trans = new VertexLighterFlat( Minecraft.getMinecraft().getBlockColors() ){ @Override protected void updateLightmap( float[] normal, float[] lightmap, float x, float y, float z ) { lightmap[0] = brightness.getRight(); lightmap[1] = brightness.getLeft(); } @Override public void setQuadTint( int tint ) { // Tint requires a block state which we don't have at this point } }; trans.setParent( builder ); quad.pipe( trans ); builder.setQuadTint( quad.getTintIndex() ); builder.setQuadOrientation( quad.getFace() ); builder.setTexture( quad.getSprite() ); builder.setApplyDiffuseLighting( false ); return builder.build(); } else { return quad; } } } } class UVLMarker { boolean uvlMarker = false; } }