Rendering/Model Fixes

This commit is contained in:
Sebastian Hartte
2020-07-18 18:44:10 +02:00
parent 1e20cd8c08
commit 19802b96e8
61 changed files with 499 additions and 495 deletions
@@ -23,6 +23,7 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Quaternion;
@@ -18,15 +18,11 @@
package appeng.client.render.effects;
import net.minecraft.client.particle.*;
import net.minecraft.client.render.VertexConsumer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.particle.IAnimatedSprite;
import net.minecraft.client.particle.IParticleFactory;
import net.minecraft.client.particle.IParticleRenderType;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.SpriteTexturedParticle;
import net.minecraft.client.render.Camera;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.MathHelper;
@@ -0,0 +1,217 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 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, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.client.render.model;
import appeng.block.storage.DriveSlotsState;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Supplier;
public class DriveBakedModel extends ForwardingBakedModel implements FabricBakedModel {
private final Map<Item, BakedModel> cellModels;
private final Map<Item, Mesh> bakedCells;
private final BakedModel defaultCellModel;
private final Mesh defaultCell;
private final RenderContext.QuadTransform[] slotTransforms;
public DriveBakedModel(BakedModel bakedBase, Map<Item, BakedModel> cellModels, BakedModel defaultCell) {
this.wrapped = bakedBase;
this.defaultCellModel = defaultCell;
this.defaultCell = convertCellModel(defaultCell);
this.slotTransforms = buildSlotTransforms();
this.bakedCells = convertCellModels(cellModels);
this.cellModels = cellModels;
}
/**
* Calculates the origin of a drive slot for positioning a cell model into it.
*/
public static void getSlotOrigin(int row, int col, Vector3f translation) {
// Position this drive model copy at the correct slot. The transform is based on
// the cell-model being in slot 0,0,0 while the upper left slot's origin is at
// 9,13,1
float xOffset = (9 - col * 8) / 16.0f;
float yOffset = (13 - row * 3) / 16.0f;
float zOffset = 1 / 16.0f;
translation.set(xOffset, yOffset, zOffset);
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
// Add cell models on top of the base model, if possible
DriveSlotsState slotsState = getDriveSlotsState(blockView, pos);
if (slotsState != null) {
for (int row = 0; row < 5; row++) {
for (int col = 0; col < 2; col++) {
int slot = getSlotIndex(row, col);
// Add the cell chassis
Item cell = slotsState.getCell(slot);
BakedModel cellChassisModel = getCellChassisModel(cell);
context.pushTransform(slotTransforms[slot]);
context.fallbackConsumer().accept(cellChassisModel);
context.meshConsumer().accept(getCellChassisMesh(cell));
context.popTransform();
}
}
}
}
private static DriveSlotsState getDriveSlotsState(BlockRenderView blockView, BlockPos pos) {
if (!(blockView instanceof RenderAttachedBlockView)) {
return null;
}
Object attachedData = ((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos);
if (!(attachedData instanceof DriveModelData)) {
return null;
}
return ((DriveModelData) attachedData).getSlotsState();
}
@Override
public boolean useAmbientOcclusion() {
// We have faces inside the chassis that are facing east, but should not receive
// ambient occlusion from the east-side, but sadly this cannot be fine-tuned on
// a face-by-face basis.
return false;
}
// Determine which drive chassis to show based on the used cell
public Mesh getCellChassisMesh(Item cell) {
if (cell == null) {
return bakedCells.get(Items.AIR);
}
final Mesh model = bakedCells.get(cell);
return model != null ? model : defaultCell;
}
public BakedModel getCellChassisModel(Item cell) {
if (cell == null) {
return cellModels.get(Items.AIR);
}
final BakedModel model = cellModels.get(cell);
return model != null ? model : defaultCellModel;
}
private RenderContext.QuadTransform[] buildSlotTransforms() {
RenderContext.QuadTransform[] result = new RenderContext.QuadTransform[5 * 2];
for (int row = 0; row < 5; row++) {
for (int col = 0; col < 2; col++) {
Vector3f translation = new Vector3f();
getSlotOrigin(row, col, translation);
result[getSlotIndex(row, col)] = new QuadTranslator(translation.getX(), translation.getY(), translation.getZ());
}
}
return result;
}
private static int getSlotIndex(int row, int col) {
return row * 2 + col;
}
private static class QuadTranslator implements RenderContext.QuadTransform {
private final float x;
private final float y;
private final float z;
public QuadTranslator(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean transform(MutableQuadView quad) {
Vector3f target = new Vector3f();
for (int i = 0; i < 4; i++) {
quad.copyPos(i, target);
target.add(x, y, z);
quad.pos(i, target);
}
return true;
}
}
private Map<Item, Mesh> convertCellModels(Map<Item, BakedModel> cellModels) {
Map<Item, Mesh> result = new IdentityHashMap<>();
for (Map.Entry<Item, BakedModel> entry : cellModels.entrySet()) {
result.put(entry.getKey(), convertCellModel(entry.getValue()));
}
return result;
}
private Mesh convertCellModel(BakedModel bakedModel) {
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
Random random = new Random();
MeshBuilder meshBuilder = renderer.meshBuilder();
QuadEmitter emitter = meshBuilder.getEmitter();
emitter.material(renderer.materialFinder().disableDiffuse(0, false).disableAo(0, true).find());
for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
Direction face = ModelHelper.faceFromIndex(i);
List<BakedQuad> quads = bakedModel.getQuads(null, face, random);
for (BakedQuad quad : quads) {
emitter.fromVanilla(quad.getVertexData(), 0, false);
emitter.cullFace(face);
emitter.nominalFace(face);
emitter.emit();
}
}
return meshBuilder.build();
}
}
@@ -0,0 +1,78 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 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, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.client.render.model;
import appeng.api.client.ICellModelRegistry;
import appeng.client.render.BasicUnbakedModel;
import appeng.core.Api;
import appeng.core.api.client.ApiCellModelRegistry;
import com.google.common.collect.ImmutableSet;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.util.Identifier;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
public class DriveModel implements BasicUnbakedModel {
private static final Identifier MODEL_BASE = new Identifier(
"appliedenergistics2:block/drive/drive_base");
private static final Identifier MODEL_CELL_EMPTY = new Identifier(
"appliedenergistics2:block/drive/drive_cell_empty");
@Nullable
@Override
public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
final ICellModelRegistry cellRegistry = Api.instance().client().cells();
final Map<Item, BakedModel> cellModels = new IdentityHashMap<>();
// Load the base model and the model for each cell model.
for (Entry<Item, Identifier> entry : cellRegistry.models().entrySet()) {
BakedModel cellModel = loader.bake(entry.getValue(), rotationContainer);
cellModels.put(entry.getKey(), cellModel);
}
final BakedModel baseModel = loader.bake(MODEL_BASE, rotationContainer);
final BakedModel defaultCell = loader.bake(cellRegistry.getDefaultModel(), rotationContainer);
cellModels.put(Items.AIR, loader.bake(MODEL_CELL_EMPTY, rotationContainer));
return new DriveBakedModel(baseModel, cellModels, defaultCell);
}
@Override
public Collection<Identifier> getModelDependencies() {
ICellModelRegistry cells = Api.instance().client().cells();
return ImmutableSet.<Identifier>builder()
.add(cells.getDefaultModel())
.addAll(ApiCellModelRegistry.getModels())
.addAll(cells.models().values())
.build();
}
}
@@ -2,15 +2,13 @@ package appeng.client.render.tesr;
import java.util.EnumMap;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import appeng.api.implementations.tiles.IChestOrDrive;
import appeng.block.storage.DriveSlotState;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.util.math.Vector3f;
/**
* Utility class to render LEDs for storage cells from a Tile Entity Renderer.
@@ -53,11 +51,11 @@ class CellLedRenderer {
// Bottom Face
R, B, FR, L, B, FR, L, B, BA, R, B, BA, };
public static final RenderType RENDER_LAYER = RenderType.makeType("ae_drive_leds",
DefaultVertexFormats.POSITION_COLOR, 7, 32565, false, true, RenderType.State.getBuilder().build(false));
public static final RenderLayer RENDER_LAYER = RenderLayer.of("ae_drive_leds",
VertexFormats.POSITION_COLOR, 7, 32565, false, true, RenderLayer.MultiPhaseParameters.builder().build(false));
public static void renderLed(IChestOrDrive drive, int slot, IVertexBuilder buffer, MatrixStack ms,
float partialTicks) {
public static void renderLed(IChestOrDrive drive, int slot, VertexConsumer buffer, MatrixStack ms,
float partialTicks) {
Vector3f color = getColorForSlot(drive, slot, partialTicks);
if (color == null) {
@@ -68,8 +66,8 @@ class CellLedRenderer {
float x = LED_QUADS[i];
float y = LED_QUADS[i + 1];
float z = LED_QUADS[i + 2];
buffer.pos(ms.getLast().getMatrix(), x, y, z).color(color.getX(), color.getY(), color.getZ(), 1.f)
.endVertex();
buffer.vertex(ms.peek().getModel(), x, y, z).color(color.getX(), color.getY(), color.getZ(), 1.f)
.next();
}
}
@@ -25,56 +25,57 @@ import java.util.Random;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import appeng.client.render.BakedModelUnwrapper;
import appeng.client.render.model.AutoRotatingBakedModel;
import appeng.client.render.model.DriveBakedModel;
import appeng.tile.storage.ChestBlockEntity;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
import appeng.api.client.ICellModelRegistry;
import appeng.block.storage.DriveSlotsState;
import appeng.client.render.DelegateBakedModel;
import appeng.client.render.FacingToRotation;
import appeng.core.Api;
import appeng.tile.storage.ChestTileEntity;
/**
* The tile entity renderer for ME chests takes care of rendering the right
* model for the inserted cell, as well as the LED.
*/
public class ChestTileEntityRenderer extends TileEntityRenderer<ChestTileEntity> {
public class ChestTileEntityRenderer extends BlockEntityRenderer<ChestBlockEntity> {
private final ICellModelRegistry cellModelRegistry = Api.instance().client().cells();
private final ModelManager modelManager;
private final BakedModelManager modelManager;
private final BlockModelRenderer blockRenderer;
public ChestTileEntityRenderer(TileEntityRendererDispatcher rendererDispatcherIn) {
super(rendererDispatcherIn);
Minecraft client = Minecraft.getInstance();
modelManager = client.getModelManager();
blockRenderer = client.getBlockRendererDispatcher().getBlockModelRenderer();
public ChestTileEntityRenderer(BlockEntityRenderDispatcher renderDispatcher) {
super(renderDispatcher);
MinecraftClient client = MinecraftClient.getInstance();
modelManager = client.getBakedModelManager();
blockRenderer = client.getBlockRenderManager().getModelRenderer();
}
@Override
public void render(ChestTileEntity chest, float partialTicks, MatrixStack matrices, IRenderTypeBuffer buffers,
int combinedLight, int combinedOverlay) {
public void render(ChestBlockEntity chest, float partialTicks, MatrixStack matrices, VertexConsumerProvider buffers,
int combinedLight, int combinedOverlay) {
World world = chest.getWorld();
if (world == null) {
@@ -88,12 +89,12 @@ public class ChestTileEntityRenderer extends TileEntityRenderer<ChestTileEntity>
return; // No cell inserted into chest
}
ResourceLocation cellModelLocation = cellModelRegistry.model(cellItem);
if (cellModelLocation == null) {
cellModelLocation = cellModelRegistry.getDefaultModel();
// Try to get the right cell chassis model from the drive model since it already loads them all
DriveBakedModel driveModel = getDriveModel();
if (driveModel == null) {
return;
}
IBakedModel model = modelManager.getModel(cellModelLocation);
BakedModel cellModel = driveModel.getCellChassisModel(cellItem);
matrices.push();
matrices.translate(0.5, 0.5, 0.5);
@@ -106,45 +107,49 @@ public class ChestTileEntityRenderer extends TileEntityRenderer<ChestTileEntity>
matrices.translate(5 / 16.0, 4 / 16.0, 0);
// Render the cell model as-if it was a block model
IVertexBuilder buffer = buffers.getBuffer(RenderType.getCutout());
VertexConsumer buffer = buffers.getBuffer(RenderLayer.getCutout());
// We "fake" the position here to make it use the light-value in front of the
// drive
FaceRotatingModel rotatedModel = new FaceRotatingModel(model, rotation);
blockRenderer.renderModel(world, rotatedModel, chest.getBlockState(), chest.getPos(), matrices, buffer, false,
new Random(), 0L, combinedOverlay, EmptyModelData.INSTANCE);
BakedModel rotatedModel = new FaceRotatingModel(cellModel, rotation);
blockRenderer.render(world, rotatedModel, chest.getCachedState(), chest.getPos(), matrices, buffer, false,
new Random(), 0L, combinedOverlay);
IVertexBuilder ledBuffer = buffers.getBuffer(CellLedRenderer.RENDER_LAYER);
VertexConsumer ledBuffer = buffers.getBuffer(CellLedRenderer.RENDER_LAYER);
CellLedRenderer.renderLed(chest, 0, ledBuffer, matrices, partialTicks);
matrices.pop();
}
private DriveBakedModel getDriveModel() {
BakedModel driveModel = modelManager.getBlockModels().getModel(Api.instance().definitions().blocks().drive().block().getDefaultState());
return BakedModelUnwrapper.unwrap(driveModel, DriveBakedModel.class);
}
/**
* The actual vertex data will be transformed using the matrix stack, but the
* faces will not be correctly rotated so the incorrect lighting data would be
* used to apply diffuse lighting and the lightmap texture.
*/
private static class FaceRotatingModel extends DelegateBakedModel {
private static class FaceRotatingModel extends ForwardingBakedModel {
private final FacingToRotation r;
protected FaceRotatingModel(IBakedModel base, FacingToRotation r) {
super(base);
protected FaceRotatingModel(BakedModel base, FacingToRotation r) {
this.wrapped = base;
this.r = r;
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand,
@Nonnull IModelData extraData) {
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand) {
if (side != null) {
side = r.rotate(side); // This fixes the incorrect lightmap position
side = r.resultingRotate(side); // This fixes the incorrect lightmap position
}
List<BakedQuad> quads = new ArrayList<>(super.getQuads(state, side, rand, extraData));
List<BakedQuad> quads = new ArrayList<>(super.getQuads(state, side, rand));
for (int i = 0; i < quads.size(); i++) {
BakedQuad quad = quads.get(i);
quads.set(i, new BakedQuad(quad.getVertexData(), quad.getTintIndex(), r.rotate(quad.getFace()),
quad.func_187508_a(), quad.shouldApplyDiffuseLighting()));
quads.set(i, new BakedQuad(quad.getVertexData(), quad.getColorIndex(), r.rotate(quad.getFace()),
/* FIXME FABRIC: sprite is protected but unused?? */ null, quad.hasShade()));
}
return quads;
@@ -1,8 +1,7 @@
package appeng.client.render.tesr;
import net.minecraft.client.render.RenderLayer;
import appeng.client.render.model.DriveBakedModel;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.util.math.MatrixStack;
@@ -13,7 +12,6 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import appeng.client.render.FacingToRotation;
import appeng.client.render.model.DriveBakedModel;
import appeng.tile.storage.DriveBlockEntity;
/**
@@ -22,8 +20,8 @@ import appeng.tile.storage.DriveBlockEntity;
@Environment(EnvType.CLIENT)
public class DriveLedTileEntityRenderer extends BlockEntityRenderer<DriveBlockEntity> {
public DriveLedTileEntityRenderer(BlockEntityRendererDispatcher rendererDispatcher) {
super(rendererDispatcher);
public DriveLedTileEntityRenderer(BlockEntityRenderDispatcher renderDispatcher) {
super(renderDispatcher);
}
@Override