Render without BlockEntityRenderer (#2)

* Add model-based rendering

* Add model-based rendering

* Rebase, clean up, fix rendering

* Update to newer Fabric API - fixes Indigo bug w/ render layers

* Fuss with lighting, ran out of time.
This commit is contained in:
grondag 2019-06-19 11:42:45 -07:00 committed by Meredith Espinosa
parent 07abe31ea4
commit 197f57f202
13 changed files with 649 additions and 246 deletions

View File

@ -44,7 +44,7 @@ dependencies {
minecraft "com.mojang:minecraft:1.14.2" minecraft "com.mojang:minecraft:1.14.2"
mappings "net.fabricmc:yarn:1.14.2+build.7" mappings "net.fabricmc:yarn:1.14.2+build.7"
modCompile "net.fabricmc:fabric-loader:0.4.8+build.154" modCompile "net.fabricmc:fabric-loader:0.4.8+build.154"
modCompile "net.fabricmc.fabric-api:fabric-api:0.3.0+build.179" modCompile "net.fabricmc.fabric-api:fabric-api:0.3.0+build.185"
implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.code.findbugs:jsr305:3.0.2"

View File

@ -15,7 +15,8 @@ public class SlopeTest implements ModInitializer {
public static final String MODID = "slopetest"; public static final String MODID = "slopetest";
public static final Block SLOPE = register("slope", new SlopeTestBlock(), ItemGroup.DECORATIONS); public static final Block SLOPE = register("slope", new SlopeTestBlock(), ItemGroup.DECORATIONS);
public static final BlockEntityType<SlopeTestEntity> SLOPE_ENTITY = register("slope", SlopeTestEntity::new, SLOPE); @SuppressWarnings("unchecked")
public static final BlockEntityType<SlopeTestEntity> SLOPE_ENTITY = register("slope", SlopeTestEntity::new, SLOPE);
@Override @Override
public void onInitialize() { public void onInitialize() {
@ -29,7 +30,8 @@ public class SlopeTest implements ModInitializer {
return block; return block;
} }
public static BlockEntityType register(String name, Supplier<BlockEntity> be, Block...blocks) { @SuppressWarnings("rawtypes")
public static BlockEntityType register(String name, Supplier<BlockEntity> be, Block...blocks) {
return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, name), BlockEntityType.Builder.create(be, blocks).build(null)); return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, name), BlockEntityType.Builder.create(be, blocks).build(null));
} }

View File

@ -1,13 +1,13 @@
package io.github.cottonmc.slopetest; package io.github.cottonmc.slopetest;
import io.github.cottonmc.slopetest.block.entity.SlopeTestEntity; import io.github.cottonmc.slopetest.model.SlopeModelVariantProvider;
import io.github.cottonmc.slopetest.block.entity.render.SlopeTestRenderer;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.render.BlockEntityRendererRegistry; import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
public class SlopeTestClient implements ClientModInitializer { public class SlopeTestClient implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
BlockEntityRendererRegistry.INSTANCE.register(SlopeTestEntity.class, new SlopeTestRenderer()); //BlockEntityRendererRegistry.INSTANCE.register(SlopeTestEntity.class, new SlopeTestRenderer());
ModelLoadingRegistry.INSTANCE.registerVariantProvider(rm -> new SlopeModelVariantProvider());
} }
} }

View File

@ -84,10 +84,10 @@ public class SlopeTestBlock extends Block implements BlockEntityProvider {
return false; return false;
} }
@Override // @Override
public BlockRenderType getRenderType(BlockState state) { // public BlockRenderType getRenderType(BlockState state) {
return BlockRenderType.INVISIBLE; // return BlockRenderType.INVISIBLE;
} // }
@Override @Override
public void onBlockRemoved(BlockState state, World world, BlockPos pos, BlockState newState, boolean boolean_1) { public void onBlockRemoved(BlockState state, World world, BlockPos pos, BlockState newState, boolean boolean_1) {

View File

@ -3,18 +3,18 @@ package io.github.cottonmc.slopetest.block.entity;
import io.github.cottonmc.slopetest.SlopeTest; import io.github.cottonmc.slopetest.SlopeTest;
import io.github.cottonmc.slopetest.util.BlockStateUtil; import io.github.cottonmc.slopetest.util.BlockStateUtil;
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable; import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.fabricmc.fabric.api.server.PlayerStream; import net.fabricmc.fabric.api.server.PlayerStream;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
public class SlopeTestEntity extends BlockEntity implements BlockEntityClientSerializable {
private BlockState renderedState = Blocks.AIR.getDefaultState();
public class SlopeTestEntity extends BlockEntity implements BlockEntityClientSerializable, RenderAttachmentBlockEntity {
private BlockState renderedState = Blocks.AIR.getDefaultState();
public SlopeTestEntity() { public SlopeTestEntity() {
super(SlopeTest.SLOPE_ENTITY); super(SlopeTest.SLOPE_ENTITY);
} }
@ -32,6 +32,9 @@ public class SlopeTestEntity extends BlockEntity implements BlockEntityClientSer
public void fromTag(CompoundTag tag) { public void fromTag(CompoundTag tag) {
super.fromTag(tag); super.fromTag(tag);
renderedState = BlockStateUtil.fromTag(tag); renderedState = BlockStateUtil.fromTag(tag);
if (world.isClient) {
((ClientWorld)world).scheduleBlockRender(pos);
}
} }
@Override @Override
@ -61,4 +64,9 @@ public class SlopeTestEntity extends BlockEntity implements BlockEntityClientSer
} }
} }
} }
@Override
public BlockState getRenderAttachmentData() {
return renderedState;
}
} }

View File

@ -1,204 +0,0 @@
package io.github.cottonmc.slopetest.block.entity.render;
import com.mojang.blaze3d.platform.GlStateManager;
import io.github.cottonmc.slopetest.util.SpriteSet;
import io.github.cottonmc.slopetest.block.entity.SlopeTestEntity;
import net.fabricmc.fabric.api.client.render.ColorProviderRegistry;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColorProvider;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.Direction;
import org.lwjgl.opengl.GL11;
public class SlopeTestRenderer extends BlockEntityRenderer<SlopeTestEntity> {
@Override
public void render(SlopeTestEntity be, double x, double y, double z, float partialTicks, int destroyStage) {
final Tessellator tessellator = Tessellator.getInstance();
final BufferBuilder buffer = tessellator.getBufferBuilder();
MinecraftClient minecraft = MinecraftClient.getInstance();
buffer.setOffset(x, y, z);
GlStateManager.enableBlend();
GlStateManager.disableAlphaTest();
GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
renderManager.textureManager.bindTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEX);
BlockState state = getWorld().getBlockState(be.getPos());
Direction dir = state.get(Properties.HORIZONTAL_FACING);
SpriteSet sprites;
int color = 0xffffff;
if (be.getRenderedState().getBlock() != Blocks.AIR) {
BlockState renderedState = be.getRenderedState();
BakedModel model = minecraft.getBlockRenderManager().getModel(renderedState);
sprites = new SpriteSet(model);
BlockColorProvider blockColor = ColorProviderRegistry.BLOCK.get(be.getRenderedState().getBlock());
if (blockColor != null) {
color = blockColor.getColor(renderedState, be.getWorld(), be.getPos(), 1);
}
} else {
sprites = new SpriteSet(minecraft.getSpriteAtlas().getSprite("minecraft:block/scaffolding_top"), false);
}
buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_UV_COLOR);
drawSlope(dir, sprites.getSprite(Direction.UP), buffer, sprites.hasColor(Direction.UP)? color : 0xffffff);
drawLeftSide(dir, sprites.getSprite(dir.rotateYCounterclockwise()), buffer, sprites.hasColor(dir.rotateYCounterclockwise())? color : 0xffffff);
drawRightSide(dir, sprites.getSprite(dir.rotateYClockwise()), buffer, sprites.hasColor(dir.rotateYClockwise())? color: 0xffffff);
drawBack(dir, sprites.getSprite(dir), buffer, sprites.hasColor(dir)? color : 0xffffff);
drawBottom(sprites.getSprite(Direction.DOWN), buffer, sprites.hasColor(Direction.DOWN)? color : 0xffffff);
GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
tessellator.draw();
GlStateManager.disableBlend();
GlStateManager.enableAlphaTest();
buffer.setOffset(0.0d, 0.0d, 0.0d);
super.render(be, x, y, z, partialTicks, destroyStage);
}
public static void drawSlope(Direction dir, Sprite sprite, BufferBuilder buffer, int color) {
float r = (float)(color >> 16 & 255) / 255.0F;
float g = (float)(color >> 8 & 255) / 255.0F;
float b = (float)(color & 255) / 255.0F;
switch (dir) {
case NORTH:
buffer.vertex(0f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case SOUTH:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
break;
case EAST:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case WEST:
buffer.vertex(0f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
default:
break;
}
}
public static void drawLeftSide(Direction dir, Sprite sprite, BufferBuilder buffer, int color) {
float r = (float)(color >> 16 & 255) / 255.0F;
float g = (float)(color >> 8 & 255) / 255.0F;
float b = (float)(color & 255) / 255.0F;
switch(dir) {
case NORTH:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case SOUTH:
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
break;
case EAST:
buffer.vertex(1f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case WEST:
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
default:
break;
}
}
public static void drawRightSide(Direction dir, Sprite sprite, BufferBuilder buffer, int color) {
float r = (float)(color >> 16 & 255) / 255.0F;
float g = (float)(color >> 8 & 255) / 255.0F;
float b = (float)(color & 255) / 255.0F;
switch(dir) {
case NORTH:
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
break;
case SOUTH:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case EAST:
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case WEST:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
default:
break;
}
}
public static void drawBack(Direction dir, Sprite sprite, BufferBuilder buffer, int color) {
float r = (float)(color >> 16 & 255) / 255.0F;
float g = (float)(color >> 8 & 255) / 255.0F;
float b = (float)(color & 255) / 255.0F;
switch(dir) {
case NORTH:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
break;
case SOUTH:
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
break;
case EAST:
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 0f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 1f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
break;
case WEST:
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 1f, 0f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
default:
break;
}
}
public static void drawBottom(Sprite sprite, BufferBuilder buffer, int color) {
float r = (float)(color >> 16 & 255) / 255.0F;
float g = (float)(color >> 8 & 255) / 255.0F;
float b = (float)(color & 255) / 255.0F;
buffer.vertex(0f, 0f, 0f).texture(sprite.getMinU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 0f).texture(sprite.getMaxU(), sprite.getMaxV()).color(r, g, b, 1f).next();
buffer.vertex(1f, 0f, 1f).texture(sprite.getMaxU(), sprite.getMinV()).color(r, g, b, 1f).next();
buffer.vertex(0f, 0f, 1f).texture(sprite.getMinU(), sprite.getMinV()).color(r, g, b, 1f).next();
}
}

View File

@ -0,0 +1,47 @@
package io.github.cottonmc.slopetest.model;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
public abstract class AbstractModel implements BakedModel, FabricBakedModel {
protected static final Renderer RENDERER = RendererAccess.INSTANCE.getRenderer();
protected final Sprite modelSprite;
protected final ModelTransformation transformation;
protected AbstractModel(
Sprite sprite,
ModelTransformation transformation) {
this.modelSprite = sprite;
this.transformation = transformation;
}
@Override
public boolean useAmbientOcclusion() {
return true;
}
@Override
public boolean hasDepthInGui() {
return true;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getSprite() {
return modelSprite;
}
@Override
public ModelTransformation getTransformation() {
return transformation;
}
}

View File

@ -0,0 +1,16 @@
package io.github.cottonmc.slopetest.model;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
public interface MeshTransformer extends QuadTransform {
MeshTransformer prepare(ExtendedBlockView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier);
MeshTransformer prepare(ItemStack stack, Supplier<Random> randomSupplier);
}

View File

@ -0,0 +1,105 @@
package io.github.cottonmc.slopetest.model;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelItemPropertyOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.ExtendedBlockView;
import net.minecraft.world.World;
/**
* Simple baked model supporting the Fabric Render API features.<p>
*/
public class SimpleModel extends AbstractModel {
protected final Mesh mesh;
protected final Supplier<MeshTransformer> transformerFactory;
protected WeakReference<List<BakedQuad>[]> quadLists = null;
protected final ItemProxy itemProxy = new ItemProxy();
public SimpleModel(
Mesh mesh,
Supplier<MeshTransformer> transformerFactory,
Sprite sprite,
ModelTransformation transformation) {
super(sprite, transformation);
this.mesh = mesh;
this.transformerFactory = transformerFactory;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction face, Random rand) {
List<BakedQuad>[] lists = quadLists == null ? null : quadLists.get();
if(lists == null) {
lists = ModelHelper.toQuadLists(this.mesh);
quadLists = new WeakReference<>(lists);
}
List<BakedQuad> result = lists[face == null ? 6 : face.getId()];
return result == null ? ImmutableList.of() : result;
}
@Override
public void emitBlockQuads(ExtendedBlockView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
final MeshTransformer transform = transformerFactory == null ? null : transformerFactory.get().prepare(blockView, state, pos, randomSupplier);
if(transform != null) {
context.pushTransform(transform);
}
if(mesh != null) {
context.meshConsumer().accept(mesh);
}
if(transform != null) {
context.popTransform();
}
}
@Override
public ModelItemPropertyOverrideList getItemPropertyOverrides() {
return itemProxy;
}
protected class ItemProxy extends ModelItemPropertyOverrideList {
public ItemProxy() {
super(null, null, null, Collections.emptyList());
}
@Override
public BakedModel apply(BakedModel bakedModel_1, ItemStack itemStack_1, World world_1, LivingEntity livingEntity_1) {
return SimpleModel.this;
}
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
final MeshTransformer transform = transformerFactory == null ? null : transformerFactory.get().prepare(stack, randomSupplier);
if(transform != null) {
context.pushTransform(transform);
}
if(mesh != null) {
context.meshConsumer().accept(mesh);
}
if(transform != null) {
context.popTransform();
}
}
}

View File

@ -0,0 +1,33 @@
package io.github.cottonmc.slopetest.model;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.Function;
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.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.Identifier;
@FunctionalInterface
public interface SimpleUnbakedModel extends UnbakedModel {
BakedModel bake();
@Override
default Collection<Identifier> getModelDependencies() {
return Collections.emptyList();
}
@Override
default Collection<Identifier> getTextureDependencies(Function<Identifier, UnbakedModel> var1, Set<String> var2) {
return Collections.emptyList();
}
@Override
default BakedModel bake(ModelLoader loader, Function<Identifier, Sprite> spriteFunc, ModelBakeSettings settings) {
return bake();
}
}

View File

@ -0,0 +1,35 @@
package io.github.cottonmc.slopetest.model;
import java.util.HashMap;
import io.github.cottonmc.slopetest.SlopeTest;
import net.fabricmc.fabric.api.client.model.ModelProviderContext;
import net.fabricmc.fabric.api.client.model.ModelProviderException;
import net.fabricmc.fabric.api.client.model.ModelVariantProvider;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.block.BlockModels;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.registry.Registry;
public class SlopeModelVariantProvider implements ModelVariantProvider {
private final HashMap<ModelIdentifier, UnbakedModel> variants = new HashMap<>();
public SlopeModelVariantProvider() {
// A bit ugly to hard-code this in the constructor, but however it is done,
// best to have variants for all the mod's blocks in a single provider/map
// instance so that model loader doesn't have to query a large number of providers.
for(BlockState state : SlopeTest.SLOPE.getStateFactory().getStates()) {
variants.put(BlockModels.getModelId(state), (SimpleUnbakedModel)() -> new SlopeTestModel(state));
}
variants.put(new ModelIdentifier(Registry.ITEM.getId(SlopeTest.SLOPE.asItem()), "inventory"), (SimpleUnbakedModel)() -> new SlopeTestModel(SlopeTest.SLOPE.getDefaultState()));
}
@Override
public UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) throws ModelProviderException {
return variants.get(modelId);
}
}

View File

@ -0,0 +1,357 @@
package io.github.cottonmc.slopetest.model;
import java.util.Random;
import java.util.function.Supplier;
import org.apache.commons.lang3.ObjectUtils;
import io.github.cottonmc.slopetest.util.SpriteSet;
import net.fabricmc.fabric.api.client.render.ColorProviderRegistry;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
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.ModelHelper;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderLayer;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColorProvider;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.MissingSprite;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.ExtendedBlockView;
public class SlopeTestModel extends SimpleModel {
private static final ThreadLocal<Transformer> TRANSFORMERS = ThreadLocal.withInitial(Transformer::new);
public SlopeTestModel(BlockState blockState) {
super(baseMesh(blockState), TRANSFORMERS::get, MissingSprite.getMissingSprite(), ModelHelper.MODEL_TRANSFORM_BLOCK);
}
private static Mesh baseMesh(BlockState state) {
final MeshBuilder builder = RENDERER.meshBuilder();
final QuadEmitter quad = builder.getEmitter();
final Direction dir = state.get(Properties.HORIZONTAL_FACING);
drawSlope(quad.spriteColor(0, -1, -1, -1, -1), dir);
drawLeftSide(quad.spriteColor(0, -1, -1, -1, -1), dir);
drawRightSide(quad.spriteColor(0, -1, -1, -1, -1), dir);
drawBack(quad.spriteColor(0, -1, -1, -1, -1), dir);
drawBottom(quad.spriteColor(0, -1, -1, -1, -1));
return builder.build();
}
private static final int TAG_SLOPE = 0;
private static final int TAG_LEFT = 1;
private static final int TAG_RIGHT = 2;
private static final int TAG_BACK = 3;
private static final int TAG_BOTTOM = 4;
private static void drawSlope(QuadEmitter quad, Direction dir) {
quad.tag(TAG_SLOPE);
switch (dir) {
case NORTH:
quad.pos(0, 0f, 1f, 0f).pos(1, 0f, 0f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 1f, 0f).emit();
break;
case SOUTH:
quad.pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 0f).emit();
break;
case EAST:
quad.pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 1f, 0f).emit();
break;
case WEST:
quad.pos(0, 0f, 1f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 0f, 0f).emit();
default:
break;
}
}
private static void drawLeftSide(QuadEmitter quad, Direction dir) {
switch(dir) {
case NORTH:
quad.tag(TAG_LEFT).pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 1f).pos(2, 0f, 0f, 1f).pos(3, 0f, 1f, 0f).emit();
break;
case SOUTH:
quad.tag(TAG_LEFT).pos(0, 1f, 0f, 0f).pos(1, 1f, 1f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 0f, 1f).emit();
break;
case EAST:
quad.tag(TAG_LEFT).pos(0, 1f, 0f, 0f).pos(1, 0f, 0f, 0f).pos(2, 0f, 0f, 0f).pos(3, 1f, 1f, 0f).emit();
break;
case WEST:
quad.tag(TAG_LEFT).pos(0, 0f, 0f, 1f).pos(1, 1f, 0f, 1f).pos(2, 1f, 0f, 1f).pos(3, 0f, 1f, 1f).emit();
default:
break;
}
}
private static void drawRightSide(QuadEmitter quad, Direction dir) {
switch(dir) {
case NORTH:
quad.tag(TAG_RIGHT).pos(0, 1f, 0f, 0f).pos(1, 1f, 1f, 0f).pos(2, 1f, 0f, 1f).pos(3, 1f, 0f, 1f).emit();
break;
case SOUTH:
quad.tag(TAG_RIGHT).pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 0f).pos(2, 0f, 0f, 1f).pos(3, 0f, 1f, 1f).emit();
break;
case EAST:
quad.tag(TAG_RIGHT).pos(0, 0f, 0f, 1f).pos(1, 0f, 0f, 1f).pos(2, 1f, 0f, 1f).pos(3, 1f, 1f, 1f).emit();
break;
case WEST:
quad.tag(TAG_RIGHT).pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 0f).pos(2, 1f, 0f, 0f).pos(3, 1f, 0f, 0f).emit();
default:
break;
}
}
private static void drawBack(QuadEmitter quad, Direction dir) {
switch(dir) {
case NORTH:
quad.tag(TAG_BACK).pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 0f).pos(2, 1f, 1f, 0f).pos(3, 1f, 0f, 0f).emit();
break;
case SOUTH:
quad.tag(TAG_BACK).pos(0, 0f, 0f, 1f).pos(1, 1f, 0f, 1f).pos(2, 1f, 1f, 1f).pos(3, 0f, 1f, 1f).emit();
break;
case EAST:
quad.tag(TAG_BACK).pos(0, 1f, 0f, 0f).pos(1, 1f, 1f, 0f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 1f).emit();
break;
case WEST:
quad.tag(TAG_BACK).pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 1f).pos(2, 0f, 1f, 1f).pos(3, 0f, 1f, 0f).emit();
default:
break;
}
}
private static void drawBottom(QuadEmitter quad) {
quad.tag(TAG_BOTTOM).pos(0, 0f, 0f, 0f).pos(1, 1f, 0f, 0f).pos(2, 1f, 0f, 1f).pos(3, 0f, 0f, 1f).emit();
}
private static class Transformer implements MeshTransformer {
private final MinecraftClient minecraft = MinecraftClient.getInstance();
private final SpriteSet sprites = new SpriteSet();
private final MaterialFinder finder = RENDERER.materialFinder();
private int color;
private Direction dir;
private RenderMaterial material;
@Override
public MeshTransformer prepare(ExtendedBlockView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier) {
dir = state.get(Properties.HORIZONTAL_FACING);
color = 0xffffff;
final BlockState template = ObjectUtils.defaultIfNull((BlockState) ((RenderAttachedBlockView)blockView).getBlockEntityRenderAttachment(pos), Blocks.AIR.getDefaultState());
final Block block = template.getBlock();
if(block == Blocks.AIR) {
sprites.clear();
material = finder.clear().blendMode(0, BlockRenderLayer.CUTOUT).find();
} else {
material = finder.clear().disableDiffuse(0, false).disableAo(0, false).blendMode(0, block.getRenderLayer()).find();
BakedModel model = minecraft.getBlockRenderManager().getModel(template);
sprites.prepare(model, randomSupplier.get());
BlockColorProvider blockColor = ColorProviderRegistry.BLOCK.get(block);
if (blockColor != null) {
color = 0xff000000 | blockColor.getColor(template, blockView, pos, 1);
}
}
return this;
}
@Override
public MeshTransformer prepare(ItemStack stack, Supplier<Random> randomSupplier) {
dir = Direction.NORTH;
color = 0xffffff;
sprites.clear();
material = finder.clear().find();
return this;
}
@Override
public boolean transform(MutableQuadView quad) {
quad.material(material);
final SpriteSet sprites = this.sprites;
switch(quad.tag()) {
case TAG_SLOPE:
if(sprites.hasColor(Direction.UP)) {
quad.spriteColor(0, color, color, color, color);
}
paintSlope(quad, dir, sprites.getSprite(Direction.UP));
break;
case TAG_LEFT:
final Direction leftDir = this.dir.rotateYCounterclockwise();
if(sprites.hasColor(leftDir)) {
quad.spriteColor(0, color, color, color, color);
}
paintLeftSide(quad, dir, sprites.getSprite(leftDir));
break;
case TAG_RIGHT: {
final Direction rightDir = this.dir.rotateYClockwise();
if(sprites.hasColor(rightDir)) {
quad.spriteColor(0, color, color, color, color);
}
paintRightSide(quad, dir, sprites.getSprite(rightDir));
break;
}
case TAG_BACK: {
if(sprites.hasColor(dir)) {
quad.spriteColor(0, color, color, color, color);
}
paintBack(quad, dir, sprites.getSprite(dir));
break;
}
case TAG_BOTTOM: {
if(sprites.hasColor(Direction.DOWN)) {
quad.spriteColor(0, color, color, color, color);
}
paintBottom(quad, sprites.getSprite(Direction.DOWN));
break;
}
default:
}
return true;
}
private static void paintSlope(MutableQuadView quad, Direction dir, Sprite sprite) {
switch (dir) {
case NORTH:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMinV())
.sprite(1, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(3, 0, sprite.getMaxU(), sprite.getMinV());
break;
case SOUTH:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMaxV());
break;
case EAST:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
break;
case WEST:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(1, 0, sprite.getMinU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(3, 0, sprite.getMaxU(), sprite.getMaxV());
default:
break;
}
}
private static void paintLeftSide(MutableQuadView quad, Direction dir, Sprite sprite) {
switch(dir) {
case NORTH:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
break;
case SOUTH:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(1, 0, sprite.getMinU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(3, 0, sprite.getMaxU(), sprite.getMaxV());
break;
case EAST:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
break;
case WEST:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
default:
break;
}
}
private static void paintRightSide(MutableQuadView quad, Direction dir, Sprite sprite) {
switch(dir) {
case NORTH:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMaxV());
break;
case SOUTH:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMinV())
.sprite(1, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(3, 0, sprite.getMaxU(), sprite.getMinV());
break;
case EAST:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMinV())
.sprite(1, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(3, 0, sprite.getMaxU(), sprite.getMinV());
break;
case WEST:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMaxV());
default:
break;
}
}
private static void paintBack(MutableQuadView quad, Direction dir, Sprite sprite) {
switch(dir) {
case NORTH:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMaxV());
break;
case SOUTH:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
break;
case EAST:
quad.sprite(0, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(2, 0, sprite.getMinU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMaxV());
break;
case WEST:
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
default:
break;
}
}
private static void paintBottom(MutableQuadView quad, Sprite sprite) {
quad.sprite(0, 0, sprite.getMinU(), sprite.getMaxV())
.sprite(1, 0, sprite.getMaxU(), sprite.getMaxV())
.sprite(2, 0, sprite.getMaxU(), sprite.getMinV())
.sprite(3, 0, sprite.getMinU(), sprite.getMinV());
}
}
}

View File

@ -1,5 +1,12 @@
package io.github.cottonmc.slopetest.util; package io.github.cottonmc.slopetest.util;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang3.ObjectUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad; import net.minecraft.client.render.model.BakedQuad;
@ -7,42 +14,39 @@ import net.minecraft.client.texture.Sprite;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class SpriteSet { public class SpriteSet {
private Sprite global; private Object2ObjectOpenHashMap<Direction, BakedQuad> quads = new Object2ObjectOpenHashMap<>();
private Map<Direction, BakedQuad> quads; private boolean isDefault = true;
private boolean singleSprite = false;
private boolean globalHasColor = false;
public static final Sprite FALLBACK = MinecraftClient.getInstance().getSpriteAtlas().getSprite(new Identifier("minecraft:block/scaffolding_top")); public static final Sprite FALLBACK = MinecraftClient.getInstance().getSpriteAtlas().getSprite(new Identifier("minecraft:block/scaffolding_top"));
public SpriteSet(Sprite allSprite, boolean hasColor) { public SpriteSet() {
this.global = allSprite; clear();
singleSprite = true;
globalHasColor = hasColor;
} }
public SpriteSet(BakedModel model) { /** Allow re-use of instances to avoid allocation in render loop */
Random rand = new Random(); public void clear() {
quads = new HashMap<>(); isDefault = true;
for (Direction dir : Direction.values()) {
List<BakedQuad> quads = model.getQuads(null, dir, rand);
if (!quads.isEmpty()) this.quads.put(dir, quads.get(0));
}
} }
/** Allow re-use of instances to avoid allocation in render loop */
//TODO: pass in block state?
public void prepare(BakedModel model, Random rand) {
this.quads.clear();
isDefault = false;
// avoid Direction.values() in hot loop - for thread safety may generate new array instances
//for (Direction dir : Direction.values()) {
for(int i = 0; i < 6; i++) {
final Direction dir = ModelHelper.faceFromIndex(i);
List<BakedQuad> quads = model.getQuads(null, dir, rand);
if (!quads.isEmpty()) this.quads.put(dir, quads.get(0));
}
}
public Sprite getSprite(Direction dir) { public Sprite getSprite(Direction dir) {
if (singleSprite) return global; return isDefault ? FALLBACK : ObjectUtils.defaultIfNull(quads.get(dir).getSprite(), FALLBACK);
if (quads.get(dir) == null) return FALLBACK;
else return quads.get(dir).getSprite();
} }
public boolean hasColor(Direction dir) { public boolean hasColor(Direction dir) {
if (singleSprite) return globalHasColor; return isDefault ? false : ObjectUtils.defaultIfNull(quads.get(dir).hasColor(), false);
if (quads.get(dir) == null) return false;
else return quads.get(dir).hasColor();
} }
} }