v1.5 new shapes + self culling + caching + coding tools/cleanup #10
@ -30,7 +30,7 @@ import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
|
|||||||
* TODO add Hammer from framed ( removes theme ) for sure
|
* TODO add Hammer from framed ( removes theme ) for sure
|
||||||
* TODO add screwdriver ( iterate over theme states ) ?
|
* TODO add screwdriver ( iterate over theme states ) ?
|
||||||
* TODO add blueprint for survival friendly copy paste of a theme.
|
* TODO add blueprint for survival friendly copy paste of a theme.
|
||||||
* TODO fix other models ( + half stair )
|
* TODO fix other models ( + half stair + layers )
|
||||||
* TODO get better naming for the shapes (will break a lot of already placed blocks)
|
* TODO get better naming for the shapes (will break a lot of already placed blocks)
|
||||||
* TODO put more coherence in the double theme orders / directions
|
* TODO put more coherence in the double theme orders / directions
|
||||||
* TODO better connected textures
|
* TODO better connected textures
|
||||||
|
@ -28,6 +28,7 @@ import static fr.adrien1106.reframed.block.ReFramedStepBlock.getStepShape;
|
|||||||
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
|
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
|
||||||
import static fr.adrien1106.reframed.util.blocks.Edge.*;
|
import static fr.adrien1106.reframed.util.blocks.Edge.*;
|
||||||
import static net.minecraft.data.client.VariantSettings.Rotation.*;
|
import static net.minecraft.data.client.VariantSettings.Rotation.*;
|
||||||
|
import static net.minecraft.util.shape.VoxelShapes.empty;
|
||||||
|
|
||||||
public class ReFramedDoubleSmallBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
|
public class ReFramedDoubleSmallBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
|
||||||
|
|
||||||
@ -57,22 +58,27 @@ public class ReFramedDoubleSmallBlock extends WaterloggableReFramedDoubleBlock i
|
|||||||
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(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
|
@Override
|
||||||
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
|
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
|
||||||
return getStepShape(state.get(EDGE));
|
return getStepShape(state.get(EDGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // TODO
|
@Override
|
||||||
public VoxelShape getShape(BlockState state, int i) {
|
public VoxelShape getShape(BlockState state, int i) {
|
||||||
return switch (state.get(EDGE)) {
|
return switch (state.get(EDGE)) {
|
||||||
case NORTH_DOWN -> SMALL_CUBE_VOXELS.get(i == 1 ? 0 : 3);
|
case NORTH_DOWN -> SMALL_CUBE_VOXELS.get(i == 1 ? 3 : 0);
|
||||||
case DOWN_SOUTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 1 : 2);
|
case DOWN_SOUTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 1 : 2);
|
||||||
case SOUTH_UP -> SMALL_CUBE_VOXELS.get(i == 1 ? 5 : 6);
|
case SOUTH_UP -> SMALL_CUBE_VOXELS.get(i == 1 ? 6 : 5);
|
||||||
case UP_NORTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 4 : 7);
|
case UP_NORTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 4 : 7);
|
||||||
case WEST_DOWN -> SMALL_CUBE_VOXELS.get(i == 1 ? 2 : 3);
|
case WEST_DOWN -> SMALL_CUBE_VOXELS.get(i == 1 ? 2 : 3);
|
||||||
case DOWN_EAST -> SMALL_CUBE_VOXELS.get(i == 1 ? 0 : 1);
|
case DOWN_EAST -> SMALL_CUBE_VOXELS.get(i == 1 ? 0 : 1);
|
||||||
case EAST_UP -> SMALL_CUBE_VOXELS.get(i == 1 ? 4 : 5);
|
case EAST_UP -> SMALL_CUBE_VOXELS.get(i == 1 ? 5 : 4);
|
||||||
case UP_WEST -> SMALL_CUBE_VOXELS.get(i == 1 ? 6 : 7);
|
case UP_WEST -> SMALL_CUBE_VOXELS.get(i == 1 ? 7 : 6);
|
||||||
case WEST_NORTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 3 : 7);
|
case WEST_NORTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 3 : 7);
|
||||||
case NORTH_EAST -> SMALL_CUBE_VOXELS.get(i == 1 ? 0 : 4);
|
case NORTH_EAST -> SMALL_CUBE_VOXELS.get(i == 1 ? 0 : 4);
|
||||||
case EAST_SOUTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 1 : 5);
|
case EAST_SOUTH -> SMALL_CUBE_VOXELS.get(i == 1 ? 1 : 5);
|
||||||
@ -82,10 +88,10 @@ public class ReFramedDoubleSmallBlock extends WaterloggableReFramedDoubleBlock i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getTopThemeIndex(BlockState state) {
|
public int getTopThemeIndex(BlockState state) {
|
||||||
return super.getTopThemeIndex(state); // TODO
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // TODO
|
@Override
|
||||||
public BlockStateSupplier getMultipart() {
|
public BlockStateSupplier getMultipart() {
|
||||||
Identifier small_cube_id = ReFramed.id("double_small_cube_special");
|
Identifier small_cube_id = ReFramed.id("double_small_cube_special");
|
||||||
return MultipartBlockStateSupplier.create(this)
|
return MultipartBlockStateSupplier.create(this)
|
||||||
|
@ -108,7 +108,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
|
|||||||
}
|
}
|
||||||
if(theme.getBlock() == Blocks.BARRIER) return;
|
if(theme.getBlock() == Blocks.BARRIER) return;
|
||||||
|
|
||||||
CamoAppearance camo = appearance_manager.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);
|
||||||
@ -135,7 +135,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
|
|||||||
int tint;
|
int tint;
|
||||||
BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index);
|
BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index);
|
||||||
if(!theme.isAir()) {
|
if(!theme.isAir()) {
|
||||||
appearance = appearance_manager.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 {
|
||||||
appearance = appearance_manager.getDefaultAppearance(theme_index);
|
appearance = appearance_manager.getDefaultAppearance(theme_index);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
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 it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
|
||||||
import net.fabricmc.api.EnvType;
|
import net.fabricmc.api.EnvType;
|
||||||
import net.fabricmc.api.Environment;
|
import net.fabricmc.api.Environment;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
||||||
@ -67,11 +68,8 @@ public class CamoAppearanceManager {
|
|||||||
private final CamoAppearance accent_appearance;
|
private final CamoAppearance accent_appearance;
|
||||||
private final CamoAppearance barrierItemAppearance;
|
private final CamoAppearance barrierItemAppearance;
|
||||||
|
|
||||||
private static final Object2ObjectLinkedOpenHashMap<BlockState, CamoAppearance> APPEARANCE_CACHE =
|
private static final Cache<BlockState, CamoAppearance> APPEARANCE_CACHE = CacheBuilder.newBuilder().maximumSize(2048).build();
|
||||||
new Object2ObjectLinkedOpenHashMap<>(2048, 0.25f) {
|
|
||||||
@Override
|
|
||||||
protected void rehash(int n) {}
|
|
||||||
};
|
|
||||||
private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable
|
private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable
|
||||||
|
|
||||||
private final EnumMap<BlendMode, RenderMaterial> ao_materials = new EnumMap<>(BlendMode.class);
|
private final EnumMap<BlendMode, RenderMaterial> ao_materials = new EnumMap<>(BlendMode.class);
|
||||||
@ -81,19 +79,24 @@ public class CamoAppearanceManager {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh cache
|
// refresh cache
|
||||||
if (APPEARANCE_CACHE.containsKey(state)) return APPEARANCE_CACHE.getAndMoveToFirst(state);
|
if (APPEARANCE_CACHE.asMap().containsKey(state)) return APPEARANCE_CACHE.getIfPresent(state);
|
||||||
|
|
||||||
CamoAppearance appearance = computeAppearance(model, state);
|
CamoAppearance appearance = computeAppearance(model, state);
|
||||||
APPEARANCE_CACHE.putAndMoveToFirst(state, appearance);
|
APPEARANCE_CACHE.put(state, appearance);
|
||||||
return appearance;
|
return appearance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -48,7 +48,7 @@ public class BlockHelper {
|
|||||||
|
|
||||||
// self culling cache of the models not made thread local so that it is only computed once
|
// 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 static final Cache<CullElement, Integer[]> INNER_CULL_MAP = CacheBuilder.newBuilder().maximumSize(1024).build();
|
||||||
private record CullElement(Object state_key, int model) {}
|
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();
|
Direction side = ctx.getSide().getOpposite();
|
||||||
@ -172,17 +172,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);
|
||||||
@ -203,10 +204,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;
|
||||||
}
|
}
|
||||||
@ -214,10 +211,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;
|
||||||
}
|
}
|
||||||
@ -225,10 +218,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;
|
||||||
}
|
}
|
||||||
@ -245,7 +234,7 @@ public class BlockHelper {
|
|||||||
public static void computeInnerCull(BlockState state, List<ForwardingBakedModel> models) {
|
public static void computeInnerCull(BlockState state, List<ForwardingBakedModel> models) {
|
||||||
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
|
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
|
||||||
Object key = frame_block.getModelCacheKey(state);
|
Object key = frame_block.getModelCacheKey(state);
|
||||||
if (INNER_CULL_MAP.asMap().containsKey(new CullElement(key, 1))) return;
|
if (INNER_CULL_MAP.asMap().containsKey(new CullElement(frame_block, key, 1))) return;
|
||||||
|
|
||||||
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
|
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
|
||||||
QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
|
QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
|
||||||
@ -275,7 +264,7 @@ public class BlockHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
INNER_CULL_MAP.put(new CullElement(key, self_id), cull_array);
|
INNER_CULL_MAP.put(new CullElement(frame_block, key, self_id), cull_array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +273,7 @@ public class BlockHelper {
|
|||||||
if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
|
if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
|
||||||
|| !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
|
|| !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
|
||||||
) return true;
|
) return true;
|
||||||
CullElement key = new CullElement(frame_block.getModelCacheKey(state), theme_index);
|
CullElement key = new CullElement(frame_block, frame_block.getModelCacheKey(state), theme_index);
|
||||||
if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
|
if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
|
||||||
|
|
||||||
// needs to be Integer object because array is initialized with null not 0
|
// needs to be Integer object because array is initialized with null not 0
|
||||||
|
Loading…
Reference in New Issue
Block a user