Merge pull request 'v1.5 new shapes + self culling + caching + coding tools/cleanup' (#10) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m42s

Reviewed-on: #10
This commit is contained in:
Adrien1106 2024-03-14 22:30:29 +01:00
commit 186cca3e27
89 changed files with 2746 additions and 1269 deletions

View File

@ -9,7 +9,7 @@ loader_version=0.15.6
# Mod Properties # Mod Properties
modrinth_id = jCpoCBpn modrinth_id = jCpoCBpn
mod_version = 1.4 mod_version = 1.5
maven_group = fr.adrien1106 maven_group = fr.adrien1106
archives_base_name = ReFramed archives_base_name = ReFramed
mod_id = reframed mod_id = reframed

View File

@ -1,7 +1,6 @@
package fr.adrien1106.reframed; package fr.adrien1106.reframed;
import fr.adrien1106.reframed.block.*; import fr.adrien1106.reframed.block.*;
import fr.adrien1106.reframed.util.BlockHelper;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
@ -25,14 +24,21 @@ import java.util.ArrayList;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
/** /**
* TODO self culling, fix other models, better connected textures, preload items, better cache * TODO make block pairable by right click -> for v1.6
* TODO add Hammer from framed ( removes theme ) -> for v1.5.5
* TODO add screwdriver ( iterate over theme states ) ?
* TODO add blueprint for survival friendly copy paste of a theme. -> for v1.5.5
* TODO add minecraft models like wall fence etc -> for v1.6
* TODO better connected textures -> maybe v1.6 ?
*/ */
public class ReFramed implements ModInitializer { public class ReFramed implements ModInitializer {
public static final String MODID = "reframed"; public static final String MODID = "reframed";
public static final ArrayList<Block> BLOCKS = new ArrayList<>(); public static final ArrayList<Block> BLOCKS = new ArrayList<>();
public static Block CUBE, STAIRS, DOUBLE_STAIRS, SLAB, DOUBLE_SLAB, STEP, DOUBLE_STEP; public static Block CUBE, SMALL_CUBE, SMALL_CUBES_STEP, STAIR, HALF_STAIR, STAIRS_CUBE, HALF_STAIRS_SLAB, HALF_STAIRS_STAIR, SLAB, SLABS_CUBE, STEP, STEPS_SLAB, LAYER;
public static ItemGroup ITEM_GROUP; public static ItemGroup ITEM_GROUP;
public static BlockEntityType<ReFramedEntity> REFRAMED_BLOCK_ENTITY; public static BlockEntityType<ReFramedEntity> REFRAMED_BLOCK_ENTITY;
@ -42,18 +48,19 @@ public class ReFramed implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
//registerReFramed mutates FRAMES as a side effect, which is a List, so order is preserved CUBE = registerReFramed("cube" , new ReFramedBlock(cp(Blocks.OAK_PLANKS)));
//the ordering is used in the creative tab, so they're roughly sorted by encounter order of the SMALL_CUBE = registerReFramed("small_cube" , new ReFramedSmallCubeBlock(cp(Blocks.OAK_PLANKS)));
//corresponding vanilla block in the "search" creative tab... with the non-vanilla "post" and SMALL_CUBES_STEP = registerReFramed("small_cubes_step" , new ReFramedSmallCubesStepBlock(cp(Blocks.OAK_PLANKS)));
//"vertical slab" inserted where they fit ...and i moved the lever way up next to the pressureplate STAIR = registerReFramed("stair" , new ReFramedStairBlock(cp(Blocks.OAK_STAIRS)));
//and button, because they're redstoney... hopefully this ordering makes sense lol STAIRS_CUBE = registerReFramed("stairs_cube" , new ReFramedStairsCubeBlock(cp(Blocks.OAK_STAIRS)));
CUBE = registerReFramed("cube" , new ReFramedBlock(cp(Blocks.OAK_PLANKS))); HALF_STAIR = registerReFramed("half_stair" , new ReFramedHalfStairBlock(cp(Blocks.OAK_STAIRS)));
STAIRS = registerReFramed("stairs" , new ReFramedStairsBlock(cp(Blocks.OAK_STAIRS))); HALF_STAIRS_SLAB = registerReFramed("half_stairs_slab" , new ReFramedHalfStairsSlabBlock(cp(Blocks.OAK_STAIRS)));
DOUBLE_STAIRS = registerReFramed("double_stairs" , new ReFramedDoubleStairsBlock(cp(Blocks.OAK_STAIRS))); HALF_STAIRS_STAIR = registerReFramed("half_stairs_stair" , new ReFramedHalfStairsStairBlock(cp(Blocks.OAK_STAIRS)));
SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB))); LAYER = registerReFramed("layer" , new ReFramedLayerBlock(cp(Blocks.OAK_SLAB)));
DOUBLE_SLAB = registerReFramed("double_slab" , new ReFramedDoubleSlabBlock(cp(Blocks.OAK_SLAB))); SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB)));
STEP = registerReFramed("step" , new ReFramedStepBlock(cp(Blocks.OAK_SLAB))); SLABS_CUBE = registerReFramed("slabs_cube" , new ReFramedSlabsCubeBlock(cp(Blocks.OAK_SLAB)));
DOUBLE_STEP = registerReFramed("double_step" , new ReFramedDoubleStepBlock(cp(Blocks.OAK_SLAB))); STEP = registerReFramed("step" , new ReFramedStepBlock(cp(Blocks.OAK_SLAB)));
STEPS_SLAB = registerReFramed("steps_slab" , new ReFramedStepsSlabBlock(cp(Blocks.OAK_SLAB)));
REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"), REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"),
FabricBlockEntityTypeBuilder.create( FabricBlockEntityTypeBuilder.create(
@ -70,7 +77,7 @@ public class ReFramed implements ModInitializer {
.filter(block -> block instanceof ReFramedDoubleBlock) .filter(block -> block instanceof ReFramedDoubleBlock)
.toArray(Block[]::new)).build(null) .toArray(Block[]::new)).build(null)
); );
ITEM_GROUP = Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder() ITEM_GROUP = Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder()
.displayName(Text.translatable("itemGroup.reframed.tab")) .displayName(Text.translatable("itemGroup.reframed.tab"))
.icon(() -> new ItemStack(SLAB)) .icon(() -> new ItemStack(SLAB))
@ -80,7 +87,7 @@ public class ReFramed implements ModInitializer {
private static AbstractBlock.Settings cp(Block base) { private static AbstractBlock.Settings cp(Block base) {
return AbstractBlock.Settings.copy(base) return AbstractBlock.Settings.copy(base)
.luminance(BlockHelper::luminance) .luminance(state -> state.contains(LIGHT) && state.get(LIGHT) ? 15 : 0)
.nonOpaque() .nonOpaque()
.sounds(BlockSoundGroup.WOOD) .sounds(BlockSoundGroup.WOOD)
.hardness(0.2f) .hardness(0.2f)

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.RecipeSetter; import fr.adrien1106.reframed.generator.RecipeSetter;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
@ -33,7 +33,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import static fr.adrien1106.reframed.util.BlockProperties.LIGHT; import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
public class ReFramedBlock extends Block implements BlockEntityProvider, RecipeSetter { public class ReFramedBlock extends Block implements BlockEntityProvider, RecipeSetter {
@ -41,6 +41,23 @@ public class ReFramedBlock extends Block implements BlockEntityProvider, RecipeS
super(settings); super(settings);
setDefaultState(getDefaultState().with(LIGHT, false)); setDefaultState(getDefaultState().with(LIGHT, false));
} }
/**
* Generates a record for the key so that it replaces the blockstate
* which may have states that returns same models
* @param state - the state_key to generate the key from
* @return a cache key with only relevant properties
*/
public Object getModelCacheKey(BlockState state) {
return "";
}
/**
* @return the amount of models the block can have prevents allocating too much space for a model
*/
public int getModelStateCount() {
return 1;
}
//For addon devs: override this so your blocks don't end up trying to place my block entity, my BlockEntityType only handles blocks internal to the mod //For addon devs: override this so your blocks don't end up trying to place my block entity, my BlockEntityType only handles blocks internal to the mod
//Just make your own BlockEntityType, it's fine, you can even use the same ReFramedEntity class //Just make your own BlockEntityType, it's fine, you can even use the same ReFramedEntity class
@ -98,7 +115,7 @@ public class ReFramedBlock extends Block implements BlockEntityProvider, RecipeS
&& themes.stream().noneMatch(theme -> theme.getLuminance() != 0)) && themes.stream().noneMatch(theme -> theme.getLuminance() != 0))
drops.add(new ItemStack(Items.GLOWSTONE_DUST)); drops.add(new ItemStack(Items.GLOWSTONE_DUST));
if(!frame_entity.isSolid() if(!frame_entity.isSolid()
&& themes.stream().anyMatch(theme -> theme.isSolid())) && themes.stream().anyMatch(AbstractBlockState::isSolid))
drops.add(new ItemStack(Items.POPPED_CHORUS_FRUIT)); drops.add(new ItemStack(Items.POPPED_CHORUS_FRUIT));
ItemScatterer.spawn(world, pos, drops); ItemScatterer.spawn(world, pos, drops);

View File

@ -1,8 +1,8 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;

View File

@ -44,7 +44,7 @@ public class ReFramedDoubleEntity extends ReFramedEntity {
public void readNbt(NbtCompound nbt) { public void readNbt(NbtCompound nbt) {
super.readNbt(nbt); super.readNbt(nbt);
BlockState rendered_state = second_state;// keep previous state to check if rerender is needed BlockState rendered_state = second_state;// keep previous state_key to check if rerender is needed
second_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 2)); second_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 2));
// Force a chunk remesh on the client if the displayed blockstate has changed // Force a chunk remesh on the client if the displayed blockstate has changed

View File

@ -1,210 +0,0 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.BlockHelper;
import fr.adrien1106.reframed.util.property.Corner;
import fr.adrien1106.reframed.util.property.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static fr.adrien1106.reframed.block.ReFramedStairsBlock.*;
import static fr.adrien1106.reframed.util.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.BlockProperties.STAIR_SHAPE;
import static fr.adrien1106.reframed.util.property.StairShape.STRAIGHT;
public class ReFramedDoubleStairsBlock extends ReFramedDoubleBlock implements BlockStateProvider {
private static final List<VoxelShape> COMPLEMENT_LIST = new ArrayList<>(52);
public ReFramedDoubleStairsBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, Corner.NORTH_DOWN).with(STAIR_SHAPE, STRAIGHT));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(this, state.get(CORNER), world, pos));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
Corner face = BlockHelper.getPlacementCorner(ctx);
StairShape shape = BlockHelper.getStairsShape(this, face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(CORNER, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
@Override
public VoxelShape getShape(BlockState state, int i) {
return i == 2 ? getComplementOutline(state) : getOutline(state);
}
private VoxelShape getComplementOutline(BlockState state) {
StairShape shape = state.get(STAIR_SHAPE);
Corner direction = state.get(CORNER);
return switch (shape) {
case STRAIGHT ->
switch (direction) {
case DOWN_SOUTH -> COMPLEMENT_LIST.get(0);
case NORTH_DOWN -> COMPLEMENT_LIST.get(1);
case UP_NORTH -> COMPLEMENT_LIST.get(2);
case SOUTH_UP -> COMPLEMENT_LIST.get(3);
case DOWN_EAST -> COMPLEMENT_LIST.get(4);
case WEST_DOWN -> COMPLEMENT_LIST.get(5);
case UP_WEST -> COMPLEMENT_LIST.get(6);
case EAST_UP -> COMPLEMENT_LIST.get(7);
case NORTH_EAST -> COMPLEMENT_LIST.get(8);
case EAST_SOUTH -> COMPLEMENT_LIST.get(9);
case SOUTH_WEST -> COMPLEMENT_LIST.get(10);
case WEST_NORTH -> COMPLEMENT_LIST.get(11);
};
case INNER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> COMPLEMENT_LIST.get(44);
case DOWN_EAST -> COMPLEMENT_LIST.get(45);
case DOWN_SOUTH -> COMPLEMENT_LIST.get(47);
case UP_WEST, UP_NORTH, WEST_NORTH -> COMPLEMENT_LIST.get(48);
case EAST_UP, NORTH_EAST -> COMPLEMENT_LIST.get(49);
case EAST_SOUTH -> COMPLEMENT_LIST.get(50);
case SOUTH_UP, SOUTH_WEST -> COMPLEMENT_LIST.get(51);
};
case INNER_RIGHT ->
switch (direction) {
case WEST_NORTH -> COMPLEMENT_LIST.get(44);
case NORTH_DOWN, NORTH_EAST -> COMPLEMENT_LIST.get(45);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> COMPLEMENT_LIST.get(46);
case WEST_DOWN, SOUTH_WEST -> COMPLEMENT_LIST.get(47);
case UP_NORTH -> COMPLEMENT_LIST.get(49);
case EAST_UP, SOUTH_UP -> COMPLEMENT_LIST.get(50);
case UP_WEST -> COMPLEMENT_LIST.get(51);
};
case OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> COMPLEMENT_LIST.get(43);
case WEST_DOWN, NORTH_DOWN -> COMPLEMENT_LIST.get(42);
case DOWN_SOUTH -> COMPLEMENT_LIST.get(41);
case EAST_UP, NORTH_EAST -> COMPLEMENT_LIST.get(39);
case UP_WEST, UP_NORTH, WEST_NORTH -> COMPLEMENT_LIST.get(38);
case SOUTH_UP, SOUTH_WEST -> COMPLEMENT_LIST.get(37);
case EAST_SOUTH -> COMPLEMENT_LIST.get(36);
};
case OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN, NORTH_EAST -> COMPLEMENT_LIST.get(43);
case WEST_NORTH -> COMPLEMENT_LIST.get(42);
case WEST_DOWN, SOUTH_WEST -> COMPLEMENT_LIST.get(41);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> COMPLEMENT_LIST.get(40);
case UP_NORTH -> COMPLEMENT_LIST.get(39);
case UP_WEST -> COMPLEMENT_LIST.get(37);
case EAST_UP, SOUTH_UP -> COMPLEMENT_LIST.get(36);
};
case FIRST_OUTER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> COMPLEMENT_LIST.get(14);
case SOUTH_UP -> COMPLEMENT_LIST.get(17);
case EAST_UP -> COMPLEMENT_LIST.get(19);
case EAST_SOUTH -> COMPLEMENT_LIST.get(20);
case DOWN_SOUTH -> COMPLEMENT_LIST.get(22);
case UP_NORTH, WEST_NORTH -> COMPLEMENT_LIST.get(25);
case SOUTH_WEST -> COMPLEMENT_LIST.get(28);
case UP_WEST -> COMPLEMENT_LIST.get(31);
case DOWN_EAST -> COMPLEMENT_LIST.get(34);
case NORTH_EAST -> COMPLEMENT_LIST.get(35);
};
case FIRST_OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN -> COMPLEMENT_LIST.get(15);
case SOUTH_UP, EAST_UP -> COMPLEMENT_LIST.get(16);
case WEST_DOWN -> COMPLEMENT_LIST.get(13);
case DOWN_SOUTH, EAST_SOUTH -> COMPLEMENT_LIST.get(23);
case UP_NORTH -> COMPLEMENT_LIST.get(24);
case WEST_NORTH -> COMPLEMENT_LIST.get(26);
case UP_WEST -> COMPLEMENT_LIST.get(28);
case SOUTH_WEST -> COMPLEMENT_LIST.get(29);
case DOWN_EAST -> COMPLEMENT_LIST.get(33);
case NORTH_EAST -> COMPLEMENT_LIST.get(34);
};
case SECOND_OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> COMPLEMENT_LIST.get(15);
case DOWN_SOUTH -> COMPLEMENT_LIST.get(13);
case UP_WEST, UP_NORTH -> COMPLEMENT_LIST.get(18);
case SOUTH_UP, SOUTH_WEST -> COMPLEMENT_LIST.get(21);
case NORTH_EAST -> COMPLEMENT_LIST.get(24);
case NORTH_DOWN -> COMPLEMENT_LIST.get(26);
case WEST_DOWN -> COMPLEMENT_LIST.get(30);
case WEST_NORTH -> COMPLEMENT_LIST.get(31);
case EAST_SOUTH -> COMPLEMENT_LIST.get(32);
case EAST_UP -> COMPLEMENT_LIST.get(35);
};
case SECOND_OUTER_RIGHT ->
switch (direction) {
case DOWN_SOUTH, DOWN_EAST -> COMPLEMENT_LIST.get(12);
case UP_WEST -> COMPLEMENT_LIST.get(17);
case UP_NORTH -> COMPLEMENT_LIST.get(19);
case SOUTH_UP -> COMPLEMENT_LIST.get(20);
case SOUTH_WEST -> COMPLEMENT_LIST.get(22);
case NORTH_EAST, NORTH_DOWN -> COMPLEMENT_LIST.get(27);
case WEST_DOWN -> COMPLEMENT_LIST.get(29);
case WEST_NORTH -> COMPLEMENT_LIST.get(30);
case EAST_UP -> COMPLEMENT_LIST.get(32);
case EAST_SOUTH -> COMPLEMENT_LIST.get(33);
};
};
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, true);
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.STAIRS)
.input(ReFramed.STEP)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
VOXEL_LIST.forEach(shape -> COMPLEMENT_LIST.add(VoxelShapes.combineAndSimplify(VoxelShapes.fullCube(), shape, BooleanBiFunction.ONLY_FIRST)));
}
}

View File

@ -1,8 +1,8 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.BlockProperties; import fr.adrien1106.reframed.util.blocks.BlockProperties;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
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;
@ -27,7 +27,7 @@ import java.util.Objects;
//Keeping the weight of this block entity down, both in terms of memory consumption and NBT sync traffic, //Keeping the weight of this block entity down, both in terms of memory consumption and NBT sync traffic,
//is pretty important since players might place a lot of them. There were tons and tons of these at Blanketcon. //is pretty important since players might place a lot of them. There were tons and tons of these at Blanketcon.
//To that end, most of the state has been crammed into a bitfield. //To that end, most of the state_key has been crammed into a bitfield.
public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity { public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity {
protected BlockState first_state = Blocks.AIR.getDefaultState(); protected BlockState first_state = Blocks.AIR.getDefaultState();
protected byte bit_field = SOLIDITY_MASK; protected byte bit_field = SOLIDITY_MASK;
@ -47,7 +47,7 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
public void readNbt(NbtCompound nbt) { public void readNbt(NbtCompound nbt) {
super.readNbt(nbt); super.readNbt(nbt);
BlockState rendered_state = first_state; // keep previous state to check if rerender is needed BlockState rendered_state = first_state; // keep previous state_key to check if rerender is needed
first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 1)); first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 1));
if (nbt.contains(BITFIELD_KEY)) bit_field = nbt.getByte(BITFIELD_KEY); if (nbt.contains(BITFIELD_KEY)) bit_field = nbt.getByte(BITFIELD_KEY);

View File

@ -0,0 +1,194 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER_FACE;
import static fr.adrien1106.reframed.util.blocks.Corner.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedHalfStairBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] HALF_STAIR_VOXELS;
private record ModelCacheKey(Corner corner, int face) {}
public ReFramedHalfStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN).with(CORNER_FACE, 0));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(CORNER), state.get(CORNER_FACE));
}
@Override
public int getModelStateCount() {
return 24;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER,CORNER_FACE));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
Corner corner = BlockHelper.getPlacementCorner(ctx);
return super.getPlacementState(ctx)
.with(CORNER, corner)
.with(CORNER_FACE, corner.getDirectionIndex(ctx.getSide().getOpposite()));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return HALF_STAIR_VOXELS[state.get(CORNER_FACE) + state.get(CORNER).getID() * 3];
}
@Override
public BlockStateSupplier getMultipart() {
return getHalfStairMultipart(
this,
ReFramed.id("half_stair_down_special"),
ReFramed.id("half_stair_side_special")
);
}
public static BlockStateSupplier getHalfStairMultipart(Block block, Identifier model_down, Identifier model_side) {
return MultipartBlockStateSupplier.create(block)
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R0))
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R270))
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R90))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R180))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R270))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R270))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R90))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R180))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R270))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R270))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R0))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 4)
.pattern("I ")
.pattern("II ")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape HALF_STAIR = VoxelShapes.combineAndSimplify(
createCuboidShape(8, 0, 0, 16, 16, 8),
createCuboidShape(0, 0, 0, 8, 8, 8),
BooleanBiFunction.OR
);
HALF_STAIR_VOXELS = VoxelListBuilder.create(HALF_STAIR, 24)
.add(0 , VoxelHelper::rotateY, VoxelHelper::mirrorZ)
.add(0 , VoxelHelper::rotateCX, VoxelHelper::mirrorZ)
.add(0 , VoxelHelper::rotateY)
.add(1 , VoxelHelper::rotateY)
.add(2 , VoxelHelper::rotateY)
.add(3 , VoxelHelper::rotateY)
.add(4 , VoxelHelper::rotateY)
.add(5 , VoxelHelper::rotateY)
.add(6 , VoxelHelper::rotateY)
.add(7 , VoxelHelper::rotateY)
.add(8 , VoxelHelper::rotateY)
.add(0 , VoxelHelper::mirrorY)
.add(1 , VoxelHelper::mirrorY)
.add(2 , VoxelHelper::mirrorY)
.add(12, VoxelHelper::rotateY)
.add(13, VoxelHelper::rotateY)
.add(14, VoxelHelper::rotateY)
.add(15, VoxelHelper::rotateY)
.add(16, VoxelHelper::rotateY)
.add(17, VoxelHelper::rotateY)
.add(18, VoxelHelper::rotateY)
.add(19, VoxelHelper::rotateY)
.add(20, VoxelHelper::rotateY)
.build();
}
}

View File

@ -0,0 +1,103 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.HALF_STAIR_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.getHalfStairMultipart;
import static fr.adrien1106.reframed.block.ReFramedSlabBlock.getSlabShape;
import static fr.adrien1106.reframed.block.ReFramedSmallCubeBlock.SMALL_CUBE_VOXELS;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER_FACE;
import static fr.adrien1106.reframed.util.blocks.Corner.NORTH_EAST_DOWN;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedHalfStairsSlabBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
private record ModelCacheKey(Corner corner, int face) {}
public ReFramedHalfStairsSlabBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN).with(CORNER_FACE, 0));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(CORNER), state.get(CORNER_FACE));
}
@Override
public int getModelStateCount() {
return 24;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER,CORNER_FACE));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
Corner corner = BlockHelper.getPlacementCorner(ctx);
return super.getPlacementState(ctx)
.with(CORNER, corner)
.with(CORNER_FACE, corner.getDirectionIndex(ctx.getSide().getOpposite()));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getSlabShape(state.get(CORNER).getDirection(state.get(CORNER_FACE)));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getSlabShape(state.get(CORNER).getDirection(state.get(CORNER_FACE)));
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Corner corner = state.get(CORNER);
int face = state.get(CORNER_FACE);
return i == 2
? SMALL_CUBE_VOXELS[corner.getOpposite(face).getID()]
: HALF_STAIR_VOXELS[face + corner.getID() * 3];
}
@Override
public BlockStateSupplier getMultipart() {
return getHalfStairMultipart(
this,
ReFramed.id("half_stairs_slab_down_special"),
ReFramed.id("half_stairs_slab_side_special")
);
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.HALF_STAIR)
.input(ReFramed.SMALL_CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@ -0,0 +1,138 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.HALF_STAIR_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedStairBlock.getStairShape;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedHalfStairsStairBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
public ReFramedHalfStairsStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getStairShape(state.get(EDGE), StairShape.STRAIGHT);
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStairShape(state.get(EDGE), StairShape.STRAIGHT);
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
Direction side = i == 1
? edge.getRightDirection()
: edge.getLeftDirection();
Corner corner = Corner.getByDirections(
edge.getFirstDirection(),
edge.getSecondDirection(),
side
);
return HALF_STAIR_VOXELS[corner.getID() * 3 + corner.getDirectionIndex(side)];
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
}
@Override
public BlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("half_stairs_stair_down_special");
Identifier side_model_id = ReFramed.id("half_stairs_stair_side_special");
Identifier reverse_model_id = ReFramed.id("half_stairs_stair_reverse_special");
return MultipartBlockStateSupplier.create(this)
/* X AXIS */
.with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(side_model_id, true, R90, R180))
.with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(side_model_id, true, R0, R180))
.with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(side_model_id, true, R270, R180))
.with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(side_model_id, true, R180, R180))
/* Y AXIS */
.with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(model_id, true, R0, R180))
.with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(model_id, true, R0, R270))
/* Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(side_model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(reverse_model_id, true, R180, R270))
.with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(side_model_id, true, R180, R90))
.with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(reverse_model_id, true, R0, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.HALF_STAIR, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@ -0,0 +1,117 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.FACING;
import static net.minecraft.state.property.Properties.LAYERS;
public class ReFramedLayerBlock extends ReFramedSlabBlock {
public static final VoxelShape[] LAYER_VOXELS;
private record ModelCacheKey(Direction face, int layer) {}
public ReFramedLayerBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(LAYERS, 1));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(FACING), state.get(LAYERS));
}
@Override
public int getModelStateCount() {
return 48;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(LAYERS));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return LAYER_VOXELS[state.get(FACING).getId() * 8 + state.get(LAYERS) - 1];
}
@Override
public boolean canReplace(BlockState state, ItemPlacementContext ctx) {
return !(!state.isOf(this) || ctx.getPlayer().isSneaking() || state.get(LAYERS) == 8);
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
BlockState previous = ctx.getWorld().getBlockState(ctx.getBlockPos());
if (!previous.isOf(this)) return super.getPlacementState(ctx);
return previous.with(LAYERS, previous.get(LAYERS) + 1);
}
@Override
public MultipartBlockStateSupplier getMultipart() {
String model_pattern = "layer_x_special";
MultipartBlockStateSupplier supplier = MultipartBlockStateSupplier.create(this);
for (int i = 1; i <= 8; i++) {
Identifier model = ReFramed.id(model_pattern.replace("x", i + ""));
supplier
.with(GBlockstate.when(FACING, Direction.DOWN, LAYERS, i),
GBlockstate.variant(model, true, R0, R0))
.with(GBlockstate.when(FACING, Direction.SOUTH, LAYERS, i),
GBlockstate.variant(model, true, R90, R0))
.with(GBlockstate.when(FACING, Direction.UP, LAYERS, i),
GBlockstate.variant(model, true, R180, R0))
.with(GBlockstate.when(FACING, Direction.NORTH, LAYERS, i),
GBlockstate.variant(model, true, R270, R0))
.with(GBlockstate.when(FACING, Direction.WEST, LAYERS, i),
GBlockstate.variant(model, true, R90, R90))
.with(GBlockstate.when(FACING, Direction.EAST, LAYERS, i),
GBlockstate.variant(model, true, R90, R270));
}
return supplier;
}
static {
VoxelListBuilder builder = VoxelListBuilder.create(createCuboidShape(0, 0, 0, 16, 2, 16), 48)
.add(createCuboidShape(0, 0, 0, 16, 4, 16))
.add(createCuboidShape(0, 0, 0, 16, 6, 16))
.add(createCuboidShape(0, 0, 0, 16, 8, 16))
.add(createCuboidShape(0, 0, 0, 16, 10, 16))
.add(createCuboidShape(0, 0, 0, 16, 12, 16))
.add(createCuboidShape(0, 0, 0, 16, 14, 16))
.add(createCuboidShape(0, 0, 0, 16, 16, 16));
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::mirrorY);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateX);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateCX);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateCZ);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateZ);
}
LAYER_VOXELS = builder.build();
}
}

View File

@ -38,6 +38,16 @@ public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements Blo
super(settings); super(settings);
setDefaultState(getDefaultState().with(FACING, Direction.DOWN)); setDefaultState(getDefaultState().with(FACING, Direction.DOWN));
} }
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(FACING);
}
@Override
public int getModelStateCount() {
return 6;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {

View File

@ -13,7 +13,6 @@ import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory; import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
@ -24,26 +23,37 @@ import static net.minecraft.data.client.VariantSettings.Rotation.R0;
import static net.minecraft.data.client.VariantSettings.Rotation.R90; import static net.minecraft.data.client.VariantSettings.Rotation.R90;
import static net.minecraft.state.property.Properties.AXIS; import static net.minecraft.state.property.Properties.AXIS;
public class ReFramedDoubleSlabBlock extends ReFramedDoubleBlock implements BlockStateProvider { public class ReFramedSlabsCubeBlock extends ReFramedDoubleBlock implements BlockStateProvider {
public ReFramedDoubleSlabBlock(Settings settings) {
public ReFramedSlabsCubeBlock(Settings settings) {
super(settings); super(settings);
setDefaultState(getDefaultState().with(Properties.AXIS, Direction.Axis.Y)); setDefaultState(getDefaultState().with(AXIS, Direction.Axis.Y));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(AXIS);
}
@Override
public int getModelStateCount() {
return 3;
} }
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(Properties.AXIS)); super.appendProperties(builder.add(AXIS));
} }
@Nullable @Nullable
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(Properties.AXIS, ctx.getSide().getAxis()); return super.getPlacementState(ctx).with(AXIS, ctx.getSide().getAxis());
} }
@Override @Override
public VoxelShape getShape(BlockState state, int i) { public VoxelShape getShape(BlockState state, int i) {
return switch (state.get(Properties.AXIS)) { return switch (state.get(AXIS)) {
case Y -> i == 2 ? UP : DOWN; case Y -> i == 2 ? UP : DOWN;
case Z -> i == 2 ? NORTH : SOUTH; case Z -> i == 2 ? NORTH : SOUTH;
case X -> i == 2 ? EAST : WEST; case X -> i == 2 ? EAST : WEST;

View File

@ -0,0 +1,113 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.Corner.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedSmallCubeBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] SMALL_CUBE_VOXELS;
public ReFramedSmallCubeBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(CORNER);
}
@Override
public int getModelStateCount() {
return 8;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(CORNER, BlockHelper.getPlacementCorner(ctx));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SMALL_CUBE_VOXELS[state.get(CORNER).getID()];
}
@Override
public BlockStateSupplier getMultipart() {
Identifier small_cube_id = ReFramed.id("small_cube_special");
return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R270))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP),
GBlockstate.variant(small_cube_id, true, R180, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP),
GBlockstate.variant(small_cube_id, true, R180, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP),
GBlockstate.variant(small_cube_id, true, R180, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP),
GBlockstate.variant(small_cube_id, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 8);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 8)
.input(ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape SMALL_CUBE = VoxelShapes.cuboid(.5f, 0f, 0f, 1f, .5f, .5f);
SMALL_CUBE_VOXELS = VoxelListBuilder.create(SMALL_CUBE, 8)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(SMALL_CUBE, VoxelHelper::mirrorY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.build();
}
}

View File

@ -0,0 +1,133 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedSmallCubeBlock.SMALL_CUBE_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedStepBlock.getStepShape;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedSmallCubesStepBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
public ReFramedSmallCubesStepBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getStepShape(state.get(EDGE));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStepShape(state.get(EDGE));
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
return SMALL_CUBE_VOXELS[Corner.getByDirections(
edge.getFirstDirection(),
edge.getSecondDirection(),
i == 1
? edge.getRightDirection()
: edge.getLeftDirection()
).getID()];
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
}
@Override
public BlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("small_cubes_step_special");
Identifier reverse_model_id = ReFramed.id("small_cubes_step_reverse_special");
return MultipartBlockStateSupplier.create(this)
/* X AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(reverse_model_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(model_id, true, R180, R0))
.with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(reverse_model_id, true, R180, R180))
.with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(model_id, true, R0, R180))
/* Y AXIS */
.with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(model_id, true, R90, R90))
.with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(model_id, true, R90, R180))
.with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(model_id, true, R90, R270))
/* Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(reverse_model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(model_id, true, R0, R270))
.with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(reverse_model_id, true, R180, R270))
.with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(model_id, true, R180, R90));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 4);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.SMALL_CUBE, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@ -0,0 +1,433 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.client.When;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.STAIR_SHAPE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static fr.adrien1106.reframed.util.blocks.StairShape.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedStairBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] STAIR_VOXELS;
private record ModelCacheKey(Edge edge, StairShape shape) {}
public ReFramedStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN).with(STAIR_SHAPE, STRAIGHT));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(EDGE), state.get(STAIR_SHAPE));
}
@Override
public int getModelStateCount() {
return 108; // Has 12 * 9 state combination and 52 models still reduces cache size
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(state.get(EDGE), world, pos));
}
@Nullable
@Override // Pretty happy of how clean it is (also got it on first try :) )
public BlockState getPlacementState(ItemPlacementContext ctx) {
Edge face = BlockHelper.getPlacementEdge(ctx);
StairShape shape = BlockHelper.getStairsShape(face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(EDGE, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStairShape(state.get(EDGE), state.get(STAIR_SHAPE));
}
public static VoxelShape getStairShape(Edge edge, StairShape shape) {
return STAIR_VOXELS[edge.getID() * 9 + shape.getID()];
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, false);
}
public static MultipartBlockStateSupplier getStairMultipart(Block block, boolean is_double) {
String infix = is_double ? "s_cube" : "";
Identifier straight_id = ReFramed.id("stair" + infix + "_special");
Identifier double_outer_id = ReFramed.id("outers_stair" + infix + "_special");
Identifier inner_id = ReFramed.id("inner_stair" + infix + "_special");
Identifier outer_id = ReFramed.id("outer_stair" + infix + "_special");
Identifier outer_side_id = ReFramed.id("outer_side_stair" + infix + "_special");
return MultipartBlockStateSupplier.create(block)
/* STRAIGHT X AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R0))
.with(GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R180))
.with(GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R180))
/* STRAIGHT Y AXIS */
.with(GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R0))
.with(GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R90))
.with(GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R180))
.with(GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R270))
/* STRAIGHT Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R90))
.with(GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R270))
.with(GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R270))
.with(GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R90))
/* INNER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R90))
/* INNER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R270))
/* OUTER EAST */
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R0))
/* OUTER SOUTH */
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R90))
/* OUTER WEST */
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R180))
/* OUTER NORTH */
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 4)
.pattern("I ")
.pattern("II ")
.pattern("III")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape STRAIGHT = VoxelShapes.combineAndSimplify(
createCuboidShape(0, 8, 0, 16, 16, 8),
createCuboidShape(0, 0, 0, 16, 8, 16),
BooleanBiFunction.OR
);
final VoxelShape INNER = Stream.of(
createCuboidShape(0, 8, 0, 16, 16, 8),
createCuboidShape(0, 8, 8, 8, 16, 16),
createCuboidShape(0, 0, 0, 16, 8, 16)
).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get();
final VoxelShape OUTER = Stream.of(
createCuboidShape(0, 8, 0, 8, 16, 8),
createCuboidShape(8, 0, 0, 16, 8, 8),
createCuboidShape(0, 0, 0, 8, 8, 16)
).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get();
final VoxelShape JUNCTION = VoxelShapes.combineAndSimplify(
createCuboidShape(0, 8, 0, 8, 16, 8),
createCuboidShape(0, 0, 0, 16, 8, 16),
BooleanBiFunction.OR
);
STAIR_VOXELS = VoxelListBuilder.create(STRAIGHT, 108)
.add(INNER).add(VoxelHelper::rotateY)
.add(OUTER).add(VoxelHelper::rotateY)
.add(JUNCTION).add(VoxelHelper::rotateY)
.add(JUNCTION, VoxelHelper::rotateX, VoxelHelper::rotateZ).add(VoxelHelper::rotateZ)
// DOWN_SOUTH
.add(0, VoxelHelper::rotateCX)
.add(1, VoxelHelper::rotateCX).add(2, VoxelHelper::rotateCX)
.add(3, VoxelHelper::rotateCX).add(4, VoxelHelper::rotateCX)
.add(5, VoxelHelper::rotateCX).add(6, VoxelHelper::rotateCX)
.add(7, VoxelHelper::rotateCX).add(8, VoxelHelper::rotateCX)
// SOUTH_UP
.add(9, VoxelHelper::rotateCX)
.add(10, VoxelHelper::rotateCX).add(11, VoxelHelper::rotateCX)
.add(12, VoxelHelper::rotateCX).add(13, VoxelHelper::rotateCX)
.add(14, VoxelHelper::rotateCX).add(15, VoxelHelper::rotateCX)
.add(16, VoxelHelper::rotateCX).add(17, VoxelHelper::rotateCX)
// UP_NORTH
.add(18, VoxelHelper::rotateCX)
.add(19, VoxelHelper::rotateCX).add(20, VoxelHelper::rotateCX)
.add(21, VoxelHelper::rotateCX).add(22, VoxelHelper::rotateCX)
.add(23, VoxelHelper::rotateCX).add(24, VoxelHelper::rotateCX)
.add(25, VoxelHelper::rotateCX).add(26, VoxelHelper::rotateCX)
// WEST_DOWN
.add(0, VoxelHelper::rotateCY)
.add(10).add(1)
.add(12).add(3)
.add(16).add(5)
.add(7, VoxelHelper::rotateCY).add(8, VoxelHelper::rotateCY)
// DOWN_EAST
.add(36, VoxelHelper::rotateZ)
.add(11).add(2)
.add(13).add(4)
.add(41, VoxelHelper::rotateZ).add(42, VoxelHelper::rotateZ)
.add(17).add(6)
// EAST_UP
.add(45, VoxelHelper::rotateZ)
.add(20).add(29)
.add(22).add(31)
.add(24).add(35)
.add(52, VoxelHelper::rotateZ).add(53, VoxelHelper::rotateZ)
// UP_WEST
.add(54, VoxelHelper::rotateZ)
.add(19).add(28)
.add(21).add(30)
.add(59, VoxelHelper::rotateZ).add(60, VoxelHelper::rotateZ)
.add(23).add(34)
// WEST_NORTH
.add(0, VoxelHelper::rotateCZ)
.add(1).add(28)
.add(3).add(30)
.add(7).add(32)
.add(44).add(69)
// NORTH_EAST
.add(72, VoxelHelper::rotateY)
.add(2).add(29)
.add(4).add(31)
.add(51).add(62)
.add(8).add(33)
// EAST_SOUTH
.add(81, VoxelHelper::rotateY)
.add(11).add(20)
.add(13).add(22)
.add(15).add(26)
.add(50).add(61)
// SOUTH_WEST
.add(90, VoxelHelper::rotateY)
.add(10).add(19)
.add(12).add(21)
.add(43).add(68)
.add(14).add(25)
.build();
}
}

View File

@ -1,525 +0,0 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.BlockHelper;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.property.Corner;
import fr.adrien1106.reframed.util.property.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.*;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.BlockProperties.*;
import static fr.adrien1106.reframed.util.property.StairShape.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static fr.adrien1106.reframed.util.property.Corner.*;
public class ReFramedStairsBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final List<VoxelShape> VOXEL_LIST = new ArrayList<>(52);
public ReFramedStairsBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, Corner.NORTH_DOWN).with(STAIR_SHAPE, STRAIGHT));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(state.getBlock(), state.get(CORNER), world, pos));
}
@Nullable
@Override // Pretty happy of how clean it is (also got it on first try :) )
public BlockState getPlacementState(ItemPlacementContext ctx) {
Corner face = BlockHelper.getPlacementCorner(ctx);
StairShape shape = BlockHelper.getStairsShape(this, face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(CORNER, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
/* ---------------------------------- DON'T GO FURTHER IF YOU LIKE HAVING EYES ---------------------------------- */
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getOutline(state);
}
public static VoxelShape getOutline(BlockState state) {
StairShape shape = state.get(STAIR_SHAPE);
Corner direction = state.get(CORNER);
return switch (shape) {
case STRAIGHT ->
switch (direction) {
case DOWN_SOUTH -> VOXEL_LIST.get(0);
case NORTH_DOWN -> VOXEL_LIST.get(1);
case UP_NORTH -> VOXEL_LIST.get(2);
case SOUTH_UP -> VOXEL_LIST.get(3);
case DOWN_EAST -> VOXEL_LIST.get(4);
case WEST_DOWN -> VOXEL_LIST.get(5);
case UP_WEST -> VOXEL_LIST.get(6);
case EAST_UP -> VOXEL_LIST.get(7);
case NORTH_EAST -> VOXEL_LIST.get(8);
case EAST_SOUTH -> VOXEL_LIST.get(9);
case SOUTH_WEST -> VOXEL_LIST.get(10);
case WEST_NORTH -> VOXEL_LIST.get(11);
};
case INNER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(44);
case DOWN_EAST -> VOXEL_LIST.get(45);
case DOWN_SOUTH -> VOXEL_LIST.get(47);
case UP_WEST, UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(48);
case EAST_UP, NORTH_EAST -> VOXEL_LIST.get(49);
case EAST_SOUTH -> VOXEL_LIST.get(50);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(51);
};
case INNER_RIGHT ->
switch (direction) {
case WEST_NORTH -> VOXEL_LIST.get(44);
case NORTH_DOWN, NORTH_EAST -> VOXEL_LIST.get(45);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(46);
case WEST_DOWN, SOUTH_WEST -> VOXEL_LIST.get(47);
case UP_NORTH -> VOXEL_LIST.get(49);
case EAST_UP, SOUTH_UP -> VOXEL_LIST.get(50);
case UP_WEST -> VOXEL_LIST.get(51);
};
case OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> VOXEL_LIST.get(43);
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(42);
case DOWN_SOUTH -> VOXEL_LIST.get(41);
case EAST_UP, NORTH_EAST -> VOXEL_LIST.get(39);
case UP_WEST, UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(38);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(37);
case EAST_SOUTH -> VOXEL_LIST.get(36);
};
case OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN, NORTH_EAST -> VOXEL_LIST.get(43);
case WEST_NORTH -> VOXEL_LIST.get(42);
case WEST_DOWN, SOUTH_WEST -> VOXEL_LIST.get(41);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(40);
case UP_NORTH -> VOXEL_LIST.get(39);
case UP_WEST -> VOXEL_LIST.get(37);
case EAST_UP, SOUTH_UP -> VOXEL_LIST.get(36);
};
case FIRST_OUTER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(14);
case SOUTH_UP -> VOXEL_LIST.get(17);
case EAST_UP -> VOXEL_LIST.get(19);
case EAST_SOUTH -> VOXEL_LIST.get(20);
case DOWN_SOUTH -> VOXEL_LIST.get(22);
case UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(25);
case SOUTH_WEST -> VOXEL_LIST.get(28);
case UP_WEST -> VOXEL_LIST.get(31);
case DOWN_EAST -> VOXEL_LIST.get(34);
case NORTH_EAST -> VOXEL_LIST.get(35);
};
case FIRST_OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN -> VOXEL_LIST.get(15);
case SOUTH_UP, EAST_UP -> VOXEL_LIST.get(16);
case WEST_DOWN -> VOXEL_LIST.get(13);
case DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(23);
case UP_NORTH -> VOXEL_LIST.get(24);
case WEST_NORTH -> VOXEL_LIST.get(26);
case UP_WEST -> VOXEL_LIST.get(28);
case SOUTH_WEST -> VOXEL_LIST.get(29);
case DOWN_EAST -> VOXEL_LIST.get(33);
case NORTH_EAST -> VOXEL_LIST.get(34);
};
case SECOND_OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> VOXEL_LIST.get(15);
case DOWN_SOUTH -> VOXEL_LIST.get(13);
case UP_WEST, UP_NORTH -> VOXEL_LIST.get(18);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(21);
case NORTH_EAST -> VOXEL_LIST.get(24);
case NORTH_DOWN -> VOXEL_LIST.get(26);
case WEST_DOWN -> VOXEL_LIST.get(30);
case WEST_NORTH -> VOXEL_LIST.get(31);
case EAST_SOUTH -> VOXEL_LIST.get(32);
case EAST_UP -> VOXEL_LIST.get(35);
};
case SECOND_OUTER_RIGHT ->
switch (direction) {
case DOWN_SOUTH, DOWN_EAST -> VOXEL_LIST.get(12);
case UP_WEST -> VOXEL_LIST.get(17);
case UP_NORTH -> VOXEL_LIST.get(19);
case SOUTH_UP -> VOXEL_LIST.get(20);
case SOUTH_WEST -> VOXEL_LIST.get(22);
case NORTH_EAST, NORTH_DOWN -> VOXEL_LIST.get(27);
case WEST_DOWN -> VOXEL_LIST.get(29);
case WEST_NORTH -> VOXEL_LIST.get(30);
case EAST_UP -> VOXEL_LIST.get(32);
case EAST_SOUTH -> VOXEL_LIST.get(33);
};
};
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, false);
}
public static MultipartBlockStateSupplier getStairMultipart(Block block, boolean is_double) {
String prefix = is_double ? "double_" : "";
Identifier straight_id = ReFramed.id(prefix + "stairs_special");
Identifier double_outer_id = ReFramed.id(prefix + "outers_stairs_special");
Identifier inner_id = ReFramed.id(prefix + "inner_stairs_special");
Identifier outer_id = ReFramed.id(prefix + "outer_stairs_special");
Identifier outer_side_id = ReFramed.id(prefix + "outer_side_stairs_special");
return MultipartBlockStateSupplier.create(block)
/* STRAIGHT X AXIS */
.with(GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R0))
.with(GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R180))
.with(GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R180))
/* STRAIGHT Y AXIS */
.with(GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R270))
/* STRAIGHT Z AXIS */
.with(GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R90))
.with(GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R270))
.with(GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R270))
.with(GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R90))
/* INNER BOTTOM */
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R90))
/* INNER TOP */
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R270))
/* OUTER EAST */
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R0))
/* OUTER SOUTH */
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R90))
/* OUTER WEST */
.with(When.anyOf(
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R180))
/* OUTER NORTH */
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R270))
.with(When.anyOf(
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R270))
.with(When.anyOf(
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, DOWN_SOUTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, WEST_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, NORTH_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, DOWN_EAST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, NORTH_EAST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, EAST_SOUTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(CORNER, SOUTH_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(CORNER, SOUTH_WEST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(CORNER, UP_NORTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, UP_WEST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(CORNER, WEST_NORTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 4)
.pattern("I ")
.pattern("II ")
.pattern("III")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape STRAIGHT = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape JUNCTION = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape OUTER = Stream.of(
VoxelShapes.cuboid(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.5f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.0f, 0.5f, 1.0f, 0.5f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape INNER = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.0f, 0.5f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.5f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
VOXEL_LIST.add(STRAIGHT);
VOXEL_LIST.add(VoxelHelper.mirror(STRAIGHT, Axis.Z));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.Z), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y), Axis.Z));
VOXEL_LIST.add(JUNCTION);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(OUTER);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(INNER);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(INNER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y));
}
}

View File

@ -0,0 +1,100 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedStairBlock.*;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.STAIR_SHAPE;
public class ReFramedStairsCubeBlock extends ReFramedDoubleBlock implements BlockStateProvider {
private static final VoxelShape[] STAIRS_CUBE_VOXELS = VoxelListBuilder.buildFrom(STAIR_VOXELS);
private record ModelCacheKey(Edge edge, StairShape shape) {}
public ReFramedStairsCubeBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN).with(STAIR_SHAPE, StairShape.STRAIGHT));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(EDGE), state.get(STAIR_SHAPE));
}
@Override
public int getModelStateCount() {
return 108; // Has 12 * 9 state combination and 52 models still reduces cache size
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(state.get(EDGE), world, pos));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
Edge face = BlockHelper.getPlacementEdge(ctx);
StairShape shape = BlockHelper.getStairsShape(face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(EDGE, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
StairShape shape = state.get(STAIR_SHAPE);
return i == 2 ? STAIRS_CUBE_VOXELS[edge.getID() * 9 + shape.getID()] : getStairShape(edge, shape);
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, true);
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.STAIR)
.input(ReFramed.STEP)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@ -1,15 +1,16 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.generator.BlockStateProvider; import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper; import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.property.Corner; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier; import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter; import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider; import net.minecraft.data.server.recipe.RecipeProvider;
@ -19,88 +20,85 @@ import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import java.util.List; import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static fr.adrien1106.reframed.util.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.property.Corner.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*; import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedStepBlock extends WaterloggableReFramedBlock implements BlockStateProvider { public class ReFramedStepBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final List<VoxelShape> STEP_VOXELS = new ArrayList<>(12); public static final VoxelShape[] STEP_VOXELS;
public ReFramedStepBlock(Settings settings) { public ReFramedStepBlock(Settings settings) {
super(settings); super(settings);
setDefaultState(getDefaultState().with(CORNER, Corner.NORTH_DOWN)); setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
} }
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER)); super.appendProperties(builder.add(EDGE));
} }
@Nullable @Nullable
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(CORNER, BlockHelper.getPlacementCorner(ctx)); return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
} }
@Override @Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return switch (state.get(CORNER)) { return getStepShape(state.get(EDGE));
case DOWN_SOUTH -> STEP_VOXELS.get(0); }
case NORTH_DOWN -> STEP_VOXELS.get(1);
case UP_NORTH -> STEP_VOXELS.get(2); public static VoxelShape getStepShape(Edge edge) {
case SOUTH_UP -> STEP_VOXELS.get(3); return STEP_VOXELS[edge.getID()];
case DOWN_EAST -> STEP_VOXELS.get(4);
case WEST_DOWN -> STEP_VOXELS.get(5);
case UP_WEST -> STEP_VOXELS.get(6);
case EAST_UP -> STEP_VOXELS.get(7);
case NORTH_EAST -> STEP_VOXELS.get(8);
case EAST_SOUTH -> STEP_VOXELS.get(9);
case SOUTH_WEST -> STEP_VOXELS.get(10);
case WEST_NORTH -> STEP_VOXELS.get(11);
};
} }
@Override @Override
public MultipartBlockStateSupplier getMultipart() { public BlockStateSupplier getMultipart() {
Identifier step_id = ReFramed.id("step_special"); Identifier model_id = ReFramed.id("step_special");
return MultipartBlockStateSupplier.create(this) return MultipartBlockStateSupplier.create(this)
/* X AXIS */ /* X AXIS */
.with(GBlockstate.when(CORNER, DOWN_EAST), .with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(step_id, true, R0, R0)) GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_UP), .with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(step_id, true, R180, R0)) GBlockstate.variant(model_id, true, R180, R0))
.with(GBlockstate.when(CORNER, UP_WEST), .with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(step_id, true, R180, R180)) GBlockstate.variant(model_id, true, R180, R180))
.with(GBlockstate.when(CORNER, WEST_DOWN), .with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(step_id, true, R0, R180)) GBlockstate.variant(model_id, true, R0, R180))
/* Y AXIS */ /* Y AXIS */
.with(GBlockstate.when(CORNER, EAST_SOUTH), .with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(step_id, true, R90, R0)) GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST), .with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(step_id, true, R90, R90)) GBlockstate.variant(model_id, true, R90, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH), .with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(step_id, true, R90, R180)) GBlockstate.variant(model_id, true, R90, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST), .with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(step_id, true, R90, R270)) GBlockstate.variant(model_id, true, R90, R270))
/* Z AXIS */ /* Z AXIS */
.with(GBlockstate.when(CORNER, DOWN_SOUTH), .with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(step_id, true, R0, R90)) GBlockstate.variant(model_id, true, R0, R90))
.with(GBlockstate.when(CORNER, NORTH_DOWN), .with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(step_id, true, R0, R270)) GBlockstate.variant(model_id, true, R0, R270))
.with(GBlockstate.when(CORNER, UP_NORTH), .with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(step_id, true, R180, R270)) GBlockstate.variant(model_id, true, R180, R270))
.with(GBlockstate.when(CORNER, SOUTH_UP), .with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(step_id, true, R180, R90)); GBlockstate.variant(model_id, true, R180, R90));
} }
@Override @Override
@ -116,21 +114,22 @@ public class ReFramedStepBlock extends WaterloggableReFramedBlock implements Blo
} }
static { static {
final VoxelShape STEP = VoxelShapes.cuboid(0f, 0f, .5f, 1f, .5f, 1f); final VoxelShape STEP = createCuboidShape(0, 0, 0, 16, 8, 8);
STEP_VOXELS.add(STEP); STEP_VOXELS = VoxelListBuilder.create(STEP, 12)
STEP_VOXELS.add(VoxelHelper.mirror(STEP, Direction.Axis.Z)); .add(VoxelHelper::rotateCX)
STEP_VOXELS.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.X), Direction.Axis.Z)); .add(VoxelHelper::rotateCX)
STEP_VOXELS.add(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.X)); .add(VoxelHelper::rotateCX)
STEP_VOXELS.add(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.Y)); .add(STEP, VoxelHelper::rotateCY)
STEP_VOXELS.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.Y), Direction.Axis.X)); .add(VoxelHelper::rotateZ)
STEP_VOXELS.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.Y), Direction.Axis.Z), Direction.Axis.X)); .add(VoxelHelper::rotateZ)
STEP_VOXELS.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STEP, Direction.Axis.Y), Direction.Axis.Z)); .add(VoxelHelper::rotateZ)
STEP_VOXELS.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateClockwise(STEP, Direction.Axis.Z), Direction.Axis.Y)); .add(STEP, VoxelHelper::rotateCZ)
STEP_VOXELS.add(VoxelHelper.rotateClockwise(STEP, Direction.Axis.Z)); .add(VoxelHelper::rotateY)
STEP_VOXELS.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STEP, Direction.Axis.Z), Direction.Axis.Y)); .add(VoxelHelper::rotateY)
STEP_VOXELS.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STEP, Direction.Axis.Z), Direction.Axis.Y), Direction.Axis.Z)); .add(VoxelHelper::rotateY)
.build();
} }
} }

View File

@ -1,9 +1,10 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.generator.BlockStateProvider; import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -24,19 +25,31 @@ import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedSlabBlock.*; import static fr.adrien1106.reframed.block.ReFramedSlabBlock.getSlabShape;
import static fr.adrien1106.reframed.block.ReFramedStepBlock.STEP_VOXELS; import static fr.adrien1106.reframed.block.ReFramedStepBlock.getStepShape;
import static net.minecraft.data.client.VariantSettings.Rotation.*; import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.FACING;
import static net.minecraft.state.property.Properties.AXIS; import static net.minecraft.state.property.Properties.AXIS;
import static net.minecraft.state.property.Properties.FACING;
import static net.minecraft.util.shape.VoxelShapes.empty; import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedDoubleStepBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider { public class ReFramedStepsSlabBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
public ReFramedDoubleStepBlock(Settings settings) { private record ModelCacheKey(Direction facing, Axis axis) {}
public ReFramedStepsSlabBlock(Settings settings) {
super(settings); super(settings);
setDefaultState(getDefaultState().with(FACING, Direction.DOWN).with(AXIS, Axis.X)); setDefaultState(getDefaultState().with(FACING, Direction.DOWN).with(AXIS, Axis.X));
} }
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(FACING), state.get(AXIS));
}
@Override
public int getModelStateCount() {
return 18;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(FACING, AXIS)); super.appendProperties(builder.add(FACING, AXIS));
@ -63,36 +76,41 @@ public class ReFramedDoubleStepBlock extends WaterloggableReFramedDoubleBlock im
@Override @Override
public VoxelShape getShape(BlockState state, int i) { public VoxelShape getShape(BlockState state, int i) {
Axis axis = state.get(AXIS); Axis axis = state.get(AXIS);
return STEP_VOXELS.get(switch (state.get(FACING)) { return getStepShape(Edge.getByDirections(
case DOWN -> axis == Axis.Z ? (i == 1 ? 0 : 1): (i == 1 ? 4 : 5 ); state.get(FACING),
case UP -> axis == Axis.Z ? (i == 1 ? 3 : 2): (i == 1 ? 7 : 6 ); switch (axis) {
case NORTH -> axis == Axis.Y ? (i == 1 ? 1 : 2): (i == 1 ? 11 : 8 ); case X -> i == 1 ? Direction.WEST : Direction.EAST;
case SOUTH -> axis == Axis.Y ? (i == 1 ? 0 : 3): (i == 1 ? 9 : 10); case Y -> i == 1 ? Direction.DOWN : Direction.UP;
case EAST -> axis == Axis.Y ? (i == 1 ? 4 : 7): (i == 1 ? 8 : 9 ); case Z -> i == 1 ? Direction.SOUTH : Direction.NORTH;
case WEST -> axis == Axis.Y ? (i == 1 ? 5 : 6): (i == 1 ? 10 : 11); }
}); ));
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
} }
@Override @Override
public MultipartBlockStateSupplier getMultipart() { public MultipartBlockStateSupplier getMultipart() {
Identifier step_id = ReFramed.id("double_step_special"); Identifier step_id = ReFramed.id("steps_slab_special");
Identifier step_side_id = ReFramed.id("double_step_side_special"); Identifier step_side_id = ReFramed.id("steps_slab_side_special");
return MultipartBlockStateSupplier.create(this) return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.X), .with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.X),
GBlockstate.variant(step_id, true, R0, R0)) GBlockstate.variant(step_id, true, R0, R180))
.with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.Z), .with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.Z),
GBlockstate.variant(step_id, true, R0, R90)) GBlockstate.variant(step_id, true, R0, R90))
.with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.X), .with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.X),
GBlockstate.variant(step_id, true, R180, R0)) GBlockstate.variant(step_id, true, R180, R180))
.with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.Z), .with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.Z),
GBlockstate.variant(step_id, true, R180, R90)) GBlockstate.variant(step_id, true, R180, R90))
.with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Z), .with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Z),
GBlockstate.variant(step_side_id, true, R0, R0)) GBlockstate.variant(step_side_id, true, R180, R0))
.with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Y), .with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R0)) GBlockstate.variant(step_side_id, true, R90, R0))
.with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.X), .with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.X),
GBlockstate.variant(step_side_id, true, R0, R90)) GBlockstate.variant(step_side_id, true, R180, R90))
.with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.Y), .with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R90)) GBlockstate.variant(step_side_id, true, R90, R90))
.with(GBlockstate.when(FACING, Direction.WEST, AXIS, Axis.Z), .with(GBlockstate.when(FACING, Direction.WEST, AXIS, Axis.Z),

View File

@ -26,31 +26,68 @@ public class ReFramedClient implements ClientModInitializer {
//all frames mustn't be on the SOLID layer because they are not opaque! //all frames mustn't be on the SOLID layer because they are not opaque!
BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getCutout(), ReFramed.BLOCKS.toArray(new Block[0])); BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getCutout(), ReFramed.BLOCKS.toArray(new Block[0]));
HELPER.addReFramedModel("cube_special" , HELPER.auto(new Identifier("block/cube"))); // CUBE
HELPER.addReFramedModel("slab_special" , HELPER.auto(new Identifier("block/slab"))); HELPER.addReFramedModel("cube_special" , HELPER.auto(new Identifier("block/cube")));
HELPER.addReFramedModel("double_slab_special" , HELPER.autoDouble(new Identifier("block/slab"), new Identifier("block/slab_top"))); // SMALL_CUBE
HELPER.addReFramedModel("stairs_special" , HELPER.auto(ReFramed.id("block/stairs"))); HELPER.addReFramedModel("small_cube_special" , HELPER.auto(ReFramed.id("block/small_cube/base")));
HELPER.addReFramedModel("outers_stairs_special" , HELPER.auto(ReFramed.id("block/double_outer_stairs"))); // SMALL_CUBES_STEP
HELPER.addReFramedModel("inner_stairs_special" , HELPER.auto(ReFramed.id("block/inner_stairs"))); HELPER.addReFramedModel("small_cubes_step_special" , HELPER.autoDouble(ReFramed.id("block/small_cube/base"), ReFramed.id("block/small_cube/step/base")));
HELPER.addReFramedModel("outer_stairs_special" , HELPER.auto(ReFramed.id("block/outer_stairs"))); HELPER.addReFramedModel("small_cubes_step_reverse_special" , HELPER.autoDouble(ReFramed.id("block/small_cube/step/base"), ReFramed.id("block/small_cube/base")));
HELPER.addReFramedModel("outer_side_stairs_special" , HELPER.auto(ReFramed.id("block/outer_side_stairs"))); // SLAB
HELPER.addReFramedModel("double_stairs_special" , HELPER.autoDouble(ReFramed.id("block/stairs"), ReFramed.id("block/stairs_complement"))); HELPER.addReFramedModel("slab_special" , HELPER.auto(new Identifier("block/slab")));
HELPER.addReFramedModel("double_outers_stairs_special" , HELPER.autoDouble(ReFramed.id("block/double_outer_stairs"), ReFramed.id("block/double_outer_stairs_complement"))); // SLAB_CUBE
HELPER.addReFramedModel("double_inner_stairs_special" , HELPER.autoDouble(ReFramed.id("block/inner_stairs"), ReFramed.id("block/inner_stairs_complement"))); HELPER.addReFramedModel("double_slab_special" , HELPER.autoDouble(new Identifier("block/slab"), new Identifier("block/slab_top")));
HELPER.addReFramedModel("double_outer_stairs_special" , HELPER.autoDouble(ReFramed.id("block/outer_stairs"), ReFramed.id("block/outer_stairs_complement"))); // STAIR
HELPER.addReFramedModel("double_outer_side_stairs_special", HELPER.autoDouble(ReFramed.id("block/outer_side_stairs"), ReFramed.id("block/outer_side_stairs_complement"))); HELPER.addReFramedModel("stair_special" , HELPER.auto(ReFramed.id("block/stair/straight")));
HELPER.addReFramedModel("step_special" , HELPER.auto(ReFramed.id("block/step"))); HELPER.addReFramedModel("outers_stair_special" , HELPER.auto(ReFramed.id("block/stair/double_outer")));
HELPER.addReFramedModel("double_step_special" , HELPER.autoDouble(ReFramed.id("block/step"), ReFramed.id("block/step_complement_slab"))); HELPER.addReFramedModel("inner_stair_special" , HELPER.auto(ReFramed.id("block/stair/inner")));
HELPER.addReFramedModel("double_step_side_special" , HELPER.autoDouble(ReFramed.id("block/step_side"), ReFramed.id("block/step_side_complement_slab"))); HELPER.addReFramedModel("outer_stair_special" , HELPER.auto(ReFramed.id("block/stair/outer")));
HELPER.addReFramedModel("outer_side_stair_special" , HELPER.auto(ReFramed.id("block/stair/outer_side")));
// STAIRS_CUBE
HELPER.addReFramedModel("stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/straight"), ReFramed.id("block/stair/cube/straight")));
HELPER.addReFramedModel("outers_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/double_outer"), ReFramed.id("block/stair/cube/double_outer")));
HELPER.addReFramedModel("inner_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/inner"), ReFramed.id("block/stair/cube/inner")));
HELPER.addReFramedModel("outer_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/outer"), ReFramed.id("block/stair/cube/outer")));
HELPER.addReFramedModel("outer_side_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/outer_side"), ReFramed.id("block/stair/cube/outer_side")));
// HALF_STAIR
HELPER.addReFramedModel("half_stair_down_special" , HELPER.auto(ReFramed.id("block/half_stair/down")));
HELPER.addReFramedModel("half_stair_side_special" , HELPER.auto(ReFramed.id("block/half_stair/side")));
// HALF_STAIRS_SLAB
HELPER.addReFramedModel("half_stairs_slab_down_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/down"), ReFramed.id("block/half_stair/slab/down")));
HELPER.addReFramedModel("half_stairs_slab_side_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/side"), ReFramed.id("block/half_stair/slab/side")));
// HALF_STAIRS_STAIR
HELPER.addReFramedModel("half_stairs_stair_down_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/down"), ReFramed.id("block/half_stair/stair/down")));
HELPER.addReFramedModel("half_stairs_stair_side_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/side"), ReFramed.id("block/half_stair/stair/side")));
HELPER.addReFramedModel("half_stairs_stair_reverse_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/stair/side"), ReFramed.id("block/half_stair/side")));
// STEP
HELPER.addReFramedModel("step_special" , HELPER.auto(ReFramed.id("block/step/down")));
// STEPS_SLAB
HELPER.addReFramedModel("steps_slab_special" , HELPER.autoDouble(ReFramed.id("block/step/down"), ReFramed.id("block/step/slab/down")));
HELPER.addReFramedModel("steps_slab_side_special" , HELPER.autoDouble(ReFramed.id("block/step/side"), ReFramed.id("block/step/slab/side")));
// LAYER
HELPER.addReFramedModel("layer_1_special" , HELPER.auto(new Identifier("block/snow_height2")));
HELPER.addReFramedModel("layer_2_special" , HELPER.auto(new Identifier("block/snow_height4")));
HELPER.addReFramedModel("layer_3_special" , HELPER.auto(new Identifier("block/snow_height6")));
HELPER.addReFramedModel("layer_4_special" , HELPER.auto(new Identifier("block/snow_height8")));
HELPER.addReFramedModel("layer_5_special" , HELPER.auto(new Identifier("block/snow_height10")));
HELPER.addReFramedModel("layer_6_special" , HELPER.auto(new Identifier("block/snow_height12")));
HELPER.addReFramedModel("layer_7_special" , HELPER.auto(new Identifier("block/snow_height14")));
HELPER.addReFramedModel("layer_8_special" , HELPER.auto(new Identifier("block/cube")));
//item model assignments (in lieu of models/item/___.json) //item model assignments (in lieu of models/item/___.json)
HELPER.assignItemModel("cube_special" , ReFramed.CUBE); HELPER.assignItemModel("cube_special" , ReFramed.CUBE);
HELPER.assignItemModel("small_cube_special" , ReFramed.SMALL_CUBE);
HELPER.assignItemModel("small_cubes_step_special" , ReFramed.SMALL_CUBES_STEP);
HELPER.assignItemModel("slab_special" , ReFramed.SLAB); HELPER.assignItemModel("slab_special" , ReFramed.SLAB);
HELPER.assignItemModel("double_slab_special" , ReFramed.DOUBLE_SLAB); HELPER.assignItemModel("double_slab_special" , ReFramed.SLABS_CUBE);
HELPER.assignItemModel("stairs_special" , ReFramed.STAIRS); HELPER.assignItemModel("stair_special" , ReFramed.STAIR);
HELPER.assignItemModel("double_stairs_special" , ReFramed.DOUBLE_STAIRS); HELPER.assignItemModel("stairs_cube_special" , ReFramed.STAIRS_CUBE);
HELPER.assignItemModel("half_stair_down_special" , ReFramed.HALF_STAIR);
HELPER.assignItemModel("half_stairs_slab_down_special" , ReFramed.HALF_STAIRS_SLAB);
HELPER.assignItemModel("half_stairs_stair_down_special", ReFramed.HALF_STAIRS_STAIR);
HELPER.assignItemModel("step_special" , ReFramed.STEP); HELPER.assignItemModel("step_special" , ReFramed.STEP);
HELPER.assignItemModel("double_step_special" , ReFramed.DOUBLE_STEP); HELPER.assignItemModel("steps_slab_special" , ReFramed.STEPS_SLAB);
HELPER.assignItemModel("layer_1_special" , ReFramed.LAYER);
} }
private void privateInit() { private void privateInit() {

View File

@ -5,7 +5,6 @@ import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@ -45,7 +44,7 @@ public class DoubleRetexturingBakedModel extends ForwardingBakedModel implements
} }
@Override @Override
public List<BakedModel> models() { public List<ForwardingBakedModel> models() {
return List.of(model_1, model_2); return List.of(model_1, model_2);
} }
} }

View File

@ -1,10 +1,10 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
import net.minecraft.client.render.model.BakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import java.util.List; import java.util.List;
public interface MultiRetexturableModel { public interface MultiRetexturableModel {
List<BakedModel> models(); List<ForwardingBakedModel> models();
} }

View File

@ -1,11 +1,14 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
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.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import org.joml.Vector3f; import org.joml.Vector3f;
@Environment(EnvType.CLIENT)
public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z) { public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z) {
public static QuadPosBounds read(QuadView quad) { public static QuadPosBounds read(QuadView quad) {
@ -76,4 +79,15 @@ public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y,
quad.pos(i, pos); quad.pos(i, pos);
} }
} }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof QuadPosBounds other)) return false;
return MathHelper.approximatelyEquals(min_x, other.min_x)
&& MathHelper.approximatelyEquals(min_y, other.min_y)
&& MathHelper.approximatelyEquals(min_z, other.min_z)
&& MathHelper.approximatelyEquals(max_x, other.max_x)
&& MathHelper.approximatelyEquals(max_y, other.max_y)
&& MathHelper.approximatelyEquals(max_z, other.max_z);
}
} }

View File

@ -1,10 +1,13 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
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.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
@Environment(EnvType.CLIENT)
public record QuadUvBounds(float minU, float maxU, float minV, float maxV) { public record QuadUvBounds(float minU, float maxU, float minV, float maxV) {
public static QuadUvBounds read(QuadView quad) { public static QuadUvBounds read(QuadView quad) {
float u0 = quad.u(0), u1 = quad.u(1), u2 = quad.u(2), u3 = quad.u(3); float u0 = quad.u(0), u1 = quad.u(1), u2 = quad.u(2), u3 = quad.u(3);

View File

@ -1,17 +1,15 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.block.ReFramedEntity; import fr.adrien1106.reframed.block.ReFramedEntity;
import fr.adrien1106.reframed.client.ReFramedClient; import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.apperance.SpriteProperties;
import fr.adrien1106.reframed.mixin.MinecraftAccessor; import fr.adrien1106.reframed.mixin.MinecraftAccessor;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearance; import fr.adrien1106.reframed.client.model.apperance.CamoAppearance;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager; import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance; import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; import net.fabricmc.fabric.api.renderer.v1.mesh.*;
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.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -26,34 +24,43 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
import java.util.List; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier; import java.util.function.Supplier;
public abstract class RetexturingBakedModel extends ForwardingBakedModel { public abstract class RetexturingBakedModel extends ForwardingBakedModel {
public RetexturingBakedModel(BakedModel base_model, CamoAppearanceManager tam, int theme_index, ModelBakeSettings settings, BlockState item_state, boolean ao) { public RetexturingBakedModel(BakedModel base_model, CamoAppearanceManager tam, int theme_index, ModelBakeSettings settings, BlockState item_state, boolean ao) {
this.wrapped = base_model; //field from the superclass; vanilla getQuads etc. will delegate through to this this.wrapped = base_model; //field from the superclass; vanilla getQuads etc. will delegate through to this
this.tam = tam; this.appearance_manager = tam;
this.theme_index = theme_index; this.theme_index = theme_index;
this.uv_lock = settings.isUvLocked(); this.uv_lock = settings.isUvLocked();
this.item_state = item_state; this.item_state = item_state;
this.ao = ao; this.ao = ao;
int cache_size = 64; // default is 64 why don't ask me and it should get overwritten
if (item_state.getBlock() instanceof ReFramedBlock frame_block) cache_size = frame_block.getModelStateCount() + 1;
BASE_MESH_CACHE = new Object2ObjectLinkedOpenHashMap<>(cache_size, 0.25f) {
@Override
protected void rehash(int v) {}
};
} }
protected final CamoAppearanceManager tam; protected final CamoAppearanceManager appearance_manager;
protected final int theme_index; protected final int theme_index;
protected final boolean uv_lock; protected final boolean uv_lock;
protected final BlockState item_state;
protected final boolean ao; protected final boolean ao;
protected final BlockState item_state;
/* ----------------------------------------------- CACHE ELEMENT ------------------------------------------------ */ protected record MeshCacheKey(Object state_key, CamoAppearance appearance, int model_id) {}
// TODO make static ? for connected textures ? /** cache that store retextured models */
protected record MeshCacheKey(BlockState state, TransformCacheKey transform) {} protected final Object2ObjectLinkedOpenHashMap<MeshCacheKey, Mesh> RETEXTURED_MESH_CACHE =
protected record TransformCacheKey(CamoAppearance appearance, int model_id) {} new Object2ObjectLinkedOpenHashMap<>(128, 0.25f) {
protected final ConcurrentMap<TransformCacheKey, RetexturingTransformer> retextured_transforms = new ConcurrentHashMap<>(); @Override
protected final ConcurrentMap<MeshCacheKey, Mesh> retextured_meshes = new ConcurrentHashMap<>(); //mutable, append-only cache protected void rehash(int v) {}
};
/** cache that stores the base meshes which has the size of the amount of models */
protected final Object2ObjectLinkedOpenHashMap<Object, Mesh> BASE_MESH_CACHE;
protected static final Direction[] DIRECTIONS_AND_NULL; protected static final Direction[] DIRECTIONS_AND_NULL;
static { static {
@ -62,11 +69,12 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
System.arraycopy(values, 0, DIRECTIONS_AND_NULL, 0, values.length); System.arraycopy(values, 0, DIRECTIONS_AND_NULL, 0, values.length);
} }
protected final ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>(); protected Mesh getBaseMesh(Object key, BlockState state) {
protected Mesh getBaseMesh(BlockState state) {
//Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate //Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate
return jsonToMesh.computeIfAbsent(state, this::convertModel); if (BASE_MESH_CACHE.containsKey(key)) return BASE_MESH_CACHE.getAndMoveToFirst(key);
Mesh mesh = convertModel(state);
BASE_MESH_CACHE.putAndMoveToFirst(key, mesh);
return mesh;
} }
protected abstract Mesh convertModel(BlockState state); protected abstract Mesh convertModel(BlockState state);
@ -78,39 +86,42 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
@Override @Override
public Sprite getParticleSprite() { public Sprite getParticleSprite() {
return tam.getDefaultAppearance(theme_index).getSprites(Direction.UP, 0).get(0).sprite(); return appearance_manager.getDefaultAppearance(theme_index).getSprites(Direction.UP, 0).get(0).sprite();
} }
@Override @Override
public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) { public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
// skip render if block not a frame (which should always be the case
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
BlockState theme = (world.getBlockEntity(pos) instanceof ThemeableBlockEntity s) ? s.getTheme(theme_index) : null; BlockState theme = (world.getBlockEntity(pos) instanceof ThemeableBlockEntity s) ? s.getTheme(theme_index) : null;
QuadEmitter quad_emitter = context.getEmitter(); QuadEmitter quad_emitter = context.getEmitter();
if(theme == null || theme.isAir()) { if(theme == null || theme.isAir()) {
getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(theme_index), 0)), 0).outputTo(quad_emitter); getRetexturedMesh(
new MeshCacheKey(
frame_block.getModelCacheKey(state),
appearance_manager.getDefaultAppearance(theme_index),
0
),
state
).outputTo(quad_emitter);
return; return;
} }
if(theme.getBlock() == Blocks.BARRIER) return; if(theme.getBlock() == Blocks.BARRIER) return;
CamoAppearance camo = tam.getCamoAppearance(world, theme, pos, theme_index); CamoAppearance camo = appearance_manager.getCamoAppearance(world, theme, pos, theme_index, false);
long seed = theme.getRenderingSeed(pos); long seed = theme.getRenderingSeed(pos);
int model_id = 0; int model_id = 0;
if (camo instanceof WeightedComputedAppearance wca) model_id = wca.getAppearanceIndex(seed); if (camo instanceof WeightedComputedAppearance wca) model_id = wca.getAppearanceIndex(seed);
int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, world, pos, 0); int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, world, pos, 0);
Mesh untintedMesh = getUntintedRetexturedMesh( Mesh untintedMesh = getRetexturedMesh(new MeshCacheKey(frame_block.getModelCacheKey(state), camo, model_id), state);
new MeshCacheKey(
state,
new TransformCacheKey(camo, model_id)
),
seed
);
//The specific tint might vary a lot; imagine grass color smoothly changing. Trying to bake the tint into //The specific tint might vary a lot; imagine grass color smoothly changing. Trying to bake the tint into
//the cached mesh will pollute it with a ton of single-use meshes with only slightly different colors. //the cached mesh will pollute it with a ton of single-use meshes with only slightly different colors.
if(tint == 0xFFFFFFFF) { if(tint == 0xFFFFFFFF) {
untintedMesh.outputTo(quad_emitter); untintedMesh.outputTo(quad_emitter);
} else { } else {
context.pushTransform(new TintingTransformer(camo, tint, seed)); context.pushTransform(new TintingTransformer(camo, model_id, tint));
untintedMesh.outputTo(quad_emitter); untintedMesh.outputTo(quad_emitter);
context.popTransform(); context.popTransform();
} }
@ -120,121 +131,72 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) { public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
//cheeky: if the item has NBT data, pluck out the blockstate from it & look up the item color provider //cheeky: if the item has NBT data, pluck out the blockstate from it & look up the item color provider
//none of this is accessible unless you're in creative mode doing ctrl-pick btw //none of this is accessible unless you're in creative mode doing ctrl-pick btw
CamoAppearance nbtAppearance; CamoAppearance appearance;
int tint; int tint;
BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index); BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index);
if(!theme.isAir()) { if(!theme.isAir()) {
nbtAppearance = tam.getCamoAppearance(null, theme, null, theme_index); appearance = appearance_manager.getCamoAppearance(null, theme, null, theme_index, true);
tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0); tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0);
} else { } else {
nbtAppearance = tam.getDefaultAppearance(theme_index); appearance = appearance_manager.getDefaultAppearance(theme_index);
tint = 0xFFFFFFFF; tint = 0xFFFFFFFF;
} }
Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(item_state, new TransformCacheKey(nbtAppearance, 0)), 0); Mesh untintedMesh = getRetexturedMesh(new MeshCacheKey("I", appearance, 0), item_state);
QuadEmitter quad_emitter = context.getEmitter(); QuadEmitter quad_emitter = context.getEmitter();
if(tint == 0xFFFFFFFF) { if(tint == 0xFFFFFFFF) {
untintedMesh.outputTo(quad_emitter); untintedMesh.outputTo(quad_emitter);
} else { } else {
context.pushTransform(new TintingTransformer(nbtAppearance, tint, 0)); context.pushTransform(new TintingTransformer(appearance, 0, tint));
untintedMesh.outputTo(quad_emitter); untintedMesh.outputTo(quad_emitter);
context.popTransform(); context.popTransform();
} }
} }
protected Mesh getUntintedRetexturedMesh(MeshCacheKey key, long seed) { protected Mesh getRetexturedMesh(MeshCacheKey key, BlockState state) {
return retextured_meshes.computeIfAbsent(key, (k) -> createUntintedRetexturedMesh(k, seed)); if (RETEXTURED_MESH_CACHE.containsKey(key)) return RETEXTURED_MESH_CACHE.getAndMoveToFirst(key);
Mesh mesh = transformMesh(key, state);
RETEXTURED_MESH_CACHE.putAndMoveToFirst(key, mesh);
return mesh;
} }
protected Mesh createUntintedRetexturedMesh(MeshCacheKey key, long seed) { protected Mesh transformMesh(MeshCacheKey key, BlockState state) {
RetexturingTransformer transformer = retextured_transforms.computeIfAbsent(key.transform, (k) -> new RetexturingTransformer(k.appearance, seed));
return pretransformMesh(getBaseMesh(key.state), transformer);
}
private static Mesh pretransformMesh(Mesh mesh, RetexturingTransformer transform) {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder(); MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter(); QuadEmitter emitter = builder.getEmitter();
mesh.forEach(quad -> { AtomicInteger quad_index = new AtomicInteger();
getBaseMesh(key.state_key, state).forEach(quad -> {
int i = -1; int i = -1;
do { do {
emitter.copyFrom(quad); emitter.copyFrom(quad);
i = transform.transform(emitter, i); i = key.appearance.transformQuad(emitter, i, quad_index.get(), key.model_id, ao, uv_lock);
} while (i > 0); } while (i > 0);
// kinda weird to do it like that but other directions don't use the quad_index so it doesn't matter
if (quad.cullFace() == null) quad_index.getAndIncrement();
}); });
return builder.build(); return builder.build();
} }
public class RetexturingTransformer {
private final long seed;
protected RetexturingTransformer(CamoAppearance ta, long seed) {
this.ta = ta;
this.seed = seed;
}
protected final CamoAppearance ta;
public int transform(QuadEmitter quad, int i) {
if(quad.tag() == 0) return 0; //Pass the quad through unmodified.
Direction direction = quad.nominalFace();
List<SpriteProperties> sprites = ta.getSprites(direction, seed);
if (i == -1) i = sprites.size();
SpriteProperties properties = sprites.get(sprites.size() - i);
i--;
QuadPosBounds bounds = properties.bounds();
if (bounds == null) { // sprite applies anywhere e.g. default behaviour
quad.material(ta.getRenderMaterial(ao));
quad.spriteBake(
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| properties.flags()
| (uv_lock ? MutableQuadView.BAKE_LOCK_UV : 0)
);
quad.tag(i+1);
quad.emit();
return i;
}
// verify if sprite covers the current quad and apply the new size
QuadPosBounds origin_bounds = QuadPosBounds.read(quad, false);
if (!bounds.matches(origin_bounds)) return i;
// apply new quad shape
quad.material(ta.getRenderMaterial(ao));
bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds);
quad.spriteBake( // seems to work without the flags and break with it
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| MutableQuadView.BAKE_LOCK_UV
);
quad.tag(i+1);
quad.emit();
return i;
}
}
protected static class TintingTransformer implements RenderContext.QuadTransform { protected static class TintingTransformer implements RenderContext.QuadTransform {
private final long seed; private final CamoAppearance appearance;
protected TintingTransformer(CamoAppearance ta, int tint, long seed) { private final int model_id;
this.ta = ta; private final int tint;
protected TintingTransformer(CamoAppearance appearance, int model_id, int tint) {
this.appearance = appearance;
this.model_id = model_id;
this.tint = tint; this.tint = tint;
this.seed = seed;
} }
protected final CamoAppearance ta;
protected final int tint;
@Override @Override
public boolean transform(MutableQuadView quad) { public boolean transform(MutableQuadView quad) {
if(quad.tag() == 0) return true; int camo_quad_index = quad.tag() - ((quad.tag() >>> 8) << 8);
if(camo_quad_index == 0) return true;
if(appearance.hasColor(quad.nominalFace(), model_id, camo_quad_index)) quad.color(tint, tint, tint, tint);
if(ta.hasColor(quad.nominalFace(), seed, quad.tag())) quad.color(tint, tint, tint, tint);
return true; return true;
} }
} }

View File

@ -43,7 +43,7 @@ public class UnbakedAutoRetexturedModel extends UnbakedRetexturedModel {
Renderer r = ReFramedClient.HELPER.getFabricRenderer(); Renderer r = ReFramedClient.HELPER.getFabricRenderer();
MeshBuilder builder = r.meshBuilder(); MeshBuilder builder = r.meshBuilder();
QuadEmitter emitter = builder.getEmitter(); QuadEmitter emitter = builder.getEmitter();
RenderMaterial mat = tam.getCachedMaterial(state, false); RenderMaterial mat = appearance_manager.getCachedMaterial(state, false);
Random rand = Random.create(42); Random rand = Random.create(42);

View File

@ -51,7 +51,7 @@ public class UnbakedJsonRetexturedModel extends UnbakedRetexturedModel {
Renderer r = ReFramedClient.HELPER.getFabricRenderer(); Renderer r = ReFramedClient.HELPER.getFabricRenderer();
MeshBuilder builder = r.meshBuilder(); MeshBuilder builder = r.meshBuilder();
QuadEmitter emitter = builder.getEmitter(); QuadEmitter emitter = builder.getEmitter();
RenderMaterial mat = tam.getCachedMaterial(state, false); RenderMaterial mat = appearance_manager.getCachedMaterial(state, false);
Random rand = Random.create(42); Random rand = Random.create(42);

View File

@ -14,7 +14,7 @@ public abstract class UnbakedRetexturedModel implements UnbakedModel {
protected int theme_index = 1; protected int theme_index = 1;
protected BlockState item_state; protected BlockState item_state;
protected boolean ao = true; protected final boolean ao = true;
public UnbakedRetexturedModel(Identifier parent) { public UnbakedRetexturedModel(Identifier parent) {
this.parent = parent; this.parent = parent;

View File

@ -1,13 +1,80 @@
package fr.adrien1106.reframed.client.model.apperance; package fr.adrien1106.reframed.client.model.apperance;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public interface CamoAppearance { @Environment(EnvType.CLIENT)
@NotNull RenderMaterial getRenderMaterial(boolean ao); public abstract class CamoAppearance {
@NotNull List<SpriteProperties> getSprites(Direction dir, long seed); protected final int id;
boolean hasColor(Direction dir, long seed, int index); protected final RenderMaterial ao_material;
protected final RenderMaterial material;
protected CamoAppearance(RenderMaterial ao_material, RenderMaterial material, int id) {
this.id = id;
this.ao_material = ao_material;
this.material = material;
}
public abstract @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id);
public abstract boolean hasColor(Direction dir, int model_id, int index);
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return ao && ao_material != null? ao_material : material;
}
public int transformQuad(QuadEmitter quad, int i, int quad_index, int model_id, boolean ao, boolean uv_lock) {
if(quad.tag() == 0) return 0; // Pass the quad through unmodified.
Direction direction = quad.nominalFace();
List<SpriteProperties> sprites = getSprites(direction, model_id);
if (i == -1) i = sprites.size();
SpriteProperties properties = sprites.get(sprites.size() - i);
int tag = i + (quad_index << 8);
i--;
QuadPosBounds bounds = properties.bounds();
if (bounds == null) { // sprite applies anywhere e.g. default behaviour
quad.material(getRenderMaterial(ao));
quad.spriteBake(
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| properties.flags()
| (uv_lock ? MutableQuadView.BAKE_LOCK_UV : 0)
);
quad.tag(tag);
quad.emit();
return i;
}
// verify if sprite covers the current quad and apply the new size
QuadPosBounds origin_bounds = QuadPosBounds.read(quad, false);
if (!bounds.matches(origin_bounds)) return i;
// apply new quad shape
quad.material(getRenderMaterial(ao));
bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds);
quad.spriteBake( // seems to work without the flags and break with it
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| MutableQuadView.BAKE_LOCK_UV
);
quad.tag(tag);
quad.emit();
return i;
}
@Override
public int hashCode() {
return id;
}
} }

View File

@ -1,10 +1,14 @@
package fr.adrien1106.reframed.client.model.apperance; package fr.adrien1106.reframed.client.model.apperance;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.client.ReFramedClient; import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.DynamicBakedModel; import fr.adrien1106.reframed.client.model.DynamicBakedModel;
import fr.adrien1106.reframed.client.model.QuadPosBounds; import fr.adrien1106.reframed.client.model.QuadPosBounds;
import fr.adrien1106.reframed.mixin.model.WeightedBakedModelAccessor; import fr.adrien1106.reframed.mixin.model.WeightedBakedModelAccessor;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
@ -29,10 +33,10 @@ import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
@Environment(EnvType.CLIENT)
public class CamoAppearanceManager { public class CamoAppearanceManager {
public CamoAppearanceManager(Function<SpriteIdentifier, Sprite> spriteLookup) { public CamoAppearanceManager(Function<SpriteIdentifier, Sprite> spriteLookup) {
@ -40,20 +44,20 @@ public class CamoAppearanceManager {
for(BlendMode blend : BlendMode.values()) { for(BlendMode blend : BlendMode.values()) {
finder.clear().disableDiffuse(false).blendMode(blend); finder.clear().disableDiffuse(false).blendMode(blend);
materialsWithoutAo.put(blend, finder.ambientOcclusion(TriState.FALSE).find()); materials.put(blend, finder.ambientOcclusion(TriState.FALSE).find());
materialsWithAo.put(blend, finder.ambientOcclusion(TriState.DEFAULT).find()); //not "true" since that *forces* AO, i just want to *allow* AO ao_materials.put(blend, finder.ambientOcclusion(TriState.DEFAULT).find()); //not "true" since that *forces* AO, i just want to *allow* AO
} }
Sprite sprite = spriteLookup.apply(DEFAULT_SPRITE_MAIN); Sprite sprite = spriteLookup.apply(DEFAULT_SPRITE_MAIN);
if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !"); if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.default_appearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.get(BlendMode.CUTOUT), serial_number.getAndIncrement()); this.default_appearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
sprite = spriteLookup.apply(DEFAULT_SPRITE_SECONDARY); sprite = spriteLookup.apply(DEFAULT_SPRITE_SECONDARY);
if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !"); if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.accent_appearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.get(BlendMode.CUTOUT), serial_number.getAndIncrement()); this.accent_appearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
sprite = spriteLookup.apply(BARRIER_SPRITE_ID); sprite = spriteLookup.apply(BARRIER_SPRITE_ID);
this.barrierItemAppearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.get(BlendMode.CUTOUT), serial_number.getAndIncrement()); this.barrierItemAppearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
} }
protected static final SpriteIdentifier DEFAULT_SPRITE_MAIN = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_block")); protected static final SpriteIdentifier DEFAULT_SPRITE_MAIN = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_block"));
@ -63,29 +67,41 @@ public class CamoAppearanceManager {
private final CamoAppearance default_appearance; private final CamoAppearance default_appearance;
private final CamoAppearance accent_appearance; private final CamoAppearance accent_appearance;
private final CamoAppearance barrierItemAppearance; private final CamoAppearance barrierItemAppearance;
private final ConcurrentHashMap<BlockState, CamoAppearance> appearanceCache = new ConcurrentHashMap<>(); //Mutable, append-only cache private static final Cache<BlockState, CamoAppearance> APPEARANCE_CACHE = CacheBuilder.newBuilder().maximumSize(2048).build();
private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable
private final EnumMap<BlendMode, RenderMaterial> materialsWithAo = new EnumMap<>(BlendMode.class); private final EnumMap<BlendMode, RenderMaterial> ao_materials = new EnumMap<>(BlendMode.class);
private final EnumMap<BlendMode, RenderMaterial> materialsWithoutAo = new EnumMap<>(BlendMode.class); //Immutable contents private final EnumMap<BlendMode, RenderMaterial> materials = new EnumMap<>(BlendMode.class); //Immutable contents
public CamoAppearance getDefaultAppearance(int appearance) { public CamoAppearance getDefaultAppearance(int appearance) {
return appearance == 2 ? accent_appearance: default_appearance; return appearance == 2 ? accent_appearance: default_appearance;
} }
public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos, int theme_index) { public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos, int theme_index, boolean item) {
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state); BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
// add support for connected textures and more generally any compatible models injected so that they return baked quads // add support for connected textures and more generally any compatible models injected so that they return baked quads
if (model instanceof DynamicBakedModel dynamic_model) { if (model instanceof DynamicBakedModel dynamic_model) {
return computeAppearance(dynamic_model.computeQuads(world, state, pos, theme_index), state); // cache items as they get rendered more often
if (item && APPEARANCE_CACHE.asMap().containsKey(state)) return APPEARANCE_CACHE.getIfPresent(state);
CamoAppearance appearance = computeAppearance(dynamic_model.computeQuads(world, state, pos, theme_index), state);
if (item) APPEARANCE_CACHE.put(state, appearance);
return appearance;
} }
return appearanceCache.computeIfAbsent(state, block_state -> computeAppearance(model, block_state));
// refresh cache
if (APPEARANCE_CACHE.asMap().containsKey(state)) return APPEARANCE_CACHE.getIfPresent(state);
CamoAppearance appearance = computeAppearance(model, state);
APPEARANCE_CACHE.put(state, appearance);
return appearance;
} }
public RenderMaterial getCachedMaterial(BlockState state, boolean ao) { public RenderMaterial getCachedMaterial(BlockState state, boolean ao) {
Map<BlendMode, RenderMaterial> m = ao ? materialsWithAo : materialsWithoutAo; Map<BlendMode, RenderMaterial> m = ao ? ao_materials : materials;
return m.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state))); return m.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)));
} }

View File

@ -1,38 +1,30 @@
package fr.adrien1106.reframed.client.model.apperance; package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class ComputedAppearance implements CamoAppearance { @Environment(EnvType.CLIENT)
public class ComputedAppearance extends CamoAppearance {
private final Appearance appearance; private final Appearance appearance;
private final int id;
private final RenderMaterial matWithAo;
private final RenderMaterial matWithoutAo;
public ComputedAppearance(@NotNull Appearance appearance, RenderMaterial withAo, RenderMaterial withoutAo, int id) { public ComputedAppearance(@NotNull Appearance appearance, RenderMaterial ao_material, RenderMaterial material, int id) {
super(ao_material, material, id);
this.appearance = appearance; this.appearance = appearance;
this.id = id;
this.matWithAo = withAo;
this.matWithoutAo = withoutAo;
} }
@Override @Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) { public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return ao ? matWithAo : matWithoutAo;
}
@Override
public @NotNull List<SpriteProperties> getSprites(Direction dir, long seed) {
return appearance.sprites().get(dir); return appearance.sprites().get(dir);
} }
@Override @Override
public boolean hasColor(Direction dir, long seed, int index) { public boolean hasColor(Direction dir, int model_id, int index) {
List<SpriteProperties> properties = getSprites(dir, seed); List<SpriteProperties> properties = getSprites(dir, model_id);
if (index != 0) index = properties.size() - index; if (index != 0) index = properties.size() - index;
return properties.get(index).has_colors(); return properties.get(index).has_colors();
} }
@ -40,13 +32,7 @@ public class ComputedAppearance implements CamoAppearance {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if(this == o) return true; if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false; if(!(o instanceof ComputedAppearance that)) return false;
ComputedAppearance that = (ComputedAppearance) o;
return id == that.id; return id == that.id;
} }
@Override
public int hashCode() {
return id;
}
} }

View File

@ -1,5 +1,7 @@
package fr.adrien1106.reframed.client.model.apperance; package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
@ -7,47 +9,34 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class SingleSpriteAppearance implements CamoAppearance { @Environment(EnvType.CLIENT)
public class SingleSpriteAppearance extends CamoAppearance {
private final @NotNull Sprite defaultSprite; private final @NotNull Sprite defaultSprite;
private final RenderMaterial mat;
private final int id;
public SingleSpriteAppearance(@NotNull Sprite defaultSprite, RenderMaterial mat, int id) { public SingleSpriteAppearance(@NotNull Sprite defaultSprite, RenderMaterial mat, int id) {
super(null, mat, id);
this.defaultSprite = defaultSprite; this.defaultSprite = defaultSprite;
this.mat = mat;
this.id = id;
} }
@Override @Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) { public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return mat;
}
@Override
public @NotNull List<SpriteProperties> getSprites(Direction dir, long seed) {
return List.of(new SpriteProperties(defaultSprite, 0, null, false)); return List.of(new SpriteProperties(defaultSprite, 0, null, false));
} }
@Override @Override
public boolean hasColor(Direction dir, long seed, int index) { public boolean hasColor(Direction dir, int model_id, int index) {
return false; return false;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if(this == o) return true; if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false; if(!(o instanceof SingleSpriteAppearance that)) return false;
SingleSpriteAppearance that = (SingleSpriteAppearance) o;
return id == that.id; return id == that.id;
} }
@Override
public int hashCode() {
return id;
}
@Override @Override
public String toString() { public String toString() {
return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, mat, id); return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, material, id);
} }
} }

View File

@ -1,5 +1,7 @@
package fr.adrien1106.reframed.client.model.apperance; package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.util.collection.Weighted; import net.minecraft.util.collection.Weighted;
import net.minecraft.util.collection.Weighting; import net.minecraft.util.collection.Weighting;
@ -9,25 +11,15 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
public class WeightedComputedAppearance implements CamoAppearance { @Environment(EnvType.CLIENT)
public class WeightedComputedAppearance extends CamoAppearance {
private final List<Weighted.Present<Appearance>> appearances; private final List<Weighted.Present<Appearance>> appearances;
private final int total_weight; private final int total_weight;
private final int id;
private final RenderMaterial matWithAo;
private final RenderMaterial matWithoutAo;
public WeightedComputedAppearance(@NotNull List<Weighted.Present<Appearance>> appearances, RenderMaterial withAo, RenderMaterial withoutAo, int id) { public WeightedComputedAppearance(@NotNull List<Weighted.Present<Appearance>> appearances, RenderMaterial ao_material, RenderMaterial material, int id) {
super(ao_material, material, id);
this.appearances = appearances; this.appearances = appearances;
this.total_weight = Weighting.getWeightSum(appearances); this.total_weight = Weighting.getWeightSum(appearances);
this.id = id;
this.matWithAo = withAo;
this.matWithoutAo = withoutAo;
}
@Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return ao ? matWithAo : matWithoutAo;
} }
@ -37,21 +29,19 @@ public class WeightedComputedAppearance implements CamoAppearance {
.map(appearances::indexOf).orElse(0); .map(appearances::indexOf).orElse(0);
} }
private Appearance getAppearance(long seed) { private Appearance getAppearance(int model_id) {
Random random = Random.create(seed); return appearances.get(model_id).getData();
return Weighting.getAt(appearances, Math.abs((int)random.nextLong()) % total_weight)
.map(Weighted.Present::getData).get();
} }
@Override @Override
public @NotNull List<SpriteProperties> getSprites(Direction dir, long seed) { public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return getAppearance(seed).sprites().get(dir); return getAppearance(model_id).sprites().get(dir);
} }
@Override @Override
public boolean hasColor(Direction dir, long seed, int index) { public boolean hasColor(Direction dir, int model_id, int index) {
List<SpriteProperties> properties = getSprites(dir, seed); List<SpriteProperties> properties = getSprites(dir, model_id);
if (index != 0) index = properties.size() - index; if (index != 0) index = properties.size() - index;
return properties.get(index).has_colors(); return properties.get(index).has_colors();
} }
@ -59,13 +49,7 @@ public class WeightedComputedAppearance implements CamoAppearance {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if(this == o) return true; if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false; if(!(o instanceof WeightedComputedAppearance that)) return false;
WeightedComputedAppearance that = (WeightedComputedAppearance) o;
return id == that.id; return id == that.id;
} }
@Override
public int hashCode() {
return id;
}
} }

View File

@ -17,9 +17,7 @@ public class GLanguage extends FabricLanguageProvider {
public void generateTranslations(TranslationBuilder builder) { public void generateTranslations(TranslationBuilder builder) {
builder.add(Registries.ITEM_GROUP.getKey(ReFramed.ITEM_GROUP).get(), "Frames"); builder.add(Registries.ITEM_GROUP.getKey(ReFramed.ITEM_GROUP).get(), "Frames");
builder.add("advancements.reframed.description", "Get all the frame types."); builder.add("advancements.reframed.description", "Get all the frame types.");
ReFramed.BLOCKS.forEach(block -> { ReFramed.BLOCKS.forEach(block -> builder.add(block, beautify(Registries.BLOCK.getId(block).getPath()) + " Frame"));
builder.add(block, beautify(Registries.BLOCK.getId(block).getPath()) + " Frame");
});
} }
private static String beautify(String name) { private static String beautify(String name) {

View File

@ -11,9 +11,7 @@ import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtHelper; import net.minecraft.nbt.NbtHelper;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@ -23,8 +21,6 @@ import static fr.adrien1106.reframed.block.ReFramedEntity.BLOCKSTATE_KEY;
@Mixin(BlockItem.class) @Mixin(BlockItem.class)
public class BlockItemMixin { public class BlockItemMixin {
@Shadow @Final @Deprecated private Block block;
@Inject( @Inject(
method = "writeNbtToBlockEntity", method = "writeNbtToBlockEntity",
at = @At( at = @At(
@ -41,6 +37,7 @@ public class BlockItemMixin {
|| !Block.isShapeFullCube(block.getBlock().getDefaultState().getCollisionShape(world, pos)) || !Block.isShapeFullCube(block.getBlock().getDefaultState().getCollisionShape(world, pos))
) return; ) return;
NbtCompound new_comp = new NbtCompound(); NbtCompound new_comp = new NbtCompound();
player.getOffHandStack().decrement(1);
new_comp.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(block.getBlock().getDefaultState())); new_comp.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(block.getBlock().getDefaultState()));
compound.set(new_comp); compound.set(new_comp);
} }

View File

@ -21,9 +21,11 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
"fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)), "fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
"fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)), "fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
"fr.adrien1106.reframed.mixin.render.TerrainRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)), "fr.adrien1106.reframed.mixin.render.TerrainRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.render.BlockRenderInfoMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)), "fr.adrien1106.reframed.mixin.render.BlockRenderInfoMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.render.AbstractBlockRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainBlockRenderInfoMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)), "fr.adrien1106.reframed.mixin.compat.IndiumTerrainBlockRenderInfoMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumAbstractBlockRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.SodiumBlockOcclusionCacheMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(2)) "fr.adrien1106.reframed.mixin.compat.SodiumBlockOcclusionCacheMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(2))
); );

View File

@ -6,7 +6,7 @@ import earth.terrarium.athena.api.client.models.AthenaBlockModel;
import fr.adrien1106.reframed.client.ReFramedClient; import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.DynamicBakedModel; import fr.adrien1106.reframed.client.model.DynamicBakedModel;
import fr.adrien1106.reframed.compat.RebakedAthenaModel; import fr.adrien1106.reframed.compat.RebakedAthenaModel;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
@ -56,8 +56,7 @@ public abstract class AthenaBakedModelMixin implements DynamicBakedModel, BakedM
level.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity level.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity
? framed_entity.getTheme(theme_index) ? framed_entity.getTheme(theme_index)
: state, pos, direction) : state, pos, direction)
) ).forEach(sprite -> face_quads.computeIfPresent(direction, (d, quads) -> {
.forEach(sprite -> face_quads.computeIfPresent(direction, (d, quads) -> {
Sprite texture = textures.get(sprite.sprite()); Sprite texture = textures.get(sprite.sprite());
if (texture == null) return quads; if (texture == null) return quads;
emitter.square(direction, sprite.left(), sprite.bottom(), sprite.right(), sprite.top(), sprite.depth()); emitter.square(direction, sprite.left(), sprite.bottom(), sprite.right(), sprite.top(), sprite.depth());

View File

@ -3,7 +3,7 @@ package fr.adrien1106.reframed.mixin.compat;
import earth.terrarium.athena.api.client.utils.AppearanceAndTintGetter; import earth.terrarium.athena.api.client.utils.AppearanceAndTintGetter;
import earth.terrarium.athena.api.client.utils.CtmUtils; import earth.terrarium.athena.api.client.utils.CtmUtils;
import earth.terrarium.athena.impl.client.models.ConnectedBlockModel; import earth.terrarium.athena.impl.client.models.ConnectedBlockModel;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import earth.terrarium.athena.api.client.fabric.WrappedGetter; import earth.terrarium.athena.api.client.fabric.WrappedGetter;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;

View File

@ -0,0 +1,29 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import link.infra.indium.renderer.mesh.MutableQuadViewImpl;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
import link.infra.indium.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(AbstractBlockRenderContext.class)
public abstract class IndiumAbstractBlockRenderContextMixin {
@Shadow(remap = false) protected BlockRenderInfo blockInfo;
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Redirect(method = "renderQuad", at = @At(value = "INVOKE", target = "Llink/infra/indium/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"))
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}

View File

@ -1,8 +1,8 @@
package fr.adrien1106.reframed.mixin.compat; package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin; import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import link.infra.indium.renderer.render.BlockRenderInfo; import link.infra.indium.renderer.render.BlockRenderInfo;
import link.infra.indium.renderer.render.TerrainBlockRenderInfo; import link.infra.indium.renderer.render.TerrainBlockRenderInfo;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache; import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache;
@ -18,7 +18,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TerrainBlockRenderInfo.class) @Mixin(TerrainBlockRenderInfo.class)
public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo implements IBlockRenderInfoMixin { public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo implements IBlockRenderInfoMixin {
@Unique private int theme_index = 1; @Unique private int theme_index = 0;
@Redirect( @Redirect(
method = "shouldDrawFaceInner", method = "shouldDrawFaceInner",
@ -40,4 +40,9 @@ public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo
this.theme_index = theme_index; this.theme_index = theme_index;
prepareForBlock(blockState, blockPos, seed, modelAo); prepareForBlock(blockState, blockPos, seed, modelAo);
} }
@Override
public int getThemeIndex() {
return theme_index;
}
} }

View File

@ -1,11 +1,13 @@
package fr.adrien1106.reframed.mixin.compat; package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel; import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.IMultipartBakedModelMixin; import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import link.infra.indium.renderer.render.AbstractBlockRenderContext; import link.infra.indium.renderer.render.AbstractBlockRenderContext;
import link.infra.indium.renderer.render.TerrainRenderContext; import link.infra.indium.renderer.render.TerrainRenderContext;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderContext; import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderContext;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedModel;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -29,13 +31,14 @@ public abstract class IndiumTerrainRenderContextMixin extends AbstractBlockRende
if (!(ctx.model() instanceof IMultipartBakedModelMixin wrapped) if (!(ctx.model() instanceof IMultipartBakedModelMixin wrapped)
|| !(wrapped.getModel(ctx.state()) instanceof MultiRetexturableModel retexturing_model)) return; || !(wrapped.getModel(ctx.state()) instanceof MultiRetexturableModel retexturing_model)) return;
List<BakedModel> models = retexturing_model.models(); List<ForwardingBakedModel> models = retexturing_model.models();
BlockHelper.computeInnerCull(ctx.state(), models);
int i = 0; int i = 0;
for (BakedModel bakedModel : models) { for (BakedModel model : models) {
i++; i++;
aoCalc.clear(); aoCalc.clear();
((IBlockRenderInfoMixin) blockInfo).prepareForBlock(ctx.state(), ctx.pos(), ctx.seed(), bakedModel.useAmbientOcclusion(), i); ((IBlockRenderInfoMixin) blockInfo).prepareForBlock(ctx.state(), ctx.pos(), ctx.seed(), model.useAmbientOcclusion(), i);
bakedModel.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this); model.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
} }
ci.cancel(); ci.cancel();
} }

View File

@ -1,8 +1,8 @@
package fr.adrien1106.reframed.mixin.compat; package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache; import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;

View File

@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.particles; package fr.adrien1106.reframed.mixin.particles;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.particle.BlockDustParticle; import net.minecraft.client.particle.BlockDustParticle;

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.particles;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.particles;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;

View File

@ -0,0 +1,36 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(AbstractBlockRenderContext.class)
public abstract class AbstractBlockRenderContextMixin {
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Shadow(remap = false) @Final protected BlockRenderInfo blockInfo;
@Redirect(
method = "renderQuad",
at = @At(
value = "INVOKE",
target = "Lnet/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"
)
)
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}

View File

@ -1,9 +1,9 @@
package fr.adrien1106.reframed.mixin.render; package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.BlockHelper; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin; import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo; import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
@ -24,8 +24,7 @@ public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
@Shadow public abstract void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo); @Shadow public abstract void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo);
@Shadow public BlockRenderView blockView; @Shadow public BlockRenderView blockView;
@Unique @Unique private int theme_index = 0;
private int theme_index = 1;
@ModifyArg(method = "prepareForBlock", @ModifyArg(method = "prepareForBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" +
@ -47,4 +46,9 @@ public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
this.theme_index = theme_index; this.theme_index = theme_index;
prepareForBlock(state, pos, ao); prepareForBlock(state, pos, ao);
} }
@Override
public int getThemeIndex() {
return theme_index;
}
} }

View File

@ -1,6 +1,6 @@
package fr.adrien1106.reframed.mixin.render; package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.util.IMultipartBakedModelMixin; import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.MultipartBakedModel; import net.minecraft.client.render.model.MultipartBakedModel;

View File

@ -1,8 +1,10 @@
package fr.adrien1106.reframed.mixin.render; package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel; import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin; import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.IMultipartBakedModelMixin; import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext; import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.TerrainRenderContext; import net.fabricmc.fabric.impl.client.indigo.renderer.render.TerrainRenderContext;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -28,13 +30,14 @@ public abstract class TerrainRenderContextMixin extends AbstractBlockRenderConte
if (!(wrapper instanceof IMultipartBakedModelMixin wrapped) if (!(wrapper instanceof IMultipartBakedModelMixin wrapped)
|| !(wrapped.getModel(state) instanceof MultiRetexturableModel retexturing_model)) return; || !(wrapped.getModel(state) instanceof MultiRetexturableModel retexturing_model)) return;
List<BakedModel> models = retexturing_model.models(); List<ForwardingBakedModel> models = retexturing_model.models();
BlockHelper.computeInnerCull(state, models);
int i = 0; int i = 0;
for (BakedModel bakedModel : models) { for (BakedModel model : models) {
i++; i++;
aoCalc.clear(); aoCalc.clear();
((IBlockRenderInfoMixin) blockInfo).prepareForBlock(state, pos, bakedModel.useAmbientOcclusion(), i); ((IBlockRenderInfoMixin) blockInfo).prepareForBlock(state, pos, model.useAmbientOcclusion(), i);
bakedModel.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this); model.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
} }
ci.cancel(); ci.cancel();
} }

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItem;

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;

View File

@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.sound; package fr.adrien1106.reframed.mixin.sound;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;

View File

@ -2,7 +2,7 @@ package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.render.WorldRenderer;

View File

@ -5,68 +5,201 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes; import net.minecraft.util.shape.VoxelShapes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import static net.minecraft.util.shape.VoxelShapes.*;
public class VoxelHelper { public class VoxelHelper {
/* ---------------------------------------- Methods for VoxelListBuilder ---------------------------------------- */
public static VoxelShape rotateX(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.X);
}
public static VoxelShape rotateY(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.Y);
}
public static VoxelShape rotateZ(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.Z);
}
public static VoxelShape rotateCX(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.X);
}
public static VoxelShape rotateCY(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.Y);
}
public static VoxelShape rotateCZ(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.Z);
}
public static VoxelShape mirrorX(VoxelShape shape) {
return mirror(shape, Direction.Axis.X);
}
public static VoxelShape mirrorY(VoxelShape shape) {
return mirror(shape, Direction.Axis.Y);
}
public static VoxelShape mirrorZ(VoxelShape shape) {
return mirror(shape, Direction.Axis.Z);
}
public static VoxelShape rotateClockwise(VoxelShape shape, Direction.Axis axis) { public static VoxelShape rotateClockwise(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() }; AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
switch (axis) { shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
case Y: new_shape.getAndUpdate(s ->
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxZ, minY, minX, 1 - minZ, maxY, maxX), BooleanBiFunction.OR)); combineAndSimplify(
break; s,
case X: switch (axis) {
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, 1 - maxZ, minY, maxX, 1 - minZ, maxY), BooleanBiFunction.OR)); case Y -> cuboid(1 - maxZ, minY, minX, 1 - minZ, maxY, maxX);
break; case X -> cuboid(minX, 1 - maxZ, minY, maxX, 1 - minZ, maxY);
case Z: case Z -> cuboid(1 - maxY, minX, minZ, 1 - minY, maxX, maxZ);
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxY, minX, minZ, 1 - minY, maxX, maxZ), BooleanBiFunction.OR)); },
break; BooleanBiFunction.OR
} )
return buffer[1]; )
);
return new_shape.get();
} }
public static VoxelShape rotateCounterClockwise(VoxelShape shape, Direction.Axis axis) { public static VoxelShape rotateCounterClockwise(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() }; AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
switch (axis) { shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
case Y: new_shape.getAndUpdate(s ->
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minZ, minY, 1 - maxX, maxZ, maxY, 1 - minX), BooleanBiFunction.OR)); combineAndSimplify(
break; s,
case X: switch (axis) {
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minZ, 1 - maxY, maxX, maxZ, 1 - minY), BooleanBiFunction.OR)); case Y -> cuboid(minZ, minY, 1 - maxX, maxZ, maxY, 1 - minX);
break; case X -> cuboid(minX, minZ, 1 - maxY, maxX, maxZ, 1 - minY);
case Z: case Z -> cuboid(minY, 1 - maxX, minZ, maxY, 1 - minX, maxZ);
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minY, 1 - maxX, minZ, maxY, 1 - minX, maxZ), BooleanBiFunction.OR)); },
break; BooleanBiFunction.OR
} )
return buffer[1]; )
);
return new_shape.get();
} }
public static VoxelShape mirror(VoxelShape shape, Direction.Axis axis) { public static VoxelShape mirror(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() }; AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
switch (axis) { shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
case Y: new_shape.getAndUpdate(s ->
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, 1 - maxY, minZ, maxX, 1 - minY, maxZ), BooleanBiFunction.OR)); combineAndSimplify(
break; s,
case X: switch (axis) {
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxX, minY, minZ, 1 - minX, maxY, maxZ), BooleanBiFunction.OR)); case Y -> cuboid(minX, 1 - maxY, minZ, maxX, 1 - minY, maxZ);
break; case X -> cuboid(1 - maxX, minY, minZ, 1 - minX, maxY, maxZ);
case Z: case Z -> cuboid(minX, minY, 1 - maxZ, maxX, maxY, 1 - minZ);
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minY, 1 - maxZ, maxX, maxY, 1 - minZ), BooleanBiFunction.OR)); },
break; BooleanBiFunction.OR
} )
return buffer[1]; )
);
return new_shape.get();
} }
public static VoxelShape offset(VoxelShape shape, Direction.Axis axis, float offset) { public static VoxelShape offset(VoxelShape shape, Direction.Axis axis, float offset) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() }; AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
switch (axis) { shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
case Y: new_shape.getAndUpdate(s ->
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, offset + minY, minZ, maxX, offset + maxY, maxZ), BooleanBiFunction.OR)); combineAndSimplify(
break; s,
case X: switch (axis) {
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( offset + minX, minY, minZ, offset + maxX, maxY, maxZ), BooleanBiFunction.OR)); case Y -> cuboid(minX, offset + minY, minZ, maxX, offset + maxY, maxZ);
break; case X -> cuboid(offset + minX, minY, minZ, offset + maxX, maxY, maxZ);
case Z: case Z -> cuboid(minX, minY, offset + minZ, maxX, maxY, offset + maxZ);
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minY, offset + minZ, maxX, maxY, offset + maxZ), BooleanBiFunction.OR)); },
break; BooleanBiFunction.OR
)
)
);
return new_shape.get();
}
public static class VoxelListBuilder {
private final List<VoxelShape> voxels;
/**
* A simple class that helps to get cleaner shape list generation (Hopefully)
* @param base_shape - the shape to start with
* @param size - the amount of shapes expected
*/
private VoxelListBuilder(VoxelShape base_shape, int size) {
voxels = new ArrayList<>(size);
voxels.add(base_shape);
}
public static VoxelListBuilder create(VoxelShape base_shape, int size) {
return new VoxelListBuilder(base_shape, size);
}
/**
* Add shape by applying modification to previous shape
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(Function<VoxelShape, VoxelShape>... modifications) {
return add(voxels.size() - 1, modifications);
}
/**
* Add shape by applying modifications to given shape
* @param ref - the index of the reference shape
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(int ref, Function<VoxelShape, VoxelShape>... modifications) {
return add(voxels.get(ref), modifications);
}
/**
* Add shape by applying modifications to given shape
* @param ref - the shape you want to add and apply modifications if present
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(VoxelShape ref, Function<VoxelShape, VoxelShape>... modifications) {
for(Function<VoxelShape, VoxelShape> modif: modifications) {
ref = modif.apply(ref);
}
voxels.add(ref);
return this;
}
/**
* @return the final array of voxel shapes
*/
public VoxelShape[] build() {
return voxels.toArray(new VoxelShape[0]);
}
/**
* build a new set of voxels based on th negation of the references and a full cube
* @param ref_voxels - the reference to subtract from the wanted shape
* @return the array of complementary voxels
*/
public static VoxelShape[] buildFrom(VoxelShape[] ref_voxels) {
return buildFrom(VoxelShapes.fullCube(), ref_voxels);
}
/**
* build a new set of voxels based on th negation of the references and a wanted_shape
* @param ref_voxels - the reference to subtract from the wanted shape
* @return the array of complementary voxels
*/
public static VoxelShape[] buildFrom(VoxelShape wanted_shape, VoxelShape[] ref_voxels) {
VoxelShape[] shapes = new VoxelShape[ref_voxels.length];
for (int i = 0; i < shapes.length; i++) {
shapes[i] = VoxelShapes.combineAndSimplify(wanted_shape, ref_voxels[i], BooleanBiFunction.ONLY_FIRST);
}
return shapes;
} }
return buffer[1];
} }
} }

View File

@ -1,9 +1,19 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.blocks;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.adrien1106.reframed.block.ReFramedBlock; import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.block.ReFramedEntity; import fr.adrien1106.reframed.block.ReFramedEntity;
import fr.adrien1106.reframed.util.property.Corner; import fr.adrien1106.reframed.block.ReFramedStairBlock;
import fr.adrien1106.reframed.util.property.StairShape; import fr.adrien1106.reframed.block.ReFramedStairsCubeBlock;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -14,42 +24,56 @@ import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Pair;
import net.minecraft.util.function.BooleanBiFunction; import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes; import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.BlockProperties.CORNER; import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.BlockProperties.LIGHT; import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
import static fr.adrien1106.reframed.util.property.StairShape.*; import static fr.adrien1106.reframed.util.blocks.StairShape.*;
import static net.minecraft.util.shape.VoxelShapes.combine; import static net.minecraft.util.shape.VoxelShapes.combine;
public class BlockHelper { public class BlockHelper {
// self culling cache of the models not made thread local so that it is only computed once
private static final Cache<CullElement, Integer[]> INNER_CULL_MAP = CacheBuilder.newBuilder().maximumSize(1024).build();
private record CullElement(Block block, Object state_key, int model) {}
public static Corner getPlacementCorner(ItemPlacementContext ctx) { public static Corner getPlacementCorner(ItemPlacementContext ctx) {
Direction side = ctx.getSide().getOpposite();
Vec3d pos = getHitPos(ctx.getHitPos(), ctx.getBlockPos());
Pair<Direction, Direction> sides = getHitSides(pos, side);
return Corner.getByDirections(side, sides.getLeft(), sides.getRight());
}
private static Pair<Direction, Direction> getHitSides(Vec3d pos, Direction side) {
Iterator<Direction.Axis> axes = Stream.of(Direction.Axis.values())
.filter(axis -> !axis.equals(side.getAxis())).iterator();
return new Pair<>(getHitDirection(axes.next(), pos), getHitDirection(axes.next(), pos));
}
public static Edge getPlacementEdge(ItemPlacementContext ctx) {
Direction side = ctx.getSide().getOpposite(); Direction side = ctx.getSide().getOpposite();
Vec3d pos = getHitPos(ctx.getHitPos(), ctx.getBlockPos()); Vec3d pos = getHitPos(ctx.getHitPos(), ctx.getBlockPos());
Direction.Axis axis = getHitAxis(pos, side); Direction.Axis axis = getHitAxis(pos, side);
Direction part_direction = Direction.from( Direction part_direction = getHitDirection(axis, pos);
axis,
axis.choose(pos.x, pos.y, pos.z) > 0
? Direction.AxisDirection.POSITIVE
: Direction.AxisDirection.NEGATIVE
);
return Corner.getByDirections(side, part_direction); return Edge.getByDirections(side, part_direction);
} }
public static Direction.Axis getHitAxis(Vec3d pos, Direction side) { public static Direction.Axis getHitAxis(Vec3d pos, Direction side) {
@ -58,7 +82,16 @@ public class BlockHelper {
Math.abs(axis_1.choose(pos.x, pos.y, pos.z)) > Math.abs(axis_2.choose(pos.x, pos.y, pos.z)) Math.abs(axis_1.choose(pos.x, pos.y, pos.z)) > Math.abs(axis_2.choose(pos.x, pos.y, pos.z))
? axis_1 ? axis_1
: axis_2 : axis_2
).get(); ).orElse(null);
}
public static Direction getHitDirection(Direction.Axis axis, Vec3d pos) {
return Direction.from(
axis,
axis.choose(pos.x, pos.y, pos.z) > 0
? Direction.AxisDirection.POSITIVE
: Direction.AxisDirection.NEGATIVE
);
} }
public static Vec3d getRelativePos(Vec3d pos, BlockPos block_pos) { public static Vec3d getRelativePos(Vec3d pos, BlockPos block_pos) {
@ -78,28 +111,28 @@ public class BlockHelper {
); );
} }
public static StairShape getStairsShape(Block block, Corner face, BlockView world, BlockPos pos) { public static StairShape getStairsShape(Edge face, BlockView world, BlockPos pos) {
StairShape shape = STRAIGHT; StairShape shape = STRAIGHT;
String sol = getNeighborPos(face, face.getFirstDirection(), true, face.getSecondDirection(), world, pos, block); String sol = getNeighborPos(face, face.getFirstDirection(), true, face.getSecondDirection(), world, pos);
switch (sol) { switch (sol) {
case "right": return INNER_RIGHT; case "right": return INNER_RIGHT;
case "left": return INNER_LEFT; case "left": return INNER_LEFT;
} }
sol = getNeighborPos(face, face.getSecondDirection(), true, face.getFirstDirection(), world, pos, block); sol = getNeighborPos(face, face.getSecondDirection(), true, face.getFirstDirection(), world, pos);
switch (sol) { switch (sol) {
case "right": return INNER_RIGHT; case "right": return INNER_RIGHT;
case "left": return INNER_LEFT; case "left": return INNER_LEFT;
} }
sol = getNeighborPos(face, face.getFirstDirection(), false, face.getSecondDirection(), world, pos, block); sol = getNeighborPos(face, face.getFirstDirection(), false, face.getSecondDirection(), world, pos);
switch (sol) { switch (sol) {
case "right" -> shape = FIRST_OUTER_RIGHT; case "right" -> shape = FIRST_OUTER_RIGHT;
case "left" -> shape = FIRST_OUTER_LEFT; case "left" -> shape = FIRST_OUTER_LEFT;
} }
sol = getNeighborPos(face, face.getSecondDirection(), false, face.getFirstDirection(), world, pos, block); sol = getNeighborPos(face, face.getSecondDirection(), false, face.getFirstDirection(), world, pos);
switch (sol) { switch (sol) {
case "right" -> { case "right" -> {
if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_RIGHT; if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_RIGHT;
@ -114,18 +147,23 @@ public class BlockHelper {
return shape; return shape;
} }
public static String getNeighborPos(Corner face, Direction direction, Boolean reverse, Direction reference, BlockView world, BlockPos pos, Block block) { public static String getNeighborPos(Edge face, Direction direction, Boolean reverse, Direction reference, BlockView world, BlockPos pos) {
BlockState block_state = world.getBlockState( BlockState block_state = world.getBlockState(
pos.offset(reverse ? direction.getOpposite() : direction) pos.offset(reverse ? direction.getOpposite() : direction)
); );
if (block_state.isOf(block) && block_state.get(CORNER).hasDirection(reference)) { if (isStair(block_state) && block_state.get(EDGE).hasDirection(reference)) {
if (block_state.get(CORNER).hasDirection(face.getLeftDirection())) return "left"; if (block_state.get(EDGE).hasDirection(face.getLeftDirection())) return "left";
else if (block_state.get(CORNER).hasDirection(face.getRightDirection())) return "right"; else if (block_state.get(EDGE).hasDirection(face.getRightDirection())) return "right";
} }
return ""; return "";
} }
public static boolean isStair(BlockState state) {
return state.getBlock() instanceof ReFramedStairBlock
|| state.getBlock() instanceof ReFramedStairsCubeBlock;
}
public static ActionResult useCamo(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit, int theme_index) { public static ActionResult useCamo(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit, int theme_index) {
if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS; if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS;
@ -141,17 +179,18 @@ public class BlockHelper {
// check for default light emission // check for default light emission
if (placement_state.getLuminance() > 0 if (placement_state.getLuminance() > 0
&& themes.stream().noneMatch(theme -> theme.getLuminance() > 0)) && themes.stream().noneMatch(theme -> theme.getLuminance() > 0)
if (block_entity.emitsLight()) Block.dropStack(world, pos, new ItemStack(Items.GLOWSTONE_DUST)); && !block_entity.emitsLight()
else block_entity.toggleLight(); )
block_entity.toggleLight();
world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight())); world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight()));
// check for default redstone emission // check for default redstone emission
if (placement_state.getWeakRedstonePower(world, pos, Direction.NORTH) > 0 if (placement_state.getWeakRedstonePower(world, pos, Direction.NORTH) > 0
&& themes.stream().noneMatch(theme -> theme.getWeakRedstonePower(world, pos, Direction.NORTH) > 0)) && themes.stream().noneMatch(theme -> theme.getWeakRedstonePower(world, pos, Direction.NORTH) > 0)
if (block_entity.emitsRedstone()) Block.dropStack(world, pos, new ItemStack(Items.GLOWSTONE_DUST)); && !block_entity.emitsRedstone()
else block_entity.toggleRedstone(); ) block_entity.toggleRedstone();
if(!player.isCreative()) held.decrement(1); if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, placement_state.getSoundGroup().getPlaceSound(), SoundCategory.BLOCKS, 1f, 1.1f); world.playSound(player, pos, placement_state.getSoundGroup().getPlaceSound(), SoundCategory.BLOCKS, 1f, 1.1f);
@ -172,10 +211,6 @@ public class BlockHelper {
if(state.contains(LIGHT) && held.getItem() == Items.GLOWSTONE_DUST) { if(state.contains(LIGHT) && held.getItem() == Items.GLOWSTONE_DUST) {
block_entity.toggleLight(); block_entity.toggleLight();
world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight())); world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight()));
if(!player.isCreative())
if (block_entity.emitsLight()) held.decrement(1);
else held.increment(1);
world.playSound(player, pos, SoundEvents.BLOCK_GLASS_HIT, SoundCategory.BLOCKS, 1f, 1f); world.playSound(player, pos, SoundEvents.BLOCK_GLASS_HIT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} }
@ -183,10 +218,6 @@ public class BlockHelper {
// frame will emit redstone if applied with redstone torch can deactivate redstone block camo emission // frame will emit redstone if applied with redstone torch can deactivate redstone block camo emission
if(held.getItem() == Items.REDSTONE_TORCH && ext.canAddRedstoneEmission(state, world, pos)) { if(held.getItem() == Items.REDSTONE_TORCH && ext.canAddRedstoneEmission(state, world, pos)) {
block_entity.toggleRedstone(); block_entity.toggleRedstone();
if(!player.isCreative())
if (block_entity.emitsRedstone()) held.decrement(1);
else held.increment(1);
world.playSound(player, pos, SoundEvents.BLOCK_LEVER_CLICK, SoundCategory.BLOCKS, 1f, 1f); world.playSound(player, pos, SoundEvents.BLOCK_LEVER_CLICK, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} }
@ -194,10 +225,6 @@ public class BlockHelper {
// Frame will lose its collision if applied with popped chorus fruit // Frame will lose its collision if applied with popped chorus fruit
if(held.getItem() == Items.POPPED_CHORUS_FRUIT && ext.canRemoveCollision(state, world, pos)) { if(held.getItem() == Items.POPPED_CHORUS_FRUIT && ext.canRemoveCollision(state, world, pos)) {
block_entity.toggleSolidity(); block_entity.toggleSolidity();
if(!player.isCreative())
if (!block_entity.isSolid()) held.decrement(1);
else held.increment(1);
world.playSound(player, pos, SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, SoundCategory.BLOCKS, 1f, 1f); world.playSound(player, pos, SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} }
@ -205,7 +232,70 @@ public class BlockHelper {
return ActionResult.PASS; return ActionResult.PASS;
} }
/**
* compute which quad might cull with another model quad
* @param state - the state of the model
* @param models - list of models on the same block
*/
@Environment(EnvType.CLIENT)
public static void computeInnerCull(BlockState state, List<ForwardingBakedModel> models) {
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
Object key = frame_block.getModelCacheKey(state);
if (INNER_CULL_MAP.asMap().containsKey(new CullElement(frame_block, key, 1))) return;
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
RenderMaterial material = r.materialFinder().clear().find();
Random random = Random.create();
List<List<QuadPosBounds>> model_bounds = models.stream()
.map(ForwardingBakedModel::getWrappedModel)
.filter(Objects::nonNull)
.map(wrapped -> wrapped.getQuads(state, null, random))
.map(quads -> quads.stream().map(quad -> {
quad_emitter.fromVanilla(quad, material, null);
return QuadPosBounds.read(quad_emitter, false);
}).toList()).toList();
Integer[] cull_array;
for(int self_id = 1; self_id <= model_bounds.size(); self_id++) {
List<QuadPosBounds> self_bounds = model_bounds.get(self_id - 1);
cull_array = new Integer[self_bounds.size()];
for (int self_quad = 0; self_quad < cull_array.length; self_quad++) {
QuadPosBounds self_bound = self_bounds.get(self_quad);
for(int other_id = 1; other_id <= model_bounds.size(); other_id++) {
if (other_id == self_id) continue;
if (model_bounds.get(other_id - 1).stream().anyMatch(other_bound -> other_bound.equals(self_bound))) {
cull_array[self_quad] = other_id;
break;
}
}
}
INNER_CULL_MAP.put(new CullElement(frame_block, key, self_id), cull_array);
}
}
@Environment(EnvType.CLIENT)
public static boolean shouldDrawInnerFace(BlockState state, BlockRenderView view, BlockPos pos, int quad_index, int theme_index) {
if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
|| !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
) return true;
CullElement key = new CullElement(frame_block, frame_block.getModelCacheKey(state), theme_index);
if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
// needs to be Integer object because array is initialized with null not 0
Integer cull_theme = Objects.requireNonNull(INNER_CULL_MAP.getIfPresent(key))[quad_index];
if (cull_theme == null) return true; // no culling possible
BlockState self_theme = frame_entity.getTheme(theme_index);
BlockState other_theme = frame_entity.getTheme(cull_theme);
if (self_theme.isSideInvisible(other_theme, null)) return false;
return !self_theme.isOpaque() || !other_theme.isOpaque();
}
// Doing this method from scratch as it is simpler to do than injecting everywhere // Doing this method from scratch as it is simpler to do than injecting everywhere
@Environment(EnvType.CLIENT)
public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) { public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) {
ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null; ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null;
ThemeableBlockEntity other = world.getBlockEntity(other_pos) instanceof ThemeableBlockEntity e ? e : null; ThemeableBlockEntity other = world.getBlockEntity(other_pos) instanceof ThemeableBlockEntity e ? e : null;
@ -298,8 +388,4 @@ public class BlockHelper {
.reduce((prev, current) -> prev && current).orElse(false) .reduce((prev, current) -> prev && current).orElse(false)
); );
} }
public static int luminance(BlockState state) {
return state.contains(LIGHT) && state.get(LIGHT) ? 15 : 0;
}
} }

View File

@ -1,12 +1,13 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.blocks;
import fr.adrien1106.reframed.util.property.Corner;
import fr.adrien1106.reframed.util.property.StairShape;
import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.IntProperty;
public class BlockProperties { public class BlockProperties {
public static final BooleanProperty LIGHT = BooleanProperty.of("emits_light"); public static final BooleanProperty LIGHT = BooleanProperty.of("emits_light");
public static final EnumProperty<Edge> EDGE = EnumProperty.of("edge", Edge.class);
public static final EnumProperty<Corner> CORNER = EnumProperty.of("corner", Corner.class); public static final EnumProperty<Corner> CORNER = EnumProperty.of("corner", Corner.class);
public static final IntProperty CORNER_FACE = IntProperty.of("face", 0, 2);
public static final EnumProperty<StairShape> STAIR_SHAPE = EnumProperty.of("shape", StairShape.class); public static final EnumProperty<StairShape> STAIR_SHAPE = EnumProperty.of("shape", StairShape.class);
} }

View File

@ -0,0 +1,97 @@
package fr.adrien1106.reframed.util.blocks;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.Direction;
import java.util.Arrays;
public enum Corner implements StringIdentifiable {
NORTH_EAST_DOWN("north_east_down", Direction.NORTH, Direction.EAST, Direction.DOWN, 0),
EAST_SOUTH_DOWN("east_south_down", Direction.EAST, Direction.SOUTH, Direction.DOWN, 1),
SOUTH_WEST_DOWN("south_west_down", Direction.SOUTH, Direction.WEST, Direction.DOWN, 2),
WEST_NORTH_DOWN("west_north_down", Direction.WEST, Direction.NORTH, Direction.DOWN, 3),
NORTH_EAST_UP("north_east_up", Direction.NORTH, Direction.EAST, Direction.UP, 4),
EAST_SOUTH_UP("east_south_up", Direction.EAST, Direction.SOUTH, Direction.UP, 5),
SOUTH_WEST_UP("south_west_up", Direction.SOUTH, Direction.WEST, Direction.UP, 6),
WEST_NORTH_UP("west_north_up", Direction.WEST, Direction.NORTH, Direction.UP, 7);
private final String name;
private final Direction first_direction;
private final Direction second_direction;
private final Direction third_direction;
private final int ID;
Corner(String name, Direction first_direction, Direction second_direction, Direction third_direction, int id) {
this.name = name;
this.first_direction = first_direction;
this.second_direction = second_direction;
this.third_direction = third_direction;
this.ID = id;
}
public String asString() {
return this.name;
}
public String toString() {
return asString();
}
public boolean hasDirection(Direction direction) {
return this.first_direction.equals(direction)
|| this.second_direction.equals(direction)
|| this.third_direction.equals(direction);
}
public int getID() {
return this.ID;
}
public static Corner getByDirections(Direction direction_1, Direction direction_2, Direction direction_3) {
return Arrays.stream(Corner.values())
.filter(value -> value.hasDirection(direction_1) && value.hasDirection(direction_2) && value.hasDirection(direction_3))
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public static Corner fromId(int id) {
return Arrays.stream(Corner.values())
.filter(value -> value.getID() == id)
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public static Corner fromName(String name) {
return Arrays.stream(Corner.values())
.filter(value -> value.name().equals(name))
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public int getDirectionIndex(Direction side) {
return side == second_direction ? 1 : side == third_direction ? 2 : 0;
}
public Direction getDirection(int index) {
return index == 1 ? second_direction : index == 2 ? third_direction : first_direction;
}
public Corner getOpposite() {
return getByDirections(first_direction.getOpposite(), second_direction.getOpposite(), third_direction.getOpposite());
}
/**
* @param index - an index of the direction to keep and use as reference
* @return the opposite corner on same direction plane
*/
public Corner getOpposite(int index) {
return getOpposite(getDirection(index));
}
/**
* @param direction - a direction to keep and use as reference
* @return the opposite corner on same direction plane
*/
public Corner getOpposite(Direction direction) {
Direction other_1 = first_direction == direction ? second_direction : first_direction;
Direction other_2 = second_direction == direction || first_direction == direction ? third_direction : second_direction;
return getByDirections(direction, other_1.getOpposite(), other_2.getOpposite());
}
}

View File

@ -1,37 +1,35 @@
package fr.adrien1106.reframed.util.property; package fr.adrien1106.reframed.util.blocks;
import net.minecraft.util.StringIdentifiable; import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import java.util.Arrays; import java.util.Arrays;
public enum Corner implements StringIdentifiable { public enum Edge implements StringIdentifiable {
NORTH_DOWN("north_down", Direction.NORTH, Direction.DOWN, Direction.EAST, 0), NORTH_DOWN("north_down", Direction.NORTH, Direction.DOWN, Direction.Axis.X, 0),
DOWN_SOUTH("down_south", Direction.DOWN, Direction.SOUTH, Direction.EAST, 1), DOWN_SOUTH("down_south", Direction.DOWN, Direction.SOUTH, Direction.Axis.X, 1),
SOUTH_UP("south_up", Direction.SOUTH, Direction.UP, Direction.EAST, 2), SOUTH_UP("south_up", Direction.SOUTH, Direction.UP, Direction.Axis.X, 2),
UP_NORTH("up_north", Direction.UP, Direction.NORTH, Direction.EAST, 3), UP_NORTH("up_north", Direction.UP, Direction.NORTH, Direction.Axis.X, 3),
WEST_DOWN("west_down", Direction.WEST, Direction.DOWN, Direction.SOUTH, 4), WEST_DOWN("west_down", Direction.WEST, Direction.DOWN, Direction.Axis.Z, 4),
DOWN_EAST("down_east", Direction.DOWN, Direction.EAST, Direction.SOUTH, 5), DOWN_EAST("down_east", Direction.DOWN, Direction.EAST, Direction.Axis.Z, 5),
EAST_UP("east_up", Direction.EAST, Direction.UP, Direction.SOUTH, 6), EAST_UP("east_up", Direction.EAST, Direction.UP, Direction.Axis.Z, 6),
UP_WEST("up_west", Direction.UP, Direction.WEST, Direction.SOUTH, 7), UP_WEST("up_west", Direction.UP, Direction.WEST, Direction.Axis.Z, 7),
WEST_NORTH("west_north", Direction.WEST, Direction.NORTH, Direction.DOWN, 8), WEST_NORTH("west_north", Direction.WEST, Direction.NORTH, Direction.Axis.Y, 8),
NORTH_EAST("north_east", Direction.NORTH, Direction.EAST, Direction.DOWN, 9), NORTH_EAST("north_east", Direction.NORTH, Direction.EAST, Direction.Axis.Y, 9),
EAST_SOUTH("east_south", Direction.EAST, Direction.SOUTH, Direction.DOWN, 10), EAST_SOUTH("east_south", Direction.EAST, Direction.SOUTH, Direction.Axis.Y, 10),
SOUTH_WEST("south_west", Direction.SOUTH, Direction.WEST, Direction.DOWN, 11); SOUTH_WEST("south_west", Direction.SOUTH, Direction.WEST, Direction.Axis.Y, 11);
private final String name; private final String name;
private final Direction first_direction; private final Direction first_direction;
private final Direction second_direction; private final Direction second_direction;
private final Direction right_direction; private final Direction.Axis axis;
private final Direction left_direction;
private final int ID; private final int ID;
Corner(String name, Direction first_direction, Direction second_direction, Direction right_direction, int id) { Edge(String name, Direction first_direction, Direction second_direction, Direction.Axis axis, int id) {
this.name = name; this.name = name;
this.first_direction = first_direction; this.first_direction = first_direction;
this.second_direction = second_direction; this.second_direction = second_direction;
this.right_direction = right_direction; this.axis = axis;
this.left_direction = right_direction.getOpposite();
this.ID = id; this.ID = id;
} }
@ -51,10 +49,18 @@ public enum Corner implements StringIdentifiable {
return second_direction; return second_direction;
} }
public Direction getRightDirection() { public Direction getRightDirection() {
return right_direction; return switch (axis) {
case X -> Direction.WEST;
case Y -> Direction.DOWN;
case Z -> Direction.SOUTH;
};
} }
public Direction getLeftDirection() { public Direction getLeftDirection() {
return left_direction; return switch (axis) {
case X -> Direction.EAST;
case Y -> Direction.UP;
case Z -> Direction.NORTH;
};
} }
public boolean hasDirection(Direction direction) { public boolean hasDirection(Direction direction) {
@ -66,21 +72,21 @@ public enum Corner implements StringIdentifiable {
return this.ID; return this.ID;
} }
public static Corner getByDirections(Direction direction_1, Direction direction_2) { public static Edge getByDirections(Direction direction_1, Direction direction_2) {
return Arrays.stream(Corner.values()) return Arrays.stream(Edge.values())
.filter(value -> value.hasDirection(direction_1) && value.hasDirection(direction_2)) .filter(value -> value.hasDirection(direction_1) && value.hasDirection(direction_2))
.findFirst().orElse(Corner.NORTH_DOWN); .findFirst().orElse(Edge.NORTH_DOWN);
} }
public static Corner fromId(int id) { public static Edge fromId(int id) {
return Arrays.stream(Corner.values()) return Arrays.stream(Edge.values())
.filter(value -> value.getID() == id) .filter(value -> value.getID() == id)
.findFirst().orElse(Corner.NORTH_DOWN); .findFirst().orElse(Edge.NORTH_DOWN);
} }
public static Corner fromName(String name) { public static Edge fromName(String name) {
return Arrays.stream(Corner.values()) return Arrays.stream(Edge.values())
.filter(value -> value.name().equals(name)) .filter(value -> value.name().equals(name))
.findFirst().orElse(Corner.NORTH_DOWN); .findFirst().orElse(Edge.NORTH_DOWN);
} }
} }

View File

@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.blocks;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;

View File

@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util.property; package fr.adrien1106.reframed.util.blocks;
import net.minecraft.util.StringIdentifiable; import net.minecraft.util.StringIdentifiable;

View File

@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.blocks;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;

View File

@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.mixin;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@ -8,4 +8,6 @@ public interface IBlockRenderInfoMixin {
void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index); void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index);
void prepareForBlock(BlockState state, BlockPos pos, long seed, boolean ao, int theme_index); void prepareForBlock(BlockState state, BlockPos pos, long seed, boolean ao, int theme_index);
int getThemeIndex();
} }

View File

@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util; package fr.adrien1106.reframed.util.mixin;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedModel;

View File

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 0, 8],
"to": [16, 8, 16],
"faces": {
"east": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 8, 16, 16], "texture": "#side"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top"},
"down": {"uv": [8, 0, 16, 8], "texture": "#bottom", "cullface": "down"}
}
},
{
"from": [8, 0, 0],
"to": [16, 8, 8],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "east"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top"},
"down": {"uv": [8, 8, 16, 16], "texture": "#bottom", "cullface": "down"}
}
},
{
"from": [0, 0, 0],
"to": [8, 8, 8],
"faces": {
"north": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "north"},
"south": {"uv": [0, 8, 8, 16], "texture": "#side"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#top"},
"down": {"uv": [0, 8, 8, 16], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 8, 0],
"to": [16, 16, 8],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 0, 16, 8], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 0, 16, 8], "texture": "#side"},
"west": {"uv": [0, 0, 8, 8], "texture": "#side"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top", "cullface": "up"}
}
},
{
"from": [8, 0, 0],
"to": [16, 8, 8],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "east"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side"},
"down": {"uv": [8, 8, 16, 16], "texture": "#bottom", "cullface": "down"}
}
},
{
"from": [8, 0, 8],
"to": [16, 8, 16],
"faces": {
"east": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 8, 16, 16], "texture": "#side"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top"},
"down": {"uv": [8, 0, 16, 8], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,35 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [0, 0, 8],
"to": [8, 8, 16],
"faces": {
"north": {"uv": [8, 8, 16, 16], "texture": "#side"},
"east": {"uv": [0, 8, 8, 16], "texture": "#side"},
"south": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "west"},
"up": {"uv": [0, 8, 8, 16], "texture": "#top"},
"down": {"uv": [0, 0, 8, 8], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,35 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 8, 8],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#side"},
"east": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 0, 16, 8], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 0, 16, 8], "texture": "#side"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top", "cullface": "up"},
"down": {"uv": [8, 0, 16, 8], "texture": "#bottom"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 8, 8],
"to": [16, 16, 16],
"faces": {
"east": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 0, 16, 8], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 0, 16, 8], "texture": "#side"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top", "cullface": "up"},
"down": {"uv": [8, 0, 16, 8], "texture": "#bottom"}
}
},
{
"from": [8, 8, 0],
"to": [16, 16, 8],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 0, 16, 8], "texture": "#side", "cullface": "east"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top", "cullface": "up"},
"down": {"uv": [8, 8, 16, 16], "texture": "#bottom"}
}
},
{
"from": [0, 8, 0],
"to": [8, 16, 8],
"faces": {
"north": {"uv": [8, 0, 16, 8], "texture": "#side", "cullface": "north"},
"south": {"uv": [0, 0, 8, 8], "texture": "#side"},
"west": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#top", "cullface": "up"},
"down": {"uv": [0, 8, 8, 16], "texture": "#bottom"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [0, 8, 0],
"to": [8, 16, 8],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 0, 16, 8], "texture": "#side"},
"south": {"uv": [0, 0, 8, 8], "texture": "#side"},
"west": {"uv": [0, 0, 8, 8], "texture": "#side", "cullface": "west"},
"up": {"uv": [0, 0, 8, 8], "texture": "#top", "cullface": "up"}
}
},
{
"from": [0, 0, 0],
"to": [8, 8, 8],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "west"},
"down": {"uv": [0, 8, 8, 16], "texture": "#bottom", "cullface": "down"}
}
},
{
"from": [0, 0, 8],
"to": [8, 8, 16],
"faces": {
"east": {"uv": [0, 8, 8, 16], "texture": "#side"},
"south": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "south"},
"west": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "west"},
"up": {"uv": [0, 8, 8, 16], "texture": "#top"},
"down": {"uv": [0, 0, 8, 8], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,35 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 0, 0],
"to": [16, 8, 8],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side", "cullface": "north"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 8, 16, 16], "texture": "#side"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top"},
"down": {"uv": [8, 8, 16, 16], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -0,0 +1,35 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"particle": "#side"
},
"elements": [
{
"from": [8, 0, 8],
"to": [16, 8, 16],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "east"},
"south": {"uv": [8, 8, 16, 16], "texture": "#side", "cullface": "south"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top"},
"down": {"uv": [8, 8, 16, 16], "texture": "#bottom", "cullface": "down"}
}
}
],
"display": {
"thirdperson_lefthand": {
"rotation": [75, -135, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"gui": {
"rotation": [30, 135, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"rotation": [0, -90, 0]
}
}
}

View File

@ -30,7 +30,7 @@
"fabricloader": "^${loader_version}", "fabricloader": "^${loader_version}",
"fabric-api": "*" "fabric-api": "*"
}, },
"suggest": { "suggests": {
"athena": "^${athena_version}", "athena": "^${athena_version}",
"sodium": "^${sodium_version}", "sodium": "^${sodium_version}",
"indium": "^${indium_version}" "indium": "^${indium_version}"

View File

@ -14,6 +14,7 @@
"compat.AthenaBakedModelMixin", "compat.AthenaBakedModelMixin",
"compat.AthenaConnectedBlockModelMixin", "compat.AthenaConnectedBlockModelMixin",
"compat.AthenaWrappedGetterMixin", "compat.AthenaWrappedGetterMixin",
"compat.IndiumAbstractBlockRenderContextMixin",
"compat.IndiumTerrainBlockRenderInfoMixin", "compat.IndiumTerrainBlockRenderInfoMixin",
"compat.IndiumTerrainRenderContextMixin", "compat.IndiumTerrainRenderContextMixin",
"compat.SodiumBlockOcclusionCacheMixin", "compat.SodiumBlockOcclusionCacheMixin",
@ -21,6 +22,7 @@
"particles.AccessorParticle", "particles.AccessorParticle",
"particles.AccessorSpriteBillboardParticle", "particles.AccessorSpriteBillboardParticle",
"particles.MixinBlockDustParticle", "particles.MixinBlockDustParticle",
"render.AbstractBlockRenderContextMixin",
"render.BlockRenderInfoMixin", "render.BlockRenderInfoMixin",
"render.MultipartBakedModelMixin", "render.MultipartBakedModelMixin",
"render.TerrainRenderContextMixin", "render.TerrainRenderContextMixin",