package blusunrize.immersiveengineering.client.models; import blusunrize.immersiveengineering.api.ApiUtils; import blusunrize.immersiveengineering.api.ComparableItemStack; import blusunrize.immersiveengineering.api.IEProperties; import blusunrize.immersiveengineering.api.shader.CapabilityShader; import blusunrize.immersiveengineering.api.shader.CapabilityShader.ShaderWrapper; import blusunrize.immersiveengineering.api.shader.IShaderItem; import blusunrize.immersiveengineering.api.shader.ShaderCase; import blusunrize.immersiveengineering.api.shader.ShaderCase.ShaderLayer; import blusunrize.immersiveengineering.client.ClientUtils; import blusunrize.immersiveengineering.client.models.smart.ConnModelReal.ExtBlockstateAdapter; import blusunrize.immersiveengineering.common.util.chickenbones.Matrix4; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; import net.minecraft.client.renderer.block.model.ItemOverrideList; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; 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.client.model.obj.OBJModel; import net.minecraftforge.client.model.obj.OBJModel.*; import net.minecraftforge.client.model.pipeline.LightUtil; import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; import net.minecraftforge.common.property.IExtendedBlockState; import net.minecraftforge.common.property.Properties; import org.apache.commons.lang3.tuple.Pair; import javax.vecmath.Matrix4f; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @SuppressWarnings("deprecation") public class IESmartObjModel extends OBJBakedModel { public static Map<ComparableItemStack, IBakedModel> cachedBakedItemModels = new ConcurrentHashMap<ComparableItemStack, IBakedModel>(); public static HashMap<ExtBlockstateAdapter, List<BakedQuad>> modelCache = new HashMap<>(); IBakedModel baseModel; HashMap<TransformType, Matrix4> transformationMap = new HashMap<TransformType, Matrix4>(); ImmutableList<BakedQuad> bakedQuads; TextureAtlasSprite tempSprite; ItemStack tempStack; IBlockState tempState; VertexFormat format; Map<String, String> texReplace = null; public IESmartObjModel(IBakedModel baseModel, OBJModel model, IModelState state, VertexFormat format, ImmutableMap<String, TextureAtlasSprite> textures, HashMap<TransformType, Matrix4> transformationMap) { model.super(model, state, format, textures); this.baseModel = baseModel; this.transformationMap = transformationMap; this.format = format; } @Override public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType) { if(transformationMap==null || transformationMap.isEmpty()) return super.handlePerspective(cameraTransformType); // Matrix4 matrix = new Matrix4(); //Assign Matrixes here manually in debug mode, then move them to the actual registration method Matrix4 matrix = transformationMap.containsKey(cameraTransformType)?transformationMap.get(cameraTransformType).copy():new Matrix4(); if(this.tempStack!=null && this.tempStack.getItem() instanceof IOBJModelCallback) matrix = ((IOBJModelCallback)this.tempStack.getItem()).handlePerspective(this.tempStack, cameraTransformType, matrix); //Dynamic stuff to use when figurign out positioning for new items! //FP_R // if(cameraTransformType==TransformType.FIRST_PERSON_RIGHT_HAND) // matrix = new Matrix4().rotate(Math.toRadians(-90), 0,1,0).scale(.1875, .25, .25).translate(-.5, .4375, .5); // else if(cameraTransformType==TransformType.FIRST_PERSON_LEFT_HAND)//FP_L // matrix = new Matrix4().rotate(Math.toRadians(90), 0,1,0).scale(.1875, .25, .25).translate(.45, .4375, .5); // else if(cameraTransformType==TransformType.THIRD_PERSON_RIGHT_HAND) //TP_R // matrix = new Matrix4().translate(-.125, .125,-.125).scale(.125, .125, .125).rotate(Math.toRadians(-90), 0,1,0).rotate(Math.toRadians(-10), 0,0,1); // else if(cameraTransformType==TransformType.THIRD_PERSON_LEFT_HAND) //TP_L // matrix = new Matrix4().translate(.0, .0625,-.125).scale(.125, .125, .125).rotate(Math.toRadians(90), 0,1,0).rotate(Math.toRadians(0), 0,0,1); // else if(cameraTransformType==TransformType.FIXED) //FIXED // matrix = new Matrix4().translate(.1875, -.0781225, -.15625).scale(.2, .2, .2).rotate(Math.toRadians(-40), 0,1,0).rotate(Math.toRadians(-35), 0,0,1); // else if(cameraTransformType==TransformType.GUI) //INV // matrix = new Matrix4().translate(-.25, 0,-.0625).scale(.1875, .1875, .1875).rotate(Math.PI, 0, 1, 0).rotate(Math.toRadians(-40), 0, 0, 1); // else //GROUND // matrix = new Matrix4().translate(.125, 0, .0625).scale(.125, .125, .125); return Pair.of(this, matrix.toMatrix4f()); } VertexFormat getFormat() { return this.format; } @Override public ItemOverrideList getOverrides() { return overrideList; } ItemOverrideList overrideList = new ItemOverrideList(new ArrayList()) { @Override public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, World world, EntityLivingBase entity) { ComparableItemStack comp = ApiUtils.createComparableItemStack(stack); if(comp == null) return originalModel; if(cachedBakedItemModels.containsKey(comp)) return cachedBakedItemModels.get(comp); if(!(originalModel instanceof IESmartObjModel)) return originalModel; IESmartObjModel model = (IESmartObjModel)originalModel; ImmutableMap.Builder<String, TextureAtlasSprite> builder = ImmutableMap.builder(); builder.put(ModelLoader.White.LOCATION.toString(), ModelLoader.White.INSTANCE); TextureAtlasSprite missing = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(new ResourceLocation("missingno").toString()); for (String s : model.getModel().getMatLib().getMaterialNames()) { TextureAtlasSprite sprite = null; if(stack.hasCapability(CapabilityShader.SHADER_CAPABILITY, null)) { ShaderWrapper wrapper = stack.getCapability(CapabilityShader.SHADER_CAPABILITY, null); ItemStack shader = wrapper.getShaderItem(); if (shader != null && shader.getItem() instanceof IShaderItem) { ShaderCase sCase = ((IShaderItem) shader.getItem()).getShaderCase(shader, stack, wrapper.getShaderType()); if(sCase!=null) { ResourceLocation rl = sCase.getReplacementSprite(shader, stack, s, 0); sprite = ClientUtils.getSprite(rl); } } } if (sprite == null && stack.getItem() instanceof IOBJModelCallback) sprite = ((IOBJModelCallback) stack.getItem()).getTextureReplacement(stack, s); if (sprite == null) sprite = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(model.getModel().getMatLib().getMaterial(s).getTexture().getTextureLocation().toString()); if (sprite == null) sprite = missing; builder.put(s, sprite); } builder.put("missingno", missing); IESmartObjModel bakedModel = new IESmartObjModel(model.baseModel, model.getModel(), model.getState(), model.getFormat(), builder.build(), transformationMap); bakedModel.tempStack = stack; cachedBakedItemModels.put(comp, bakedModel); return bakedModel; } }; @Override public List<BakedQuad> getQuads(IBlockState blockState, EnumFacing side, long rand) { OBJState objState = null; Map<String, String> tex = null; if (blockState instanceof IExtendedBlockState) { IExtendedBlockState ext = (IExtendedBlockState) blockState; if (ext.getUnlistedNames().contains(Properties.AnimationProperty)) { IModelState modState = ext.getValue(Properties.AnimationProperty); if (modState instanceof OBJState) objState = (OBJState) modState; } if (ext.getUnlistedNames().contains(IEProperties.OBJ_TEXTURE_REMAP)) tex = ext.getValue(IEProperties.OBJ_TEXTURE_REMAP); } return getQuads(blockState, side, rand, objState, tex, false); } public List<BakedQuad> getQuads(IBlockState blockState, EnumFacing side, long rand, OBJState objstate, Map<String, String> tex, boolean addAnimationAndTex) { texReplace = tex; this.tempState = blockState; if(blockState instanceof IExtendedBlockState) { IExtendedBlockState exState = (IExtendedBlockState) blockState; ExtBlockstateAdapter adapter; if (objstate!=null) { if(objstate.parent==null || objstate.parent==TRSRTransformation.identity()) objstate.parent = this.getState(); if(objstate.getVisibilityMap().containsKey(Group.ALL) || objstate.getVisibilityMap().containsKey(Group.ALL_EXCEPT)) this.updateStateVisibilityMap(objstate); } if (addAnimationAndTex) adapter = new ExtBlockstateAdapter(exState, MinecraftForgeClient.getRenderLayer(), ExtBlockstateAdapter.CONNS_OBJ_CALLBACK, new Object[]{objstate, tex}); else adapter = new ExtBlockstateAdapter(exState, MinecraftForgeClient.getRenderLayer(), ExtBlockstateAdapter.CONNS_OBJ_CALLBACK); if(!modelCache.containsKey(adapter)) { IESmartObjModel model = null; if(objstate!=null) model = new IESmartObjModel(baseModel, getModel(), objstate, getFormat(), getTextures(), transformationMap); if(model==null) model = new IESmartObjModel(baseModel, getModel(), this.getState(), getFormat(), getTextures(), transformationMap); model.tempState = blockState; model.texReplace = tex; modelCache.put(adapter, model.buildQuads()); } return Collections.synchronizedList(Lists.newArrayList(modelCache.get(adapter))); } if(bakedQuads==null) bakedQuads = buildQuads(); List<BakedQuad> quadList = Collections.synchronizedList(Lists.newArrayList(bakedQuads)); return quadList; } private ImmutableList<BakedQuad> buildQuads() { List<BakedQuad> quads = Lists.newArrayList(); ItemStack shader = null; ShaderCase sCase = null; IOBJModelCallback callback = null; Object callbackObject = null; if(this.tempStack!=null && tempStack.hasCapability(CapabilityShader.SHADER_CAPABILITY, null)) { ShaderWrapper wrapper = tempStack.getCapability(CapabilityShader.SHADER_CAPABILITY, null); shader = wrapper.getShaderItem(); if(shader!=null && shader.getItem() instanceof IShaderItem) sCase = ((IShaderItem)shader.getItem()).getShaderCase(shader, tempStack, wrapper.getShaderType()); } else if(this.tempState != null && this.tempState instanceof IExtendedBlockState && ((IExtendedBlockState)this.tempState).getUnlistedNames().contains(CapabilityShader.BLOCKSTATE_PROPERTY)) { ShaderWrapper wrapper = ((IExtendedBlockState)this.tempState).getValue(CapabilityShader.BLOCKSTATE_PROPERTY); shader = wrapper.getShaderItem(); if(shader!=null && shader.getItem() instanceof IShaderItem) sCase = ((IShaderItem)shader.getItem()).getShaderCase(shader, null, wrapper.getShaderType()); } if(this.tempStack!=null && tempStack.getItem() instanceof IOBJModelCallback) { callback = (IOBJModelCallback)tempStack.getItem(); callbackObject = this.tempStack; } else if(this.tempState != null && this.tempState instanceof IExtendedBlockState && ((IExtendedBlockState)this.tempState).getUnlistedNames().contains(IOBJModelCallback.PROPERTY)) { callback = ((IExtendedBlockState)this.tempState).getValue(IOBJModelCallback.PROPERTY); callbackObject = this.tempState; } int maxPasses = 1; if(sCase!=null) maxPasses = sCase.getLayers().length; for(int pass=0; pass<maxPasses; pass++) { ShaderLayer shaderLayer = sCase!=null?sCase.getLayers()[pass]:null; for(Group g : getModel().getMatLib().getGroups().values()) { if(callback != null) if(!callback.shouldRenderGroup(callbackObject, g.getName())) continue; if(sCase != null) if(!sCase.renderModelPartForPass(shader, tempStack, g.getName(), pass)) continue; Set<Face> faces = Collections.synchronizedSet(new LinkedHashSet<Face>()); Optional<TRSRTransformation> transform = Optional.absent(); if(this.getState() instanceof OBJState) { OBJState state = (OBJState)this.getState(); if(state.parent != null) transform = state.parent.apply(Optional.absent()); if(callback != null) transform = callback.applyTransformations(callbackObject, g.getName(), transform); if(state.getGroupsWithVisibility(true).contains(g.getName())) faces.addAll(g.applyTransform(transform)); } else { transform = getState().apply(Optional.absent()); if(callback != null) transform = callback.applyTransformations(callbackObject, g.getName(), transform); faces.addAll(g.applyTransform(transform)); } int argb = 0xffffffff; if(sCase != null) argb = sCase.getARGBColourModifier(shader, tempStack, g.getName(), pass); else if(callback != null) argb = callback.getRenderColour(callbackObject, g.getName()); float[] colour = {(argb >> 16 & 255) / 255f, (argb >> 8 & 255) / 255f, (argb & 255) / 255f, (argb >> 24 & 255) / 255f}; for(Face f : faces) { tempSprite = null; if(this.getModel().getMatLib().getMaterial(f.getMaterialName()).isWhite() && !"null".equals(f.getMaterialName())) { for(Vertex v : f.getVertices()) if(!v.getMaterial().equals(this.getModel().getMatLib().getMaterial(v.getMaterial().getName()))) v.setMaterial(this.getModel().getMatLib().getMaterial(v.getMaterial().getName())); tempSprite = ModelLoader.White.INSTANCE; } else { if(sCase!=null) { ResourceLocation rl = sCase.getReplacementSprite(shader, tempStack, g.getName(), pass); if(rl!=null) tempSprite = ClientUtils.getSprite(rl); } if(tempSprite==null && callback!=null) tempSprite = callback.getTextureReplacement(callbackObject, f.getMaterialName()); if(tempSprite==null&&tempState!=null&&texReplace!=null) { String s = texReplace.get(g.getName()); if (s!=null) tempSprite = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(s); } if(tempSprite==null && !"null".equals(f.getMaterialName())) tempSprite = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(this.getModel().getMatLib().getMaterial(f.getMaterialName()).getTexture().getTextureLocation().toString()); } if(tempSprite != null) { UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(getFormat()); builder.setQuadOrientation(EnumFacing.getFacingFromVector(f.getNormal().x, f.getNormal().y, f.getNormal().z)); builder.setTexture(tempSprite); builder.setQuadTint(pass); Normal faceNormal = f.getNormal(); TextureCoordinate[] uvs = new TextureCoordinate[4]; boolean renderFace = true; for(int i=0; i<4; i++) { Vertex vertex = f.getVertices()[i]; //V-Flip is processed here already, rather than in the later method, since it's needed for easy UV comparissons on the Shader Layers uvs[i] = vertex.hasTextureCoordinate() ? new TextureCoordinate(vertex.getTextureCoordinate().u,1-vertex.getTextureCoordinate().v,vertex.getTextureCoordinate().w) : TextureCoordinate.getDefaultUVs()[i]; if(shaderLayer!=null) { double[] texBounds = shaderLayer.getTextureBounds(); if(texBounds!=null) { if(texBounds[0]>uvs[i].u || uvs[i].u>texBounds[2] || texBounds[1]>uvs[i].v || uvs[i].v>texBounds[3])//if any uvs are outside the layers bounds { renderFace = false; break; } double dU = texBounds[2] - texBounds[0]; double dV = texBounds[3] - texBounds[1]; //Rescaling to the partial bounds that the texture represents uvs[i].u = (float)((uvs[i].u-texBounds[0])/dU); uvs[i].v = (float)((uvs[i].v-texBounds[1])/dV); } //Rescaling to the selective area of the texture that is used double[] cutBounds = shaderLayer.getCutoutBounds(); if(cutBounds!=null) { double dU = cutBounds[2] - cutBounds[0]; double dV = cutBounds[3] - cutBounds[1]; uvs[i].u = (float)(cutBounds[0] + dU*uvs[i].u); uvs[i].v = (float)(cutBounds[1] + dV*uvs[i].v); } } } if(renderFace) { for(int i=0; i<4; i++) putVertexData(builder, f.getVertices()[i], faceNormal, uvs[i], tempSprite, colour); quads.add(builder.build()); } } } } } if(callback != null) quads = callback.modifyQuads(callbackObject, quads); return ImmutableList.copyOf(quads); } protected final void putVertexData(UnpackedBakedQuad.Builder builder, Vertex v, Normal faceNormal, TextureCoordinate texCoord, TextureAtlasSprite sprite, float[] colour) { for(int e = 0; e < getFormat().getElementCount(); e++) { switch (getFormat().getElement(e).getUsage()) { case POSITION: builder.put(e, v.getPos().x, v.getPos().y, v.getPos().z, v.getPos().w); break; case COLOR: float d; if(v.hasNormal()) d = LightUtil.diffuseLight(v.getNormal().x, v.getNormal().y, v.getNormal().z); else d = LightUtil.diffuseLight(faceNormal.x, faceNormal.y, faceNormal.z); if(v.getMaterial() != null) builder.put(e, d * v.getMaterial().getColor().x*colour[0], d * v.getMaterial().getColor().y*colour[1], d * v.getMaterial().getColor().z*colour[2], v.getMaterial().getColor().w*colour[3]); else builder.put(e, d*colour[0], d*colour[1], d*colour[2], 1*colour[3]); break; case UV: if(sprite==null)//Double Safety. I have no idea how it even happens, but it somehow did .-. sprite = Minecraft.getMinecraft().getTextureMapBlocks().getMissingSprite(); builder.put(e, sprite.getInterpolatedU(texCoord.u * 16), sprite.getInterpolatedV((texCoord.v) * 16),//v-flip used to be processed here but was moved because of shader layers 0, 1); break; case NORMAL: if(!v.hasNormal()) builder.put(e, faceNormal.x, faceNormal.y, faceNormal.z, 0); else builder.put(e, v.getNormal().x, v.getNormal().y, v.getNormal().z, 0); break; default: builder.put(e); } } } static int getExtendedStateHash(IExtendedBlockState state) { return state.hashCode()*31 + state.getUnlistedProperties().hashCode(); } // private final LoadingCache<Integer, IESmartObjModel> ieobjcache = CacheBuilder.newBuilder().maximumSize(20).build(new CacheLoader<Integer, IESmartObjModel>() // { // public IESmartObjModel load(IModelState state) throws Exception // { // return new IESmartObjModel(baseModel, getModel(), state, getFormat(), getTextures(), transformationMap); // } // }); protected void updateStateVisibilityMap(OBJState state) { if (state.getVisibilityMap().containsKey(Group.ALL)) { boolean operation = state.getVisibilityMap().get(Group.ALL); state.getVisibilityMap().clear(); for (String s : this.getModel().getMatLib().getGroups().keySet()) { state.getVisibilityMap().put(s, OBJState.Operation.SET_TRUE.performOperation(operation)); } } else if (state.getVisibilityMap().containsKey(Group.ALL_EXCEPT)) { List<String> exceptList = state.getGroupNamesFromMap().subList(1, state.getGroupNamesFromMap().size()); state.getVisibilityMap().remove(Group.ALL_EXCEPT); for (String s : this.getModel().getMatLib().getGroups().keySet()) { if (!exceptList.contains(s)) { state.getVisibilityMap().put(s, OBJState.Operation.SET_TRUE.performOperation(state.getVisibilityMap().get(s))); } } } else { for (String s : state.getVisibilityMap().keySet()) { state.getVisibilityMap().put(s, OBJState.Operation.SET_TRUE.performOperation(state.getVisibilityMap().get(s))); } } } static Field f_textures; public static ImmutableMap<String, TextureAtlasSprite> getTexturesForOBJModel(IBakedModel model) { try{ if(f_textures==null) { f_textures = OBJBakedModel.class.getDeclaredField("textures"); f_textures.setAccessible(true); } return (ImmutableMap<String, TextureAtlasSprite>)f_textures.get(model); }catch(Exception e){ e.printStackTrace(); } return null; } public ImmutableMap<String, TextureAtlasSprite> getTextures() { try{ if(f_textures==null) { f_textures = OBJBakedModel.class.getDeclaredField("textures"); f_textures.setAccessible(true); } return (ImmutableMap<String, TextureAtlasSprite>)f_textures.get(this); }catch(Exception e){ e.printStackTrace(); } return null; } }