/* * Minecraft Forge * Copyright (c) 2016. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.client.model.pipeline; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.renderer.vertex.VertexFormatElement; import net.minecraft.util.EnumFacing; // advantages: non-fixed-length vertex format, no overhead of packing and unpacking attributes to transform the model // disadvantages: (possibly) larger memory footprint, overhead on packing the attributes at the final rendering stage public class UnpackedBakedQuad extends BakedQuad { protected final float[][][] unpackedData; protected final VertexFormat format; protected boolean packed = false; public UnpackedBakedQuad(float[][][] unpackedData, int tint, EnumFacing orientation, TextureAtlasSprite texture, boolean applyDiffuseLighting, VertexFormat format) { super(new int[format.getNextOffset() /* / 4 * 4 */], tint, orientation, texture, applyDiffuseLighting, format); this.unpackedData = unpackedData; this.format = format; } @Override public int[] getVertexData() { if(!packed) { packed = true; for(int v = 0; v < 4; v++) { for(int e = 0; e < format.getElementCount(); e++) { LightUtil.pack(unpackedData[v][e], vertexData, format, v, e); } } } return vertexData; } @Override public void pipe(IVertexConsumer consumer) { int[] eMap = LightUtil.mapFormats(consumer.getVertexFormat(), format); if(hasTintIndex()) { consumer.setQuadTint(getTintIndex()); } consumer.setTexture(sprite); consumer.setApplyDiffuseLighting(applyDiffuseLighting); consumer.setQuadOrientation(getFace()); for(int v = 0; v < 4; v++) { for(int e = 0; e < consumer.getVertexFormat().getElementCount(); e++) { if(eMap[e] != format.getElementCount()) { consumer.put(e, unpackedData[v][eMap[e]]); } else { consumer.put(e); } } } } public static class Builder implements IVertexConsumer { private final VertexFormat format; private final float[][][] unpackedData; private int tint = -1; private EnumFacing orientation; private TextureAtlasSprite texture; private boolean applyDiffuseLighting = true; private int vertices = 0; private int elements = 0; private boolean full = false; private boolean contractUVs = false; public Builder(VertexFormat format) { this.format = format; unpackedData = new float[4][format.getElementCount()][4]; } public VertexFormat getVertexFormat() { return format; } public void setContractUVs(boolean value) { this.contractUVs = value; } public void setQuadTint(int tint) { this.tint = tint; } public void setQuadOrientation(EnumFacing orientation) { this.orientation = orientation; } // FIXME: move (or at least add) into constructor public void setTexture(TextureAtlasSprite texture) { this.texture = texture; } public void setApplyDiffuseLighting(boolean diffuse) { this.applyDiffuseLighting = diffuse; } public void put(int element, float... data) { for(int i = 0; i < 4; i++) { if(i < data.length) { unpackedData[vertices][element][i] = data[i]; } else { unpackedData[vertices][element][i] = 0; } } elements++; if(elements == format.getElementCount()) { vertices++; elements = 0; } if(vertices == 4) { full = true; } } private final float eps = 1f / 0x100; public UnpackedBakedQuad build() { if(!full) { throw new IllegalStateException("not enough data"); } if(texture == null) { throw new IllegalStateException("texture not set"); } if(contractUVs) { float tX = texture.getOriginX() / texture.getMinU(); float tY = texture.getOriginY() / texture.getMinV(); float tS = tX > tY ? tX : tY; float ep = 1f / (tS * 0x100); int uve = 0; while(uve < format.getElementCount()) { VertexFormatElement e = format.getElement(uve); if(e.getUsage() == VertexFormatElement.EnumUsage.UV && e.getIndex() == 0) { break; } uve++; } if(uve == format.getElementCount()) { throw new IllegalStateException("Can't contract UVs: format doesn't contain UVs"); } float[] uvc = new float[4]; for(int v = 0; v < 4; v++) { for(int i = 0; i < 4; i++) { uvc[i] += unpackedData[v][uve][i] / 4; } } for(int v = 0; v < 4; v++) { for (int i = 0; i < 4; i++) { float uo = unpackedData[v][uve][i]; float un = uo * (1 - eps) + uvc[i] * eps; float ud = uo - un; float aud = ud; if(aud < 0) aud = -aud; if(aud < ep) // not moving a fraction of a pixel { float udc = uo - uvc[i]; if(udc < 0) udc = -udc; if(udc < 2 * ep) // center is closer than 2 fractions of a pixel, don't move too close { un = (uo + uvc[i]) / 2; } else // move at least by a fraction { un = uo + (ud < 0 ? ep : -ep); } } unpackedData[v][uve][i] = un; } } } return new UnpackedBakedQuad(unpackedData, tint, orientation, texture, applyDiffuseLighting, format); } } }