package com.hbm.render.anim; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonElement; import com.google.gson.JsonArray; import net.minecraft.client.Minecraft; import net.minecraft.util.ResourceLocation; import com.hbm.render.anim.BusAnimationKeyframe.IType; import com.hbm.render.anim.BusAnimationKeyframe.EType; import com.hbm.render.anim.BusAnimationKeyframe.HType; import com.hbm.render.anim.BusAnimationSequence.Dimension; public class AnimationLoader { // The collada loader is great, but is not so backwards compatible and spews keyframes rather than doing interpolation // Yeah - more animation loading is not so great, but 3mb for a single door opening is maybe overkill on a 50mb mod // and even though the format supports multiple animations, no fucking animation software will actually export multiple animations, // (even though blender even has a fucking toggle for it, but it doesn't _do_ anything) // This instead just loads transformation data from a JSON file, turning it into a set of BusAnimations // See ntm-animator.blend for a JSON format creation script // "How do I make animations?" // See ntm-animator.blend, it has the Colt/Python already setup and animated as an example, it'll generate JSON data that this can load public static final Gson gson = new Gson(); public static HashMap load(ResourceLocation file) { HashMap animations = new HashMap(); InputStream in; try { in = Minecraft.getMinecraft().getResourceManager().getResource(file).getInputStream(); } catch (IOException ex) { return null; } InputStreamReader reader = new InputStreamReader(in); JsonObject json = gson.fromJson(reader, JsonObject.class); // Load our model offsets, we'll place these into all the sequences that share the name of the offset // The offsets are only required when sequences are played for an object, which is why we don't globally offset! The obj rendering handles the non-animated case fine // Effectively, this removes double translation AND ensures that rotations occur around the individual object origin, rather than the weapon origin HashMap offsets = new HashMap(); for(Map.Entry root : json.getAsJsonObject("offset").entrySet()) { JsonArray array = root.getValue().getAsJsonArray(); double[] offset = new double[3]; for(int i = 0; i < 3; i++) { offset[i] = array.get(i).getAsDouble(); } offsets.put(root.getKey(), offset); } // Rotation modes, swizzled into our local space. YZX in blender becomes XYZ due to: // * rotation order reversed in blender (XYZ -> ZYX) // * dimensions Y and Z are swapped in blender (ZYX -> YZX) HashMap rotModes = new HashMap(); if(json.has("rotmode")) { for(Map.Entry root : json.getAsJsonObject("rotmode").entrySet()) { String mode = root.getValue().getAsString(); double[] rotMode = new double[3]; rotMode[0] = getRot(mode.charAt(2)); rotMode[1] = getRot(mode.charAt(0)); rotMode[2] = getRot(mode.charAt(1)); } } // Top level parsing, this is for the animation name as set in Blender for(Map.Entry root : json.getAsJsonObject("anim").entrySet()) { BusAnimation animation = new BusAnimation(); // Loading the buses for this animation JsonObject entryObject = root.getValue().getAsJsonObject(); for(Map.Entry model : entryObject.entrySet()) { String modelName = model.getKey(); double[] offset = new double[3]; double[] rotMode = new double[] { 0, 1, 2 }; if(offsets.containsKey(modelName)) offset = offsets.get(modelName); if(rotModes.containsKey(modelName)) rotMode = rotModes.get(modelName); animation.addBus(modelName, loadSequence(model.getValue().getAsJsonObject(), offset, rotMode)); } animations.put(root.getKey(), animation); } return animations; } private static double getRot(char value) { switch(value) { case 'X': return 0; case 'Y': return 1; case 'Z': return 2; default: return 0; } } private static BusAnimationSequence loadSequence(JsonObject json, double[] offset, double[] rotMode) { BusAnimationSequence sequence = new BusAnimationSequence(); // Location fcurves if(json.has("location")) { JsonObject location = json.getAsJsonObject("location"); if(location.has("x")) addToSequence(sequence, Dimension.TX, location.getAsJsonArray("x")); if(location.has("y")) addToSequence(sequence, Dimension.TY, location.getAsJsonArray("y")); if(location.has("z")) addToSequence(sequence, Dimension.TZ, location.getAsJsonArray("z")); } // Rotation fcurves, only euler at the moment if(json.has("rotation_euler")) { JsonObject rotation = json.getAsJsonObject("rotation_euler"); if(rotation.has("x")) addToSequence(sequence, Dimension.RX, rotation.getAsJsonArray("x")); if(rotation.has("y")) addToSequence(sequence, Dimension.RY, rotation.getAsJsonArray("y")); if(rotation.has("z")) addToSequence(sequence, Dimension.RZ, rotation.getAsJsonArray("z")); } // Scale fcurves if(json.has("scale")) { JsonObject scale = json.getAsJsonObject("scale"); if(scale.has("x")) addToSequence(sequence, Dimension.SX, scale.getAsJsonArray("x")); if(scale.has("y")) addToSequence(sequence, Dimension.SY, scale.getAsJsonArray("y")); if(scale.has("z")) addToSequence(sequence, Dimension.SZ, scale.getAsJsonArray("z")); } sequence.offset = offset; sequence.rotMode = rotMode; return sequence; } private static void addToSequence(BusAnimationSequence sequence, Dimension dimension, JsonArray array) { IType prevInterp = null; for(JsonElement element : array) { BusAnimationKeyframe keyframe = loadKeyframe(element, prevInterp); prevInterp = keyframe.interpolationType; sequence.addKeyframe(dimension, keyframe); } } private static BusAnimationKeyframe loadKeyframe(JsonElement element, IType prevInterp) { JsonArray array = element.getAsJsonArray(); double value = array.get(0).getAsDouble(); int duration = array.get(1).getAsInt(); IType interpolation = array.size() >= 3 ? IType.valueOf(array.get(2).getAsString()) : IType.LINEAR; EType easing = array.size() >= 4 ? EType.valueOf(array.get(3).getAsString()) : EType.AUTO; BusAnimationKeyframe keyframe = new BusAnimationKeyframe(value, duration, interpolation, easing); int i = 4; if(prevInterp == IType.BEZIER) { keyframe.leftX = array.get(i++).getAsDouble(); keyframe.leftY = array.get(i++).getAsDouble(); keyframe.leftType = HType.valueOf(array.get(i++).getAsString()); } if(interpolation == IType.LINEAR || interpolation == IType.CONSTANT) return keyframe; if(interpolation == IType.BEZIER) { keyframe.rightX = array.get(i++).getAsDouble(); keyframe.rightY = array.get(i++).getAsDouble(); keyframe.rightType = HType.valueOf(array.get(i++).getAsString()); } if(interpolation == IType.ELASTIC) { keyframe.amplitude = array.get(i++).getAsDouble(); keyframe.period = array.get(i++).getAsDouble(); } else if(interpolation == IType.BACK) { keyframe.back = array.get(i++).getAsDouble(); } return keyframe; } }