package com.agilemods.materiamuto.client.model.importer; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.client.Minecraft; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelRenderer; import net.minecraft.client.resources.IResource; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.IModelCustom; import net.minecraftforge.client.model.ModelFormatException; import org.lwjgl.opengl.GL11; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.Map.Entry; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipInputStream; @SideOnly(Side.CLIENT) public class TechneModel extends ModelBase implements IModelCustom { public static final List<String> cubeTypes = Arrays.asList("d9e621f7-957f-4b77-b1ae-20dcd0da7751", "de81aa14-bd60-4228-8d8d-5238bcd3caaa"); private String fileName; private Map<String, byte[]> zipContents = new HashMap<String, byte[]>(); public Map<String, ModelRenderer> parts = new LinkedHashMap<String, ModelRenderer>(); private String texture = null; private int textureName; private boolean textureNameSet = false; public TechneModel(ResourceLocation resource) throws ModelFormatException { this.fileName = resource.toString(); try { IResource res = Minecraft.getMinecraft().getResourceManager().getResource(resource); loadTechneModel(res.getInputStream()); } catch (Exception e) { throw new ModelFormatException("IO Exception reading models format", e); } } private void loadTechneModel(InputStream stream) throws ModelFormatException { try { ZipInputStream zipInput = new ZipInputStream(stream); ZipEntry entry; while ((entry = zipInput.getNextEntry()) != null) { byte[] data = new byte[(int) entry.getSize()]; // For some reason, using read(byte[]) makes reading stall upon reaching a 0x1E byte int i = 0; while (zipInput.available() > 0 && i < data.length) { data[i++] = (byte) zipInput.read(); } zipContents.put(entry.getName(), data); } byte[] modelXml = zipContents.get("models.xml"); if (modelXml == null) { modelXml = zipContents.get("model.xml"); if (modelXml == null) { throw new ModelFormatException("Model " + fileName + " contains no model(s).xml file"); } } DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(new ByteArrayInputStream(modelXml)); NodeList nodeListTechne = document.getElementsByTagName("Techne"); if (nodeListTechne.getLength() < 1) { throw new ModelFormatException("Model " + fileName + " contains no Techne tag"); } NodeList nodeListModel = document.getElementsByTagName("Model"); if (nodeListModel.getLength() < 1) { throw new ModelFormatException("Model " + fileName + " contains no Model tag"); } NamedNodeMap modelAttributes = nodeListModel.item(0).getAttributes(); if (modelAttributes == null) { throw new ModelFormatException("Model " + fileName + " contains a Model tag with no attributes"); } Node modelTexture = modelAttributes.getNamedItem("texture"); if (modelTexture != null) { texture = modelTexture.getTextContent(); } NodeList textureSize = document.getElementsByTagName("TextureSize"); for (int i = 0; i < textureSize.getLength(); i++) { String size = textureSize.item(i).getTextContent(); String[] textureDimensions = size.split(","); textureWidth = Integer.parseInt(textureDimensions[0]); textureHeight = Integer.parseInt(textureDimensions[1]); } NodeList shapes = document.getElementsByTagName("Shape"); for (int i = 0; i < shapes.getLength(); i++) { Node shape = shapes.item(i); NamedNodeMap shapeAttributes = shape.getAttributes(); if (shapeAttributes == null) { throw new ModelFormatException("Shape #" + (i + 1) + " in " + fileName + " has no attributes"); } Node name = shapeAttributes.getNamedItem("name"); String shapeName = null; if (name != null) { shapeName = name.getNodeValue(); } if (shapeName == null) { shapeName = "Shape #" + (i + 1); } String shapeType = null; Node type = shapeAttributes.getNamedItem("type"); if (type != null) { shapeType = type.getNodeValue(); } if (shapeType != null && !cubeTypes.contains(shapeType)) { FMLLog.warning("Model shape [" + shapeName + "] in " + fileName + " is not a cube, ignoring"); continue; } try { boolean mirrored = false; String[] offset = new String[3]; String[] position = new String[3]; String[] rotation = new String[3]; String[] size = new String[3]; String[] textureOffset = new String[2]; NodeList shapeChildren = shape.getChildNodes(); for (int j = 0; j < shapeChildren.getLength(); j++) { Node shapeChild = shapeChildren.item(j); String shapeChildName = shapeChild.getNodeName(); String shapeChildValue = shapeChild.getTextContent(); if (shapeChildValue != null) { shapeChildValue = shapeChildValue.trim(); if (shapeChildName.equals("IsMirrored")) { mirrored = !shapeChildValue.equals("False"); } else if (shapeChildName.equals("Offset")) { offset = shapeChildValue.split(","); } else if (shapeChildName.equals("Position")) { position = shapeChildValue.split(","); } else if (shapeChildName.equals("Rotation")) { rotation = shapeChildValue.split(","); } else if (shapeChildName.equals("Size")) { size = shapeChildValue.split(","); } else if (shapeChildName.equals("TextureOffset")) { textureOffset = shapeChildValue.split(","); } } } // That's what the ModelBase subclassing is needed for ModelRenderer cube = new ModelRenderer(this, shapeName); cube.setTextureOffset(Integer.parseInt(textureOffset[0]), Integer.parseInt(textureOffset[1])); cube.mirror = mirrored; cube.addBox(Float.parseFloat(offset[0]), Float.parseFloat(offset[1]), Float.parseFloat(offset[2]), Integer.parseInt(size[0]), Integer.parseInt(size[1]), Integer.parseInt(size[2])); cube.setRotationPoint(Float.parseFloat(position[0]), Float.parseFloat(position[1]) - 16, Float.parseFloat(position[2])); cube.rotateAngleX = (float) Math.toRadians(Float.parseFloat(rotation[0])); cube.rotateAngleY = (float) Math.toRadians(Float.parseFloat(rotation[1])); cube.rotateAngleZ = (float) Math.toRadians(Float.parseFloat(rotation[2])); if (parts.containsKey(shapeName)) { throw new ModelFormatException("Model contained duplicate part name: '" + shapeName + "' node #" + i); } parts.put(shapeName, cube); } catch (NumberFormatException e) { FMLLog.warning("Model shape [" + shapeName + "] in " + fileName + " contains malformed integers within its data, ignoring"); e.printStackTrace(); } } } catch (ZipException e) { throw new ModelFormatException("Model " + fileName + " is not a valid zip file"); } catch (IOException e) { throw new ModelFormatException("Model " + fileName + " could not be read", e); } catch (ParserConfigurationException e) { // hush } catch (SAXException e) { throw new ModelFormatException("Model " + fileName + " contains invalid XML", e); } } private void bindTexture() { } @Override public String getType() { return "tcn"; } private void setup() { GL11.glScalef(-1.0F, -1.0F, 1.0F); } @Override public void renderAll() { GL11.glPushMatrix(); bindTexture(); setup(); for (ModelRenderer part : parts.values()) { part.render(0.0625f); } GL11.glPopMatrix(); } @Override public void renderPart(String partName) { ModelRenderer part = parts.get(partName); if (part != null) { GL11.glPushMatrix(); setup(); bindTexture(); part.render(0.0625f); GL11.glPopMatrix(); } } @Override public void renderOnly(String... groupNames) { GL11.glPushMatrix(); setup(); bindTexture(); Iterator<Entry<String, ModelRenderer>> it = parts.entrySet().iterator(); while (it.hasNext()) { Entry<String, ModelRenderer> entry = it.next(); for (String groupName : groupNames) { if (entry.getKey().equalsIgnoreCase(groupName)) { entry.getValue().render(0.0625f); } } } GL11.glPopMatrix(); } public void renderOnlyAroundPivot(double angle, double rotX, double rotY, double rotZ, String... groupNames) { GL11.glPushMatrix(); setup(); bindTexture(); Iterator<Entry<String, ModelRenderer>> it = parts.entrySet().iterator(); while (it.hasNext()) { Entry<String, ModelRenderer> entry = it.next(); for (String groupName : groupNames) { if (entry.getKey().equalsIgnoreCase(groupName)) { GL11.glPushMatrix(); ModelRenderer model = entry.getValue(); GL11.glTranslatef(model.rotationPointX / 16, model.rotationPointY / 16, model.rotationPointZ / 16); GL11.glRotated(angle, rotX, rotY, rotZ); GL11.glTranslatef(-model.rotationPointX / 16, -model.rotationPointY / 16, -model.rotationPointZ / 16); model.render(0.0625f); GL11.glPopMatrix(); } } } GL11.glPopMatrix(); } @Override public void renderAllExcept(String... excludedGroupNames) { GL11.glPushMatrix(); setup(); Iterator<Entry<String, ModelRenderer>> it = parts.entrySet().iterator(); loop: while (it.hasNext()) { Entry<String, ModelRenderer> entry = it.next(); for (String groupName : excludedGroupNames) { if (entry.getKey().equalsIgnoreCase(groupName)) { continue loop; } } entry.getValue().render(0.0625f); } GL11.glPopMatrix(); } }