// Path: src/main/java/uk/kihira/gltf/Model.java
// public class Model implements IDisposable
// {
//     private final ArrayList<Node> allNodes;
//     private final ArrayList<Node> rootNodes;
//     private final HashMap<String, Animation> animations;
//     private final ArrayList<ResourceLocation> textures;
// 
//     public Model(ArrayList<Node> allNodes, ArrayList<Node> rootNodes, HashMap<String, Animation> animations, ArrayList<ResourceLocation> textures) {
//         this.allNodes = allNodes;
//         this.rootNodes = rootNodes;
//         this.animations = animations;
//         this.textures = textures;
//     }
// 
//     public void render(MatrixStack matrixStack)
//     {
//         for (Node node : rootNodes)
//         {
//             node.render(matrixStack);
//         }
//     }
// 
//     public void dispose()
//     {
//         allNodes.forEach(Node::dispose);
// 
//         // Textures
//         textures.forEach(texture -> Minecraft.getInstance().getTextureManager().deleteTexture(texture));
//         textures.clear();
//     }
// }
// 
// Path: src/main/java/uk/kihira/tails/client/outfit/OutfitPart.java
// public class OutfitPart {
//     public final UUID basePart;
//     public MountPoint mountPoint;
//     public float[] mountOffset; // [x,y,z]
//     public float[] rotation; // [x,y,z]
//     public float[] scale; // [x,y,z]
//     public float[][] tint; // [[r,g,b],[r,g,b]]
//     public UUID texture;
// 
//     // Client only fields
//     private transient Part part;
//     public transient ResourceLocation textureLoc;
// 
//     public OutfitPart(Part part) {
//         this.basePart = part.id;
//         this.mountPoint = part.mountPoint;
//         this.mountOffset = part.mountOffset;
//         this.rotation = part.rotation;
//         this.scale = part.scale;
//         this.tint = part.tint;
//         this.texture = part.textures[0];
// 
//         this.textureLoc = new ResourceLocation(Tails.MOD_ID, this.texture.toString());
//     }
// 
//     /**
//      * Gets the {@link Part} that has the ID for {@link #basePart}
//      * @return The part
//      */
//     @Nullable
//     public Part getPart() 
//     {
//         if (part == null) 
//         {
//             part = PartRegistry.getPart(basePart).orElse(null);
//         }
//         return part;
//     }
// }
// 
// Path: src/main/java/uk/kihira/tails/common/Tails.java
// @Mod(Tails.MOD_ID)
// public class Tails 
// {
//     public static final String MOD_ID = "tails";
//     public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
//     public static final Gson GSON = new GsonBuilder().create();
//     public static final boolean DEBUG = true;
// 
//     public static boolean hasRemote;
// 
//     public static CommonProxy proxy = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new);
// 
//     public Tails()
//     {
//         FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
//         FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onConfigChange);
//         MinecraftForge.EVENT_BUS.addListener(this::addReloadListener);
//     }
// 
//     private void setup(final FMLCommonSetupEvent event)
//     {
//         proxy.preInit();
//         Config.loadConfig();
// 
//         PartRegistry.loadAllPartsFromResources();
//     }
// 
//     private void addReloadListener(final AddReloadListenerEvent event)
//     {
//         event.addListener((stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor) ->
//                 CompletableFuture.runAsync(() -> proxy.onResourceManagerReload()));
//     }
// 
//     public void onConfigChange(final ModConfigEvent event)
//     {
//         if (event.getConfig().getModId().equals(Tails.MOD_ID)) 
//         {
//             Config.loadConfig();
//         }
//     }
// 
//     public static void setLocalOutfit(final Outfit outfit)
//     {
//         Config.localOutfit.set(outfit);
//         Config.configuration.save();
//     }
// }


import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.util.math.vector.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import uk.kihira.gltf.Model;
import uk.kihira.tails.client.outfit.OutfitPart;
import uk.kihira.tails.common.Tails;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;
        shader.registerUniform("tints");
    }

    /**
     * Gets a {@link FloatBuffer} from the pool. If there is none left, creates a new one
     *
     * @return
     */
    private FloatBuffer getFloatBuffer()
    {
        if (bufferPool.size() == 0) {
            return BufferUtils.createFloatBuffer(16);
        } else return bufferPool.pop();
    }

    /**
     * Returns a {@link FloatBuffer} back to the pool
     */
    private void freeFloatBuffer(FloatBuffer buffer)
    {
        bufferPool.add(buffer);
    }

    /**
     * Queues up a part to be rendered
     */
    public void render(MatrixStack matrixStack, OutfitPart part)
    {
        GL11.glPushMatrix();
        matrixStack.push();
// Path: src/main/java/uk/kihira/gltf/Model.java
// public class Model implements IDisposable
// {
//     private final ArrayList<Node> allNodes;
//     private final ArrayList<Node> rootNodes;
//     private final HashMap<String, Animation> animations;
//     private final ArrayList<ResourceLocation> textures;
// 
//     public Model(ArrayList<Node> allNodes, ArrayList<Node> rootNodes, HashMap<String, Animation> animations, ArrayList<ResourceLocation> textures) {
//         this.allNodes = allNodes;
//         this.rootNodes = rootNodes;
//         this.animations = animations;
//         this.textures = textures;
//     }
// 
//     public void render(MatrixStack matrixStack)
//     {
//         for (Node node : rootNodes)
//         {
//             node.render(matrixStack);
//         }
//     }
// 
//     public void dispose()
//     {
//         allNodes.forEach(Node::dispose);
// 
//         // Textures
//         textures.forEach(texture -> Minecraft.getInstance().getTextureManager().deleteTexture(texture));
//         textures.clear();
//     }
// }
// 
// Path: src/main/java/uk/kihira/tails/client/outfit/OutfitPart.java
// public class OutfitPart {
//     public final UUID basePart;
//     public MountPoint mountPoint;
//     public float[] mountOffset; // [x,y,z]
//     public float[] rotation; // [x,y,z]
//     public float[] scale; // [x,y,z]
//     public float[][] tint; // [[r,g,b],[r,g,b]]
//     public UUID texture;
// 
//     // Client only fields
//     private transient Part part;
//     public transient ResourceLocation textureLoc;
// 
//     public OutfitPart(Part part) {
//         this.basePart = part.id;
//         this.mountPoint = part.mountPoint;
//         this.mountOffset = part.mountOffset;
//         this.rotation = part.rotation;
//         this.scale = part.scale;
//         this.tint = part.tint;
//         this.texture = part.textures[0];
// 
//         this.textureLoc = new ResourceLocation(Tails.MOD_ID, this.texture.toString());
//     }
// 
//     /**
//      * Gets the {@link Part} that has the ID for {@link #basePart}
//      * @return The part
//      */
//     @Nullable
//     public Part getPart() 
//     {
//         if (part == null) 
//         {
//             part = PartRegistry.getPart(basePart).orElse(null);
//         }
//         return part;
//     }
// }
// 
// Path: src/main/java/uk/kihira/tails/common/Tails.java
// @Mod(Tails.MOD_ID)
// public class Tails 
// {
//     public static final String MOD_ID = "tails";
//     public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
//     public static final Gson GSON = new GsonBuilder().create();
//     public static final boolean DEBUG = true;
// 
//     public static boolean hasRemote;
// 
//     public static CommonProxy proxy = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new);
// 
//     public Tails()
//     {
//         FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
//         FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onConfigChange);
//         MinecraftForge.EVENT_BUS.addListener(this::addReloadListener);
//     }
// 
//     private void setup(final FMLCommonSetupEvent event)
//     {
//         proxy.preInit();
//         Config.loadConfig();
// 
//         PartRegistry.loadAllPartsFromResources();
//     }
// 
//     private void addReloadListener(final AddReloadListenerEvent event)
//     {
//         event.addListener((stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor) ->
//                 CompletableFuture.runAsync(() -> proxy.onResourceManagerReload()));
//     }
// 
//     public void onConfigChange(final ModConfigEvent event)
//     {
//         if (event.getConfig().getModId().equals(Tails.MOD_ID)) 
//         {
//             Config.loadConfig();
//         }
//     }
// 
//     public static void setLocalOutfit(final Outfit outfit)
//     {
//         Config.localOutfit.set(outfit);
//         Config.configuration.save();
//     }
// }


// Path: src/main/java/uk/kihira/tails/client/PartRenderer.java
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.util.math.vector.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import uk.kihira.gltf.Model;
import uk.kihira.tails.client.outfit.OutfitPart;
import uk.kihira.tails.common.Tails;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;

        shader.registerUniform("tints");
    }

    /**
     * Gets a {@link FloatBuffer} from the pool. If there is none left, creates a new one
     *
     * @return
     */
    private FloatBuffer getFloatBuffer()
    {
        if (bufferPool.size() == 0) {
            return BufferUtils.createFloatBuffer(16);
        } else return bufferPool.pop();
    }

    /**
     * Returns a {@link FloatBuffer} back to the pool
     */
    private void freeFloatBuffer(FloatBuffer buffer)
    {
        bufferPool.add(buffer);
    }

    /**
     * Queues up a part to be rendered
     */
    public void render(MatrixStack matrixStack, OutfitPart part)
    {
        GL11.glPushMatrix();
        matrixStack.push();
        matrixStack.translate(part.mountOffset[0], part.mountOffset[1], part.mountOffset[2]);