Package codechicken.lib.render

Source Code of codechicken.lib.render.QBImporter$RasterisedModel

package codechicken.lib.render;

import codechicken.lib.render.uv.UV;
import codechicken.lib.render.uv.UVScale;
import codechicken.lib.vec.*;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;

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 codechicken.lib.render.QBImporter$RasterisedModel

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.