Java tutorial
/* * Minecraft Forge * Copyright (c) 2016-2019. * * 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 java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.tuple.Pair; import net.minecraft.client.renderer.model.BakedQuad; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.renderer.vertex.VertexFormatElement; import net.minecraft.util.Direction; public class LightUtil { public static float diffuseLight(float x, float y, float z) { return Math.min(x * x * 0.6f + y * y * ((3f + y) / 4f) + z * z * 0.8f, 1f); } public static float diffuseLight(Direction side) { switch (side) { case DOWN: return .5f; case UP: return 1f; case NORTH: case SOUTH: return .8f; default: return .6f; } } public static Direction toSide(float x, float y, float z) { if (Math.abs(x) > Math.abs(y)) { if (Math.abs(x) > Math.abs(z)) { if (x < 0) return Direction.WEST; return Direction.EAST; } else { if (z < 0) return Direction.NORTH; return Direction.SOUTH; } } else { if (Math.abs(y) > Math.abs(z)) { if (y < 0) return Direction.DOWN; return Direction.UP; } else { if (z < 0) return Direction.NORTH; return Direction.SOUTH; } } } private static final ConcurrentMap<Pair<VertexFormat, VertexFormat>, int[]> formatMaps = new ConcurrentHashMap<>(); public static void putBakedQuad(IVertexConsumer consumer, BakedQuad quad) { consumer.setTexture(quad.getSprite()); consumer.setQuadOrientation(quad.getFace()); if (quad.hasTintIndex()) { consumer.setQuadTint(quad.getTintIndex()); } consumer.setApplyDiffuseLighting(quad.shouldApplyDiffuseLighting()); float[] data = new float[4]; VertexFormat formatFrom = consumer.getVertexFormat(); VertexFormat formatTo = DefaultVertexFormats.BLOCK; int countFrom = formatFrom.func_227894_c_().size(); int countTo = formatTo.func_227894_c_().size(); int[] eMap = mapFormats(formatFrom, formatTo); for (int v = 0; v < 4; v++) { for (int e = 0; e < countFrom; e++) { if (eMap[e] != countTo) { unpack(quad.getVertexData(), data, formatTo, v, eMap[e]); consumer.put(e, data); } else { consumer.put(e); } } } } // TODO: probably useless now, remove? private static final VertexFormat DEFAULT_FROM = VertexLighterFlat.withNormal(DefaultVertexFormats.BLOCK); private static final VertexFormat DEFAULT_TO = DefaultVertexFormats.BLOCK; private static final int[] DEFAULT_MAPPING = generateMapping(DEFAULT_FROM, DEFAULT_TO); public static int[] mapFormats(VertexFormat from, VertexFormat to) { //Speedup: in 99.99% this is the mapping, no need to go make a pair, and go through the slower hash map if (from.equals(DEFAULT_FROM) && to.equals(DEFAULT_TO)) return DEFAULT_MAPPING; return formatMaps.computeIfAbsent(Pair.of(from, to), pair -> generateMapping(pair.getLeft(), pair.getRight())); } private static int[] generateMapping(VertexFormat from, VertexFormat to) { int fromCount = from.func_227894_c_().size(); int toCount = to.func_227894_c_().size(); int[] eMap = new int[fromCount]; for (int e = 0; e < fromCount; e++) { VertexFormatElement expected = from.func_227894_c_().get(e); int e2; for (e2 = 0; e2 < toCount; e2++) { VertexFormatElement current = to.func_227894_c_().get(e2); if (expected.getUsage() == current.getUsage() && expected.getIndex() == current.getIndex()) { break; } } eMap[e] = e2; } return eMap; } public static void unpack(int[] from, float[] to, VertexFormat formatFrom, int v, int e) { int length = 4 < to.length ? 4 : to.length; VertexFormatElement element = formatFrom.func_227894_c_().get(e); int vertexStart = v * formatFrom.getSize() + formatFrom.getOffset(e); int count = element.getElementCount(); VertexFormatElement.Type type = element.getType(); VertexFormatElement.Usage usage = element.getUsage(); int size = type.getSize(); int mask = (256 << (8 * (size - 1))) - 1; for (int i = 0; i < length; i++) { if (i < count) { int pos = vertexStart + size * i; int index = pos >> 2; int offset = pos & 3; int bits = from[index]; bits = bits >>> (offset * 8); if ((pos + size - 1) / 4 != index) { bits |= from[index + 1] << ((4 - offset) * 8); } bits &= mask; if (type == VertexFormatElement.Type.FLOAT) { to[i] = Float.intBitsToFloat(bits); } else if (type == VertexFormatElement.Type.UBYTE || type == VertexFormatElement.Type.USHORT) { to[i] = (float) bits / mask; } else if (type == VertexFormatElement.Type.UINT) { to[i] = (float) ((double) (bits & 0xFFFFFFFFL) / 0xFFFFFFFFL); } else if (type == VertexFormatElement.Type.BYTE) { to[i] = ((float) (byte) bits) / (mask >> 1); } else if (type == VertexFormatElement.Type.SHORT) { to[i] = ((float) (short) bits) / (mask >> 1); } else if (type == VertexFormatElement.Type.INT) { to[i] = (float) ((double) (bits & 0xFFFFFFFFL) / (0xFFFFFFFFL >> 1)); } } else { to[i] = (i == 3 && usage == VertexFormatElement.Usage.POSITION) ? 1 : 0; } } } public static void pack(float[] from, int[] to, VertexFormat formatTo, int v, int e) { VertexFormatElement element = formatTo.func_227894_c_().get(e); int vertexStart = v * formatTo.getSize() + formatTo.getOffset(e); int count = element.getElementCount(); VertexFormatElement.Type type = element.getType(); int size = type.getSize(); int mask = (256 << (8 * (size - 1))) - 1; for (int i = 0; i < 4; i++) { if (i < count) { int pos = vertexStart + size * i; int index = pos >> 2; int offset = pos & 3; int bits = 0; float f = i < from.length ? from[i] : 0; if (type == VertexFormatElement.Type.FLOAT) { bits = Float.floatToRawIntBits(f); } else if (type == VertexFormatElement.Type.UBYTE || type == VertexFormatElement.Type.USHORT || type == VertexFormatElement.Type.UINT) { bits = Math.round(f * mask); } else { bits = Math.round(f * (mask >> 1)); } to[index] &= ~(mask << (offset * 8)); to[index] |= (((bits & mask) << (offset * 8))); // TODO handle overflow into to[index + 1] } } } private static final class ItemPipeline { final VertexBufferConsumer bufferConsumer; final ItemConsumer itemConsumer; ItemPipeline() { this.bufferConsumer = new VertexBufferConsumer(); this.itemConsumer = new ItemConsumer(bufferConsumer); } } private static final ThreadLocal<ItemPipeline> itemPipeline = ThreadLocal.withInitial(ItemPipeline::new); // renders quad in any Vertex Format, but is slower /*public static void renderQuadColorSlow(IVertexBuilder buffer, BakedQuad quad, float r, float g, float b, float a) { ItemPipeline pipeline = itemPipeline.get(); pipeline.bufferConsumer.setBuffer(buffer); ItemConsumer cons = pipeline.itemConsumer; cons.setAuxColor(r, g, b, a); quad.pipe(cons); } public static void renderQuadColor(IVertexBuilder builder, MatrixStack.Entry entry, BakedQuad quad, float r, float g, float b, float a, int light, int overlaylight) { if (quad.getFormat().equals(DefaultVertexFormats.BLOCK)) { builder.addVertexData(entry, quad, r, g, b, a, light, overlaylight); } else { renderQuadColorSlow(builder, quad, r, g, b, a); } }*/ public static class ItemConsumer extends VertexTransformer { private int vertices = 0; private float[] auxColor = new float[] { 1, 1, 1, 1 }; private float[] buf = new float[4]; public ItemConsumer(IVertexConsumer parent) { super(parent); } public void setAuxColor(float... auxColor) { System.arraycopy(auxColor, 0, this.auxColor, 0, this.auxColor.length); } @Override public void put(int element, float... data) { if (getVertexFormat().func_227894_c_().get(element).getUsage() == VertexFormatElement.Usage.COLOR) { System.arraycopy(auxColor, 0, buf, 0, buf.length); int n = Math.min(4, data.length); for (int i = 0; i < n; i++) { buf[i] *= data[i]; } super.put(element, buf); } else { super.put(element, data); } if (element == getVertexFormat().func_227894_c_().size() - 1) { vertices++; if (vertices == 4) { vertices = 0; } } } } }