Package cofh.repack.codechicken.lib.render

Source Code of cofh.repack.codechicken.lib.render.QBImporter

package cofh.repack.codechicken.lib.render;

import cofh.repack.codechicken.lib.render.uv.UV;
import cofh.repack.codechicken.lib.render.uv.UVScale;
import cofh.repack.codechicken.lib.vec.BlockCoord;
import cofh.repack.codechicken.lib.vec.Cuboid6;
import cofh.repack.codechicken.lib.vec.CuboidCoord;
import cofh.repack.codechicken.lib.vec.Rectangle4i;
import cofh.repack.codechicken.lib.vec.Rotation;
import cofh.repack.codechicken.lib.vec.Scale;
import cofh.repack.codechicken.lib.vec.Transformation;
import cofh.repack.codechicken.lib.vec.Translation;
import cofh.repack.codechicken.lib.vec.Vector3;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;

public class QBImporter {

  private static class ImagePackNode {

    Rectangle4i rect;
    ImagePackNode child1;
    ImagePackNode child2;
    QBImage packed;

    public ImagePackNode(int x, int y, int w, int h) {

      rect = new Rectangle4i(x, y, w, h);
    }

    public boolean pack(QBImage img) {

      if (child1 != null) {
        return child1.pack(img) || child2.pack(img);
      }

      if (packed != null) {
        return false;
      }

      int fit = getFit(img.width(), img.height());
      if (fit == 0) {
        return false;
      }

      if ((fit & 2) != 0) {// exact fit
        packed = img;
        img.packSlot = rect;
        img.packT = new ImageTransform((fit & 1) << 2);
        return true;
      }

      int w = (fit & 1) == 0 ? img.width() : img.height();
      int h = (fit & 1) == 0 ? img.height() : img.width();

      if (rect.w - w > rect.h - h) {// create split with biggest leftover space
        child1 = new ImagePackNode(rect.x, rect.y, w, rect.h);
        child2 = new ImagePackNode(rect.x + w, rect.y, rect.w - w, rect.h);
      } else {
        child1 = new ImagePackNode(rect.x, rect.y, rect.w, h);
        child2 = new ImagePackNode(rect.x, rect.y + h, rect.w, rect.h - h);
      }
      return child1.pack(img);
    }

    private int getFit(int w, int h) {

      if (w == rect.w && h == rect.h) {
        return 2;
      }
      if (w == rect.h && h == rect.w) {
        return 3;
      }
      if (rect.w >= w && rect.h >= h) {
        return 4;
      }
      if (rect.w >= h && rect.h >= w) {
        return 5;
      }

      return 0;
    }

    private static void nextSize(Rectangle4i rect, boolean square) {

      if (square) {
        rect.w <<= 1;
        rect.h <<= 1;
      } else {
        if (rect.w == rect.h) {
          rect.w *= 2;
        } else {
          rect.h *= 2;
        }
      }
    }

    public static ImagePackNode pack(List<QBImage> images, boolean square) {

      Collections.sort(images);

      int area = 0;
      for (QBImage img : images) {
        area += img.area();
      }

      ImagePackNode node = new ImagePackNode(0, 0, 2, 2);
      while (node.rect.area() < area) {
        nextSize(node.rect, square);
      }

      while (true) {
        boolean packed = true;
        for (QBImage img : images) {
          if (!node.pack(img)) {
            packed = false;
            break;
          }
        }

        if (packed) {
          return node;
        }

        node.child1 = node.child2 = null;
        nextSize(node.rect, square);
      }
    }

    public BufferedImage toImage() {

      BufferedImage img = new BufferedImage(rect.w, rect.h, BufferedImage.TYPE_INT_ARGB);
      write(img);
      return img;
    }

    private void write(BufferedImage img) {

      if (child1 != null) {
        child1.write(img);
        child2.write(img);
      } else if (packed != null) {
        ImageTransform t = packed.packT;
        for (int u = 0; u < rect.w; u++) {
          for (int v = 0; v < rect.h; v++) {
            int rgba = t.access(packed, u, v);
            img.setRGB(u + rect.x, v + rect.y, rgba >>> 8 | rgba << 24);
          }
        }
      }
    }
  }

  private static class ImageTransform {

    int transform;

    public ImageTransform(int i) {

      transform = i;
    }

    public ImageTransform() {

      this(0);
    }

    public boolean transpose() {

      return (transform & 4) != 0;
    }

    public boolean flipU() {

      return (transform & 1) != 0;
    }

    public boolean flipV() {

      return (transform & 2) != 0;
    }

    public int access(QBImage img, int u, int v) {

      if (transpose()) {
        int tmp = u;
        u = v;
        v = tmp;
      }
      if (flipU()) {
        u = img.width() - 1 - u;
      }
      if (flipV()) {
        v = img.height() - 1 - v;
      }

      return img.data[u][v];
    }

    public UV transform(UV uv) {

      if (transpose()) {
        double tmp = uv.u;
        uv.u = uv.v;
        uv.v = tmp;
      }
      if (flipU()) {
        uv.u = 1 - uv.u;
      }
      if (flipV()) {
        uv.v = 1 - uv.v;
      }

      return uv;
    }
  }

  public static class QBImage implements Comparable<QBImage> {

    int[][] data;
    ImageTransform packT;
    Rectangle4i packSlot;

    public int width() {

      return data.length;
    }

    public int height() {

      return data[0].length;
    }

    public int area() {

      return width() * height();
    }

    @Override
    public int compareTo(QBImage o) {

      int a = area();
      int b = o.area();
      return a > b ? -1 : a == b ? 0 : 1;
    }

    public ImageTransform transformTo(QBImage img) {

      if (width() == img.width() && height() == img.height()) {
        for (int i = 0; i < 4; i++) {
          ImageTransform t = new ImageTransform(i);
          if (equals(img, t)) {
            return t;
          }
        }
      }
      if (width() == img.height() && height() == img.width()) {
        for (int i = 4; i < 8; i++) {
          ImageTransform t = new ImageTransform(i);
          if (equals(img, t)) {
            return t;
          }
        }
      }
      return null;
    }

    public boolean equals(QBImage img, ImageTransform t) {

      for (int u = 0; u < img.width(); u++) {
        for (int v = 0; v < img.height(); v++) {
          if (t.access(this, u, v) != img.data[u][v]) {
            return false;
          }
        }
      }

      return true;
    }

    public void transform(UV uv) {

      packT.transform(uv);
      uv.u *= packSlot.w;
      uv.v *= packSlot.h;
      uv.u += packSlot.x;
      uv.v += packSlot.y;
    }
  }

  private static final int[][] vertOrder = new int[][] {// clockwise because MC is left handed
  { 3, 0 }, { 1, 0 }, { 1, 2 }, { 3, 2 } };

  public static class QBQuad {

    public Vertex5[] verts = new Vertex5[4];
    public QBImage image = new QBImage();
    public ImageTransform t = new ImageTransform();
    public int side;

    public QBQuad(int side) {

      this.side = side;
    }

    public void applyImageT() {

      for (Vertex5 vert : verts) {
        t.transform(vert.uv);
        image.transform(vert.uv);
      }
    }

    public static QBQuad restore(Rectangle4i flat, int side, double d, QBImage img) {

      QBQuad quad = new QBQuad(side);
      quad.image = img;

      Transformation t = new Scale(-1, 1, -1).with(Rotation.sideOrientation(side, 0)).with(new Translation(new Vector3().setSide(side, d)));
      quad.verts[0] = new Vertex5(flat.x, 0, flat.y, 0, 0);
      quad.verts[1] = new Vertex5(flat.x + flat.w, 0, flat.y, 1, 0);
      quad.verts[2] = new Vertex5(flat.x + flat.w, 0, flat.y + flat.h, 1, 1);
      quad.verts[3] = new Vertex5(flat.x, 0, flat.y + flat.h, 0, 1);
      for (Vertex5 vert : quad.verts) {
        vert.apply(t);
      }

      return quad;
    }

    public Rectangle4i flatten() {

      Transformation t = Rotation.sideOrientation(side, 0).inverse().with(new Scale(-1, 0, -1));
      Vector3 vmin = verts[0].vec.copy().apply(t);
      Vector3 vmax = verts[2].vec.copy().apply(t);
      return new Rectangle4i((int) vmin.x, (int) vmin.z, (int) (vmax.x - vmin.x), (int) (vmax.z - vmin.z));
    }
  }

  public static class QBCuboid {

    public QBMatrix mat;
    public CuboidCoord c;
    public int sides;

    public QBCuboid(QBMatrix mat, CuboidCoord c) {

      this.mat = mat;
      this.c = c;
      sides = 0;
    }

    public static boolean intersects(QBCuboid a, QBCuboid b) {

      CuboidCoord c = a.c;
      CuboidCoord d = b.c;
      return c.min.x <= d.max.x && d.min.x <= c.max.x && c.min.y <= d.max.y && d.min.y <= c.max.y && c.min.z <= d.max.z && d.min.z <= c.max.z;
    }

    public static void clip(QBCuboid a, QBCuboid b) {

      if (intersects(a, b)) {
        a.clip(b);
        b.clip(a);
      }
    }

    public void clip(QBCuboid o) {

      CuboidCoord d = o.c;
      for (int a = 0; a < 6; a += 2) {
        int a1 = (a + 2) % 6;
        int a2 = (a + 4) % 6;
        if (c.getSide(a1 + 1) <= d.getSide(a1 + 1) && c.getSide(a1) >= d.getSide(a1) && c.getSide(a2 + 1) <= d.getSide(a2 + 1)
            && c.getSide(a2) >= d.getSide(a2)) {

          if (c.getSide(a) <= d.getSide(a + 1) && c.getSide(a) >= d.getSide(a)) {
            c.setSide(a, d.getSide(a + 1) + 1);
            sides |= 1 << a;
          }
          if (c.getSide(a + 1) >= d.getSide(a) && c.getSide(a + 1) <= d.getSide(a + 1)) {
            c.setSide(a + 1, d.getSide(a) - 1);
            sides |= 2 << a;
          }
        }
      }
    }

    public void extractQuads(List<QBQuad> quads) {

      Cuboid6 box = c.bounds();
      for (int s = 0; s < 6; s++) {
        if ((sides & 1 << s) == 0) {
          quads.add(extractQuad(s, box));
        }
      }
    }

    private QBQuad extractQuad(int side, Cuboid6 box) {

      double[] da = new double[3];
      da[side >> 1] = box.getSide(side);

      QBQuad quad = new QBQuad(side);
      for (int i = 0; i < 4; i++) {
        int rU = vertOrder[i][0];
        int rV = vertOrder[i][1];
        int sideU = Rotation.rotateSide(side, rU);
        int sideV = Rotation.rotateSide(side, rV);
        da[sideU >> 1] = box.getSide(sideU);
        da[sideV >> 1] = box.getSide(sideV);
        quad.verts[i] = new Vertex5(Vector3.fromAxes(da), (3 - rU) / 2, rV / 2);
      }

      int sideU = Rotation.rotateSide(side, 1);
      int sideV = Rotation.rotateSide(side, 2);
      quad.image.data = new int[c.size(sideU)][c.size(sideV)];
      QBImage image = quad.image;

      int[] ia = new int[3];
      ia[side >> 1] = c.getSide(side);
      ia[sideU >> 1] = c.getSide(sideU ^ 1);
      ia[sideV >> 1] = c.getSide(sideV ^ 1);
      BlockCoord b = BlockCoord.fromAxes(ia);
      BlockCoord bU = BlockCoord.sideOffsets[sideU];
      BlockCoord bV = BlockCoord.sideOffsets[sideV];
      for (int u = 0; u < image.width(); u++) {
        for (int v = 0; v < image.height(); v++) {
          image.data[u][v] = mat.matrix[b.x + bU.x * u + bV.x * v][b.y + bU.y * u + bV.y * v][b.z + bU.z * u + bV.z * v];
        }
      }

      return quad;
    }
  }

  public static class QBMatrix {

    public String name;
    public BlockCoord pos;
    public BlockCoord size;
    public int[][][] matrix;

    public void readMatrix(DataInputStream din, boolean compressed) throws IOException {

      if (compressed) {
        int z = 0;
        while (z < size.z) {
          int index = 0;

          while (true) {
            int data = din.readInt();

            if (data == NEXTSLICEFLAG) {
              break;
            }

            if (data == CODEFLAG) {
              int count = readTni(din);
              data = din.readInt();

              for (int j = 0; j < count; j++, index++) {
                matrix[index % size.x][index / size.x][z] = data;
              }
            } else {
              matrix[index % size.x][index / size.x][z] = data;
              index++;
            }
          }
          z++;
        }
      } else {
        for (int z = 0; z < size.z; z++) {
          for (int y = 0; y < size.y; y++) {
            for (int x = 0; x < size.x; x++) {
              matrix[x][y][z] = din.readInt();
            }
          }
        }
      }
    }

    public void convertBGRAtoRGBA() {

      for (int z = 0; z < size.z; z++) {
        for (int y = 0; y < size.y; y++) {
          for (int x = 0; x < size.x; x++) {
            int i = matrix[x][y][z];
            matrix[x][y][z] = Integer.reverseBytes(i >>> 8) | i & 0xFF;
          }
        }
      }
    }

    private boolean voxelFull(boolean[][][] solid, CuboidCoord c) {

      for (BlockCoord b : c) {
        if (matrix[b.x][b.y][b.z] == 0) {
          return false;
        }
      }
      for (BlockCoord b : c) {
        solid[b.x][b.y][b.z] = false;
      }
      return true;
    }

    private QBCuboid expand(boolean[][][] solid, BlockCoord b) {

      CuboidCoord c = new CuboidCoord(b);
      solid[b.x][b.y][b.z] = false;

      for (int s = 0; s < 6; s++) {
        CuboidCoord slice = c.copy();
        slice.expand(s ^ 1, -(slice.size(s) - 1));
        slice.expand(s, 1);

        while (slice.getSide(s) >= 0 && slice.getSide(s) < size.getSide(s)) {
          if (!voxelFull(solid, slice)) {
            break;
          }
          slice.expand(s ^ 1, -1);
          slice.expand(s, 1);
          c.expand(s, 1);
        }
      }
      return new QBCuboid(this, c);
    }

    public List<QBCuboid> rectangulate() {

      List<QBCuboid> list = new ArrayList<QBCuboid>();
      boolean[][][] solid = new boolean[size.x][size.y][size.z];
      for (int z = 0; z < size.z; z++) {
        for (int y = 0; y < size.y; y++) {
          for (int x = 0; x < size.x; x++) {
            solid[x][y][z] = matrix[x][y][z] != 0;
          }
        }
      }

      for (int x = 0; x < size.x; x++) {
        for (int z = 0; z < size.z; z++) {
          for (int y = 0; y < size.y; y++) {
            if (solid[x][y][z]) {
              list.add(expand(solid, new BlockCoord(x, y, z)));
            }
          }
        }
      }

      for (int i = 0; i < list.size(); i++) {
        for (int j = i + 1; j < list.size(); j++) {
          QBCuboid.clip(list.get(i), list.get(j));
        }
      }

      return list;
    }

    public List<QBQuad> extractQuads(boolean texturePlanes) {

      List<QBQuad> quads = new LinkedList<QBQuad>();
      for (QBCuboid c : rectangulate()) {
        c.extractQuads(quads);
      }

      if (texturePlanes) {
        optimisePlanes(quads);
      }

      return quads;
    }

    private void optimisePlanes(List<QBQuad> quads) {

      Multimap<Integer, QBQuad> map = HashMultimap.create();
      for (QBQuad quad : quads) {
        map.put(quad.side | ((int) quad.verts[0].vec.getSide(quad.side)) << 3, quad);
      }

      quads.clear();
      for (Integer key : map.keySet()) {
        Collection<QBQuad> plane = map.get(key);
        if (plane.size() == 1) {
          quads.add(plane.iterator().next());
          continue;
        }

        int side = key & 7;
        Rectangle4i rect = null;
        for (QBQuad q : plane) {
          if (rect == null) {
            rect = q.flatten();
          } else {
            rect.include(q.flatten());
          }
        }

        QBImage img = new QBImage();
        img.data = new int[rect.w][rect.h];
        for (QBQuad q : plane) {
          QBImage from = q.image;
          Rectangle4i r = q.flatten();
          int du = r.x - rect.x;
          int dv = r.y - rect.y;
          for (int u = 0; u < from.width(); u++) {
            for (int v = 0; v < from.height(); v++) {
              img.data[du + u][dv + v] = from.data[u][v];
            }
          }
        }

        quads.add(QBQuad.restore(rect, side, key >> 3, img));
      }
    }

    public CCModel buildModel(List<QBQuad> quads, BufferedImage img, boolean scaleMC) {

      CCModel m = CCModel.quadModel(quads.size() * 4);
      int i = 0;
      for (QBQuad quad : quads) {
        quad.applyImageT();
        m.verts[i++] = quad.verts[0];
        m.verts[i++] = quad.verts[1];
        m.verts[i++] = quad.verts[2];
        m.verts[i++] = quad.verts[3];
      }
      m.apply(new UVScale(1D / img.getWidth(), 1D / img.getHeight()));
      m.apply(new Translation(pos.x, pos.y, pos.z));
      if (scaleMC) {
        m.apply(new Scale(1 / 16D));
      }
      m.computeNormals();
      return m;
    }

    private static void addImages(List<QBQuad> quads, List<QBImage> images) {

      for (QBQuad q : quads) {
        QBImage img = q.image;
        boolean matched = false;
        for (QBImage img2 : images) {
          ImageTransform t = img.transformTo(img2);
          if (t != null) {
            q.t = t;
            q.image = img2;
            matched = true;
            break;
          }
        }
        if (!matched) {
          images.add(img);
        }
      }
    }
  }

  public static final int TEXTUREPLANES = 1;
  public static final int SQUARETEXTURE = 2;
  public static final int MERGETEXTURES = 4;
  public static final int SCALEMC = 8;

  public static class QBModel {

    public QBMatrix[] matrices;
    public boolean rightHanded;

    public RasterisedModel toRasterisedModel(int flags) {

      List<QBImage> qbImages = new ArrayList<QBImage>();
      List<List<QBQuad>> modelQuads = new ArrayList<List<QBQuad>>();
      List<BufferedImage> images = new ArrayList<BufferedImage>();

      boolean texturePlanes = (flags & TEXTUREPLANES) != 0;
      boolean squareTextures = (flags & SQUARETEXTURE) != 0;
      boolean mergeTextures = (flags & MERGETEXTURES) != 0;
      boolean scaleMC = (flags & SCALEMC) != 0;

      for (QBMatrix mat : matrices) {
        List<QBQuad> quads = mat.extractQuads(texturePlanes);
        modelQuads.add(quads);
        QBMatrix.addImages(quads, qbImages);
        if (!mergeTextures) {
          images.add(ImagePackNode.pack(qbImages, squareTextures).toImage());
          qbImages.clear();
        }
      }

      if (mergeTextures) {
        images.add(ImagePackNode.pack(qbImages, squareTextures).toImage());
      }

      RasterisedModel m = new RasterisedModel(images);
      for (int i = 0; i < matrices.length; i++) {
        QBMatrix mat = matrices[i];
        BufferedImage img = images.get(mergeTextures ? 0 : i);
        m.add(mat.name, mat.buildModel(modelQuads.get(i), img, scaleMC));
      }
      return m;
    }
  }

  public static class RasterisedModel {

    private class Holder {

      CCModel m;
      int img;

      public Holder(CCModel m, int img) {

        this.m = m;
        this.img = img;
      }
    }

    private Map<String, Holder> map = new HashMap<String, Holder>();
    private List<BufferedImage> images;
    private String[] icons;

    public RasterisedModel(List<BufferedImage> images) {

      this.images = images;
      icons = new String[images.size()];
    }

    public void add(String name, CCModel m) {

      map.put(name, new Holder(m, Math.min(map.size(), images.size() - 1)));
    }

    public CCModel getModel(String key) {

      return map.get(key).m;
    }

    public IIcon getIcon(String key, IIconRegister r, String iconName) {

      int img = map.get(key).img;
      if (icons[img] != null && !iconName.equals(icons[img])) {
        throw new IllegalArgumentException("Attempted to get a previously registered icon by a different name: " + icons[img] + ", " + iconName);
      }
      if (icons[img] != null) {
        return r.registerIcon(iconName);
      }

      icons[img] = iconName;
      return TextureUtils.getTextureSpecial(r, iconName).addTexture(new TextureDataHolder(images.get(img)));
    }

    private void exportImg(BufferedImage img, File imgFile) throws IOException {

      if (!imgFile.exists()) {
        imgFile.createNewFile();
      }
      ImageIO.write(img, "PNG", imgFile);
    }

    public void export(File objFile, File imgDir) {

      try {
        if (!objFile.exists()) {
          objFile.createNewFile();
        }
        if (!imgDir.exists()) {
          imgDir.mkdirs();
        }

        Map<String, CCModel> modelMap = new HashMap<String, CCModel>();
        for (Map.Entry<String, Holder> e : map.entrySet()) {
          modelMap.put(e.getKey(), e.getValue().m);
        }

        PrintWriter p = new PrintWriter(objFile);
        CCModel.exportObj(modelMap, p);
        p.close();

        if (images.size() < map.size()) {
          exportImg(images.get(0), new File(imgDir, objFile.getName().replaceAll("(.+)\\..+", "$1.png")));
        } else {
          for (Map.Entry<String, Holder> e : map.entrySet()) {
            exportImg(images.get(e.getValue().img), new File(imgDir, e.getKey() + ".png"));
          }
        }

      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  private static String readAsciiString(DataInputStream din) throws IOException {

    byte[] bytes = new byte[din.readByte() & 0xFF];
    din.readFully(bytes);
    return new String(bytes, "US-ASCII");
  }

  private static int readTni(DataInputStream din) throws IOException {

    return Integer.reverseBytes(din.readInt());
  }

  private static final int CODEFLAG = Integer.reverseBytes(2);
  private static final int NEXTSLICEFLAG = Integer.reverseBytes(6);

  public static QBModel loadQB(InputStream input) throws IOException {

    DataInputStream din = new DataInputStream(input);

    QBModel m = new QBModel();
    int version = din.readInt();
    int colorFormat = din.readInt();
    m.rightHanded = din.readInt() != 0;
    boolean compressed = din.readInt() != 0;
    boolean visEncoded = din.readInt() != 0;

    if (visEncoded) {
      throw new IllegalArgumentException("Encoded Visiblity States not supported");
    }

    m.matrices = new QBMatrix[readTni(din)];
    for (int i = 0; i < m.matrices.length; i++) {
      QBMatrix mat = new QBMatrix();
      m.matrices[i] = mat;
      mat.name = readAsciiString(din);
      mat.size = new BlockCoord(readTni(din), readTni(din), readTni(din));
      mat.pos = new BlockCoord(readTni(din), readTni(din), readTni(din));
      mat.matrix = new int[mat.size.x][mat.size.y][mat.size.z];
      mat.readMatrix(din, compressed);
      if (colorFormat == 1) {
        mat.convertBGRAtoRGBA();
      }
    }

    return m;
  }

  public static QBModel loadQB(ResourceLocation res) {

    try {
      return loadQB(Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream());
    } catch (Exception e) {
      throw new RuntimeException("failed to load model: " + res, e);
    }
  }

  public static QBModel loadQB(File file) {

    try {
      FileInputStream fin = new FileInputStream(file);
      try {
        return loadQB(fin);
      } finally {
        fin.close();
      }
    } catch (Exception e) {
      throw new RuntimeException("failed to load model: " + file.getPath(), e);
    }
  }
}
TOP

Related Classes of cofh.repack.codechicken.lib.render.QBImporter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.