From 7450370d99c6f23b8f8b963728a59e42eba5672b Mon Sep 17 00:00:00 2001 From: Chicken-Bomes Date: Tue, 28 Jan 2014 16:54:30 +1000 Subject: [PATCH] Added support for Qubicle Model files. --- codechicken/lib/render/CCModel.java | 81 ++- codechicken/lib/render/QBImporter.java | 755 +++++++++++++++++++++++++ codechicken/lib/render/UV.java | 8 + codechicken/lib/vec/BlockCoord.java | 35 ++ codechicken/lib/vec/CuboidCoord.java | 38 +- codechicken/lib/vec/Rectangle4i.java | 62 +- codechicken/lib/vec/Vector3.java | 40 ++ 7 files changed, 1006 insertions(+), 13 deletions(-) create mode 100644 codechicken/lib/render/QBImporter.java diff --git a/codechicken/lib/render/CCModel.java b/codechicken/lib/render/CCModel.java index 6b0620f..d90a545 100644 --- a/codechicken/lib/render/CCModel.java +++ b/codechicken/lib/render/CCModel.java @@ -1,9 +1,6 @@ package codechicken.lib.render; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -702,6 +699,82 @@ public class CCModel return model; } + private static int addIndex(List list, T elem) + { + int i = list.indexOf(elem)+1; + if(i == 0) { + list.add(elem); + i = list.size(); + } + return i; + } + + private static String clean(double d) { + return d == (int) d ? Integer.toString((int)d) : Double.toString(d); + } + + public static void exportObj(Map models, PrintWriter p) + { + List verts = new ArrayList(); + List uvs = new ArrayList(); + List normals = new ArrayList(); + List polys = new ArrayList(); + for(Map.Entry e : models.entrySet()) { + p.println("g "+e.getKey()); + CCModel m = e.getValue(); + + int vStart = verts.size(); + int uStart = uvs.size(); + int nStart = normals.size(); + boolean hasNormals = m.normals != null; + polys.clear(); + + for(int i = 0; i < m.verts.length; i++) { + int[] ia = new int[hasNormals ? 3 : 2]; + ia[0] = addIndex(verts, m.verts[i].vec); + ia[1] = addIndex(uvs, m.verts[i].uv); + if(hasNormals) + ia[2] = addIndex(normals, m.normals[i]); + polys.add(ia); + } + + if(vStart < verts.size()) { + p.println(); + for(int i = vStart; i < verts.size(); i++) { + Vector3 v = verts.get(i); + p.format("v %s %s %s\n", clean(v.x), clean(v.y), clean(v.z)); + } + } + if(uStart < uvs.size()) { + p.println(); + for(int i = uStart; i < uvs.size(); i++) { + UV uv = uvs.get(i); + p.format("vt %s %s\n", clean(uv.u), clean(uv.v)); + } + } + if(nStart < normals.size()) { + p.println(); + for(int i = nStart; i < normals.size(); i++) { + Vector3 n = normals.get(i); + p.format("vn %s %s %s\n", clean(n.x), clean(n.y), clean(n.z)); + } + } + + p.println(); + for(int i = 0; i < polys.size(); i++) { + if(i%m.vp == 0) + p.format("f"); + int[] ia = polys.get(i); + if(hasNormals) + p.format(" %d/%d/%d", ia[0], ia[1], ia[2]); + else + p.format(" %d/%d", ia[0], ia[1]); + if(i%m.vp == m.vp-1) + p.println(); + } + } + } + /** * Brings the UV coordinates of each face closer to the center UV by d. * Useful for fixing texture seams diff --git a/codechicken/lib/render/QBImporter.java b/codechicken/lib/render/QBImporter.java new file mode 100644 index 0000000..8af4aa3 --- /dev/null +++ b/codechicken/lib/render/QBImporter.java @@ -0,0 +1,755 @@ +package codechicken.lib.render; + +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.IconRegister; +import net.minecraft.util.Icon; +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 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 + { + 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<= d.getSide(a) && c.getSide(a+1) <= d.getSide(a+1)) { + c.setSide(a + 1, d.getSide(a) - 1); + sides|=2< quads) { + Cuboid6 box = c.bounds(); + for(int s = 0; s < 6; s++) + if((sides & 1<>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.shrink(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.shrink(s ^ 1, 1); + slice.expand(s, 1); + c.expand(s, 1); + } + } + return new QBCuboid(this, c); + } + + public List rectangulate() { + List list = new ArrayList(); + 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 extractQuads(boolean texturePlanes) { + List quads = new LinkedList(); + for(QBCuboid c : rectangulate()) + c.extractQuads(quads); + + if(texturePlanes) + optimisePlanes(quads); + + return quads; + } + + private void optimisePlanes(List quads) { + Multimap 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 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 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 quads, List 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 qbImages = new ArrayList(); + List> modelQuads = new ArrayList>(); + List images = new ArrayList(); + + 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 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 map = new HashMap(); + private List images; + private String[] icons; + + public RasterisedModel(List 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 Icon getIcon(String key, IconRegister 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 modelMap = new HashMap(); + for(Map.Entry 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 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); + } + } + + /** + * try { + File dump = new File("packed.png"); + if(!dump.exists()) dump.createNewFile(); + ImageIO.write(img, "PNG", dump); + + File dump2 = new File("packed.obj"); + if(!dump2.exists()) dump2.createNewFile(); + PrintWriter p = new PrintWriter(dump2); + CCModel.exportObj(Collections.singletonMap(name, m), p); + p.close(); + } catch(IOException e) { + e.printStackTrace(); + } + */ +} diff --git a/codechicken/lib/render/UV.java b/codechicken/lib/render/UV.java index 6233302..9181e5a 100644 --- a/codechicken/lib/render/UV.java +++ b/codechicken/lib/render/UV.java @@ -68,4 +68,12 @@ public class UV transform.transform(this); return this; } + + @Override + public boolean equals(Object o) { + if(!(o instanceof UV)) + return false; + UV uv = (UV)o; + return u == uv.u && v == uv.v; + } } \ No newline at end of file diff --git a/codechicken/lib/vec/BlockCoord.java b/codechicken/lib/vec/BlockCoord.java index e47dce1..547b830 100644 --- a/codechicken/lib/vec/BlockCoord.java +++ b/codechicken/lib/vec/BlockCoord.java @@ -35,6 +35,11 @@ public class BlockCoord implements Comparable { } + public static BlockCoord fromAxes(int[] ia) + { + return new BlockCoord(ia[2], ia[0], ia[1]); + } + @Override public boolean equals(Object obj) { @@ -146,6 +151,36 @@ public class BlockCoord implements Comparable { return offset(side, -amount); } + + public int getSide(int side) { + switch(side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public BlockCoord setSide(int s, int v) + { + switch(s) + { + case 0: + case 1: y = v; break; + case 2: + case 3: z = v; break; + case 4: + case 5: x = v; break; + default: throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } public static final BlockCoord[] sideOffsets = new BlockCoord[]{ new BlockCoord( 0,-1, 0), diff --git a/codechicken/lib/vec/CuboidCoord.java b/codechicken/lib/vec/CuboidCoord.java index 5551630..509477a 100644 --- a/codechicken/lib/vec/CuboidCoord.java +++ b/codechicken/lib/vec/CuboidCoord.java @@ -69,7 +69,36 @@ public class CuboidCoord implements Iterable return 0; } } - + + public int getSide(int s) + { + switch(s) + { + case 0: return min.y; + case 1: return max.y; + case 2: return min.z; + case 3: return max.z; + case 4: return min.x; + case 5: return max.x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public CuboidCoord setSide(int s, int v) + { + switch(s) + { + case 0: min.y = v; break; + case 1: max.y = v; break; + case 2: min.z = v; break; + case 3: max.z = v; break; + case 4: min.x = v; break; + case 5: max.x = v; break; + default: throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + public int getVolume() { return (max.x-min.x+1)*(max.y-min.y+1)*(max.z-min.z+1); @@ -108,9 +137,14 @@ public class CuboidCoord implements Iterable return new CuboidCoord(min.copy(), max.copy()); } + public Cuboid6 bounds() + { + return new Cuboid6(min.x, min.y, min.z, max.x+1, max.y+1, max.z+1); + } + public AxisAlignedBB toAABB() { - return AxisAlignedBB.getBoundingBox(min.x, min.y, min.z, max.x+1, max.y+1, max.z+1); + return bounds().toAABB(); } public void set(BlockCoord min, BlockCoord max) diff --git a/codechicken/lib/vec/Rectangle4i.java b/codechicken/lib/vec/Rectangle4i.java index 0792a97..0e29d35 100644 --- a/codechicken/lib/vec/Rectangle4i.java +++ b/codechicken/lib/vec/Rectangle4i.java @@ -18,6 +18,22 @@ public class Rectangle4i this.w = w; this.h = h; } + + public int x1() { + return x; + } + + public int y1() { + return y; + } + + public int x2() { + return x+w-1; + } + + public int y2() { + return y+h-1; + } public Rectangle4i offset(int dx, int dy) { @@ -25,13 +41,39 @@ public class Rectangle4i y+=dy; return this; } - + + @Deprecated public Rectangle4i with(int px, int py) { - if(x > px) x = px; - if(y > py) y = py; - if(x + w <= px) w = px-x+1; - if(y + h <= py) h = py-y+1; + return include(px, py); + } + + public Rectangle4i include(int px, int py) { + if(px < x) expand(px-x, 0); + if(px >= x+w) expand(px-x-w+1, 0); + if(py < y) expand(0, py-y); + if(py >= y+h) expand(0, py-y-h+1); + return this; + } + + public Rectangle4i include(Rectangle4i r) { + include(r.x, r.y); + return include(r.x2(), r.y2()); + } + + public Rectangle4i expand(int px, int py) { + if(px > 0) + w+=px; + else { + x+=px; + w-=px; + } + if(py > 0) + h+=py; + else { + y+=py; + h-=py; + } return this; } @@ -42,7 +84,13 @@ public class Rectangle4i public boolean intersects(Rectangle4i r) { - return x <= r.x+r.w && r.x <= x+w && - y <= r.y+r.h && r.y <= y+h; + return r.x+r.w > x && + r.x < x+w && + r.y+r.h > y && + r.y < y+h; + } + + public int area() { + return w * h; } } diff --git a/codechicken/lib/vec/Vector3.java b/codechicken/lib/vec/Vector3.java index 3a76e61..09d0a7f 100644 --- a/codechicken/lib/vec/Vector3.java +++ b/codechicken/lib/vec/Vector3.java @@ -45,6 +45,11 @@ public class Vector3 z = vec.z; } + public Vector3(double[] da) + { + this(da[0], da[1], da[2]); + } + public Vector3(Vec3 vec) { x = vec.xCoord; @@ -84,6 +89,11 @@ public class Vector3 return new Vector3(e.xCoord + 0.5, e.yCoord + 0.5, e.zCoord + 0.5); } + public static Vector3 fromAxes(double[] da) + { + return new Vector3(da[2], da[0], da[1]); + } + public Vector3 set(double d, double d1, double d2) { x = d; @@ -100,6 +110,36 @@ public class Vector3 return this; } + public double getSide(int side) { + switch(side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public Vector3 setSide(int s, double v) + { + switch(s) + { + case 0: + case 1: y = v; break; + case 2: + case 3: z = v; break; + case 4: + case 5: x = v; break; + default: throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + public double dotProduct(Vector3 vec) { double d = vec.x * x + vec.y * y + vec.z * z;