package mekanism.client.render.ctm; import java.util.List; import javax.annotation.Nullable; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.renderer.vertex.VertexFormatElement; import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage; import net.minecraft.util.EnumFacing; import net.minecraftforge.client.model.pipeline.IVertexConsumer; import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.util.vector.Vector; import org.lwjgl.util.vector.Vector2f; import org.lwjgl.util.vector.Vector3f; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.MultimapBuilder; public class Quad { public static final ISubmap TOP_LEFT = new Submap(8, 8, 0, 0); public static final ISubmap TOP_RIGHT = new Submap(8, 8, 8, 0); public static final ISubmap BOTTOM_LEFT = new Submap(8, 8, 0, 8); public static final ISubmap BOTTOM_RIGHT = new Submap(8, 8, 8, 8); public static class Vertex { Vector3f pos; Vector2f uvs; } private static final TextureAtlasSprite BASE = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(TextureMap.LOCATION_MISSING_TEXTURE.toString()); public class UVs { private float minU, minV, maxU, maxV; private final TextureAtlasSprite sprite; private final Vector2f[] data; private UVs(Vector2f... data) { this(BASE, data); } private UVs(TextureAtlasSprite sprite, Vector2f... data) { this.data = data; this.sprite = sprite; float minU = Float.MAX_VALUE; float minV = Float.MAX_VALUE; float maxU = 0, maxV = 0; for (Vector2f v : data) { minU = Math.min(minU, v.x); minV = Math.min(minV, v.y); maxU = Math.max(maxU, v.x); maxV = Math.max(maxV, v.y); } this.minU = minU; this.minV = minV; this.maxU = maxU; this.maxV = maxV; } public UVs(float minU, float minV, float maxU, float maxV, TextureAtlasSprite sprite) { this.minU = minU; this.minV = minV; this.maxU = maxU; this.maxV = maxV; this.sprite = sprite; this.data = vectorize(); } public UVs transform(TextureAtlasSprite other, ISubmap submap) { UVs normal = normalize(); submap = submap.normalize(); float width = normal.maxU - normal.minU; float height = normal.maxV - normal.minV; float minU = submap.getXOffset(); float minV = submap.getYOffset(); minU += normal.minU * submap.getWidth(); minV += normal.minV * submap.getHeight(); float maxU = minU + (width * submap.getWidth()); float maxV = minV + (height * submap.getHeight()); // TODO this is horrid return new UVs(other, new Vector2f(data[0].x == this.minU ? minU : maxU, data[0].y == this.minV ? minV : maxV), new Vector2f(data[1].x == this.minU ? minU : maxU, data[1].y == this.minV ? minV : maxV), new Vector2f(data[2].x == this.minU ? minU : maxU, data[2].y == this.minV ? minV : maxV), new Vector2f(data[3].x == this.minU ? minU : maxU, data[3].y == this.minV ? minV : maxV)) .relativize(); } public UVs normalizeQuadrant() { UVs normal = normalize(); int quadrant = normal.getQuadrant(); float minUInterp = quadrant == 1 || quadrant == 2 ? 0.5f : 0; float minVInterp = quadrant < 2 ? 0.5f : 0; float maxUInterp = quadrant == 0 || quadrant == 3 ? 0.5f : 1; float maxVInterp = quadrant > 1 ? 0.5f : 1; normal = new UVs(sprite, normalize(new Vector2f(minUInterp, minVInterp), new Vector2f(maxUInterp, maxVInterp), normal.vectorize())); return normal.relativize(); } public UVs normalize() { Vector2f min = new Vector2f(sprite.getMinU(), sprite.getMinV()); Vector2f max = new Vector2f(sprite.getMaxU(), sprite.getMaxV()); return new UVs(sprite, normalize(min, max, data)); } public UVs relativize() { return relativize(sprite); } public TextureAtlasSprite getSprite() { return sprite; } public UVs relativize(TextureAtlasSprite sprite) { Vector2f min = new Vector2f(sprite.getMinU(), sprite.getMinV()); Vector2f max = new Vector2f(sprite.getMaxU(), sprite.getMaxV()); return new UVs(sprite, lerp(min, max, data)); } public Vector2f[] vectorize() { return data == null ? new Vector2f[]{ new Vector2f(minU, minV), new Vector2f(minU, maxV), new Vector2f(maxU, maxV), new Vector2f(maxU, minV) } : data; } private Vector2f[] normalize(Vector2f min, Vector2f max, Vector2f... vecs) { Vector2f[] ret = new Vector2f[vecs.length]; for(int i = 0; i < ret.length; i++) { ret[i] = normalize(min, max, vecs[i]); } return ret; } private Vector2f normalize(Vector2f min, Vector2f max, Vector2f vec) { return new Vector2f(Quad.normalize(min.x, max.x, vec.x), Quad.normalize(min.y, max.y, vec.y)); } private Vector2f[] lerp(Vector2f min, Vector2f max, Vector2f... vecs) { Vector2f[] ret = new Vector2f[vecs.length]; for(int i = 0; i < ret.length; i++) { ret[i] = lerp(min, max, vecs[i]); } return ret; } private Vector2f lerp(Vector2f min, Vector2f max, Vector2f vec) { return new Vector2f(Quad.lerp(min.x, max.x, vec.x), Quad.lerp(min.y, max.y, vec.y)); } public int getQuadrant() { if(maxU <= 0.5f) { if(maxV <= 0.5f) { return 3; } else { return 0; } } else { if(maxV <= 0.5f) { return 2; } else { return 1; } } } } private final Vector3f[] vertPos; private final Vector2f[] vertUv; // Technically nonfinal, but treated as such except in constructor private UVs uvsObj; private final Builder builder; private Quad(Vector3f[] verts, Vector2f[] uvs, Builder b) { vertPos = verts; vertUv = uvs; builder = b; uvsObj = new UVs(uvs); } private Quad(Vector3f[] verts, UVs uvs, Builder builder) { this(verts, uvs.vectorize(), builder); uvsObj = new UVs(uvs.getSprite(), vertUv); } public UVs getUVs() { return uvsObj; } public Quad[] subdivide(int count) { List<Quad> rects = Lists.newArrayList(); Pair<Quad, Quad> firstDivide = divide(false); Pair<Quad, Quad> secondDivide = firstDivide.getLeft().divide(true); rects.add(secondDivide.getLeft()); if(firstDivide.getRight() != null) { Pair<Quad, Quad> thirdDivide = firstDivide.getRight().divide(true); rects.add(thirdDivide.getLeft()); rects.add(thirdDivide.getRight()); } else { rects.add(null); rects.add(null); } rects.add(secondDivide.getRight()); return rects.toArray(new Quad[rects.size()]); } @Nullable private Pair<Quad, Quad> divide(boolean vertical) { float min, max; UVs uvs = uvsObj.normalize(); if(vertical) { min = uvs.minV; max = uvs.maxV; } else { min = uvs.minU; max = uvs.maxU; } if(min < 0.5 && max > 0.5) { UVs first = new UVs(vertical ? uvs.minU : 0.5f, vertical ? 0.5f : uvs.minV, uvs.maxU, uvs.maxV, uvs.getSprite()); UVs second = new UVs(uvs.minU, uvs.minV, vertical ? uvs.maxU : 0.5f, vertical ? 0.5f : uvs.maxV, uvs.getSprite()); int firstIndex = 0; for(int i = 0; i < vertUv.length; i++) { if(vertUv[i].y == uvsObj.minV && vertUv[i].x == uvsObj.minU) { firstIndex = i; break; } } float f = (0.5f - min) / (max - min); Vector3f[] firstQuad = new Vector3f[4]; Vector3f[] secondQuad = new Vector3f[4]; for(int i = 0; i < 4; i++) { int idx = (firstIndex + i) % 4; firstQuad[i] = new Vector3f(vertPos[idx]); secondQuad[i] = new Vector3f(vertPos[idx]); } int i1 = 0; int i2 = vertical ? 1 : 3; int j1 = vertical ? 3 : 1; int j2 = 2; firstQuad[i1].x = lerp(firstQuad[i1].x, firstQuad[i2].x, f); firstQuad[i1].y = lerp(firstQuad[i1].y, firstQuad[i2].y, f); firstQuad[i1].z = lerp(firstQuad[i1].z, firstQuad[i2].z, f); firstQuad[j1].x = lerp(firstQuad[j1].x, firstQuad[j2].x, f); firstQuad[j1].y = lerp(firstQuad[j1].y, firstQuad[j2].y, f); firstQuad[j1].z = lerp(firstQuad[j1].z, firstQuad[j2].z, f); secondQuad[i2].x = lerp(secondQuad[i1].x, secondQuad[i2].x, f); secondQuad[i2].y = lerp(secondQuad[i1].y, secondQuad[i2].y, f); secondQuad[i2].z = lerp(secondQuad[i1].z, secondQuad[i2].z, f); secondQuad[j2].x = lerp(secondQuad[j1].x, secondQuad[j2].x, f); secondQuad[j2].y = lerp(secondQuad[j1].y, secondQuad[j2].y, f); secondQuad[j2].z = lerp(secondQuad[j1].z, secondQuad[j2].z, f); Quad q1 = new Quad(firstQuad, first.relativize(), builder); Quad q2 = new Quad(secondQuad, second.relativize(), builder); return Pair.of(q1, q2); } else { return Pair.of(this, null); } } static float lerp(float a, float b, float f) { float ret = (a * (1 - f)) + (b * f); return ret; } static float normalize(float min, float max, float x) { float ret = (x - min) / (max - min); return ret; } public Quad rotate(int amount) { Vector2f[] uvs = new Vector2f[4]; TextureAtlasSprite s = uvsObj.getSprite(); for(int i = 0; i < 4; i++) { Vector2f normalized = new Vector2f(normalize(s.getMinU(), s.getMaxU(), vertUv[i].x), normalize(s.getMinV(), s.getMaxV(), vertUv[i].y)); Vector2f uv; switch(amount) { case 1: uv = new Vector2f(normalized.y, 1 - normalized.x); break; case 2: uv = new Vector2f(1 - normalized.x, 1 - normalized.y); break; case 3: uv = new Vector2f(1 - normalized.y, normalized.x); break; default: uv = new Vector2f(normalized.x, normalized.y); break; } uvs[i] = uv; } for(int i = 0; i < uvs.length; i++) { uvs[i] = new Vector2f(lerp(s.getMinU(), s.getMaxU(), uvs[i].x), lerp(s.getMinV(), s.getMaxV(), uvs[i].y)); } Quad ret = new Quad(vertPos, uvs, builder); ret.uvsObj = new UVs(uvsObj.getSprite(), ret.vertUv); return ret; } public Quad derotate() { int start = 0; for(int i = 0; i < 4; i++) { if(vertUv[i].x <= uvsObj.minU && vertUv[i].y <= uvsObj.minV) { start = i; break; } } Vector2f[] uvs = new Vector2f[4]; for(int i = 0; i < 4; i++) { uvs[i] = vertUv[(i + start) % 4]; } return new Quad(vertPos, uvs, builder); } public BakedQuad rebake() { UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(this.builder.vertexFormat); builder.setQuadOrientation(this.builder.quadOrientation); builder.setQuadTint(this.builder.quadTint); builder.setApplyDiffuseLighting(this.builder.applyDiffuseLighting); builder.setTexture(this.uvsObj.getSprite()); for(int v = 0; v < 4; v++) { for(int i = 0; i < this.builder.vertexFormat.getElementCount(); i++) { VertexFormatElement ele = this.builder.vertexFormat.getElement(i); switch(ele.getUsage()) { case UV: Vector2f uv = vertUv[v]; builder.put(i, uv.x, uv.y, 0, 1); break; case POSITION: Vector3f p = vertPos[v]; builder.put(i, p.x, p.y, p.z, 1); break; default: builder.put(i, this.builder.vertexData.get(ele.getUsage()).get(v)); } } } return builder.build(); } public Quad transformUVs(TextureAtlasSprite sprite) { return transformUVs(sprite, CTM.FULL_TEXTURE.normalize()); } public Quad transformUVs(TextureAtlasSprite sprite, ISubmap submap) { return new Quad(vertPos, uvsObj.transform(sprite, submap), builder); } public Quad grow() { return new Quad(vertPos, uvsObj.normalizeQuadrant(), builder); } public static Quad from(BakedQuad baked, VertexFormat fmt) { Builder b = new Builder(fmt); baked.pipe(b); return b.build().derotate(); // for now we will ignore rotated UVs } public static class Builder implements IVertexConsumer { public Builder(VertexFormat fmt) { vertexFormat = fmt; } private final VertexFormat vertexFormat; private int quadTint; private EnumFacing quadOrientation; private boolean applyDiffuseLighting; @Override public void setQuadTint(int tint) { quadTint = tint; } @Override public void setQuadOrientation(EnumFacing orientation) { quadOrientation = orientation; } @Override public VertexFormat getVertexFormat() { return vertexFormat; } @Override public void setApplyDiffuseLighting(boolean lighting) { applyDiffuseLighting = lighting; } private ListMultimap<EnumUsage, float[]> vertexData = MultimapBuilder.enumKeys(EnumUsage.class).arrayListValues().build(); @Override public void put(int element, float... data) { float[] copy = new float[data.length]; System.arraycopy(data, 0, copy, 0, data.length); VertexFormatElement ele = vertexFormat.getElement(element); vertexData.put(ele.getUsage(), copy); } public Quad build() { Vector3f[] verts = fromData(vertexData.get(EnumUsage.POSITION), 3); Vector2f[] uvs = fromData(vertexData.get(EnumUsage.UV), 2); return new Quad(verts, uvs, this); } @SuppressWarnings("unchecked") private <T extends Vector> T[] fromData(List<float[]> data, int size) { Vector[] ret = size == 2 ? new Vector2f[data.size()] : new Vector3f[data.size()]; for(int i = 0; i < data.size(); i++) { ret[i] = size == 2 ? new Vector2f(data.get(i)[0], data.get(i)[1]) : new Vector3f(data.get(i)[0], data.get(i)[1], data.get(i)[2]); } return (T[])ret; } } }