/* * Copyright (c) 2017 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.wrapper.mc.forge.v18.wrapper.render.backward; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.renderer.vertex.VertexFormatElement; import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import nova.core.render.Color; import nova.core.render.model.Face; import nova.core.render.model.MeshModel; import nova.core.render.model.Model; import nova.core.render.model.Vertex; import nova.core.util.Direction; import nova.core.util.math.MatrixStack; import nova.core.util.math.TransformUtil; import nova.core.util.math.Vector3DUtil; import nova.core.wrapper.mc.forge.v18.wrapper.assets.AssetConverter; import nova.internal.core.Game; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; import org.apache.commons.math3.linear.LUDecomposition; import org.apache.commons.math3.linear.MatrixUtils; import org.apache.commons.math3.linear.RealMatrix; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * @author ExE Boss */ public class BWBakedModel extends MeshModel { @SuppressWarnings("deprecation") public final IBakedModel wrapped; public final VertexFormat format; public BWBakedModel(@SuppressWarnings("deprecation") IBakedModel wrapped) { this(wrapped, DefaultVertexFormats.ITEM); } @SuppressWarnings("unchecked") public BWBakedModel(@SuppressWarnings("deprecation") IBakedModel wrapped, VertexFormat format) { this.wrapped = wrapped; this.format = format; this.matrix.translate(-0.5, -0.5, -0.5); if (!((List<VertexFormatElement>) format.getElements()).stream().anyMatch(VertexFormatElement::isPositionElement)) return; // VertexFormat doesn't have a position getGeneralQuads().stream() .map(this::quadToFace) .forEachOrdered(faces::add); Arrays.stream(Direction.VALID_DIRECTIONS) .map(this::getFaceQuads) .flatMap(Collection::stream) .map(this::quadToFace) .forEachOrdered(faces::add); } @Override public Set<Model> flatten(MatrixStack matrixStack) { Set<Model> models = new HashSet<>(); matrixStack.pushMatrix(); matrixStack.transform(matrix.getMatrix()); //Create a new model with transformation applied. MeshModel transformedModel = clone(); // correct formula for Normal Matrix is transpose(inverse(mat3(model_mat)) // we have to augemnt that to 4x4 RealMatrix normalMatrix3x3 = new LUDecomposition(matrixStack.getMatrix().getSubMatrix(0, 2, 0, 2), 1e-5).getSolver().getInverse().transpose(); RealMatrix normalMatrix = MatrixUtils.createRealMatrix(4, 4); normalMatrix.setSubMatrix(normalMatrix3x3.getData(), 0, 0); normalMatrix.setEntry(3, 3, 1); transformedModel.faces.stream().forEach(f -> { f.normal = TransformUtil.transform(f.normal, normalMatrix); f.vertices.forEach(v -> { v.vec = matrixStack.apply(v.vec); v.normal = v.normal.map(n -> TransformUtil.transform(n, normalMatrix)); }); } ); models.add(transformedModel); //Flatten child models matrixStack.pushMatrix(); matrixStack.translate(0.5, 0.5, 0.5); models.addAll(children.stream().flatMap(m -> m.flatten(matrixStack).stream()).collect(Collectors.toSet())); matrixStack.popMatrix().popMatrix(); return models; } public List<BakedQuad> getFaceQuads(Direction direction) { if (direction == Direction.UNKNOWN) return getGeneralQuads(); return getFaceQuads((EnumFacing) Game.natives().toNative(direction)); } @SuppressWarnings("unchecked") public List<BakedQuad> getFaceQuads(EnumFacing direction) { return wrapped.getFaceQuads(direction); } @SuppressWarnings("unchecked") public List<BakedQuad> getGeneralQuads() { return wrapped.getGeneralQuads(); } @SuppressWarnings("unchecked") public Face quadToFace(BakedQuad quad) { Face face = new Face(); final VertexFormat format = this.format; // 1.11.2 stores VertexFormat on a per-quad basis. int[] data = quad.getVertexData(); Optional<TextureAtlasSprite> texture = Optional.ofNullable(wrapped.getTexture()); final Optional<VertexFormatElement> posElement = ((Collection<VertexFormatElement>)format.getElements()).stream() .filter(VertexFormatElement::isPositionElement) .findFirst(); final Optional<VertexFormatElement> uvElement = ((Collection<VertexFormatElement>)format.getElements()).stream() .filter(vfe -> vfe.getUsage() == VertexFormatElement.EnumUsage.UV) .findFirst(); face.texture = texture .filter(t -> uvElement.isPresent()) .map(TextureAtlasSprite::getIconName) .map(ResourceLocation::new) .map(AssetConverter.instance()::toNovaTexture); // `VertexFormat` offsets are for a `ByteBuffer` // `data` is an int array, so we convert the offsets // TODO: support offsets which are not divisible by four final int posOffset = posElement.map(VertexFormatElement::getOffset).map(i -> i / 4).orElse(-1); final int uvOffset = uvElement.map(VertexFormatElement::getOffset).map(i -> i / 4).orElse(-1); final int colorOffset = format.hasColor() ? (format.getColorOffset() / 4) : -1; final int normalOffset = format.hasNormal() ? (format.getNormalOffset() / 4) : -1; for (int i = 0; i < data.length; i += 7) { Vector3D pos = posElement.isPresent() ? new Vector3D( Float.intBitsToFloat(data[i + posOffset]), Float.intBitsToFloat(data[i + posOffset + 1]), Float.intBitsToFloat(data[i + posOffset + 2])) : Vector3D.ZERO; Vector2D uv = uvElement.isPresent() ? new Vector2D( deinterpolateU(Float.intBitsToFloat(data[i + uvOffset]), texture), deinterpolateV(Float.intBitsToFloat(data[i + uvOffset + 1]), texture)) : Vector2D.ZERO; Vertex vertex = new Vertex(pos, uv); if (format.hasColor()) { if (DefaultVertexFormats.BLOCK.equals(format)) vertex.color = Color.argb(data[i + colorOffset]); else vertex.color = Color.rgba(data[i + colorOffset]); } Optional<Vector3D> normal = Optional.empty(); if (format.hasNormal()) { int mergedNormal = data[i + normalOffset]; if (mergedNormal != 0) normal = Optional.of(new Vector3D(((byte)(mergedNormal & 0xFF)) / 127D, ((byte)((mergedNormal >> 8) & 0xFF)) / 127D, ((byte)((mergedNormal >> 16) & 0xFF)) / 127D)); } if (format.hasNormal()) vertex.normal = normal; face.drawVertex(vertex); } face.normal = Vector3DUtil.calculateNormal(face); return face; } private double deinterpolateU(double u, Optional<TextureAtlasSprite> texture) { if (!texture.isPresent()) return u; TextureAtlasSprite tex = texture.get(); return (u - tex.getMinU()) / (tex.getMaxU() - tex.getMinU()); } private double deinterpolateV(double v, Optional<TextureAtlasSprite> texture) { if (!texture.isPresent()) return v; TextureAtlasSprite tex = texture.get(); return (v - tex.getMinV()) / (tex.getMaxV() - tex.getMinV()); } }