diff --git a/xplat/src/main/java/gay/pridecraft/joy/block/BlahajBlocks.java b/xplat/src/main/java/gay/pridecraft/joy/block/BlahajBlocks.java index 90a127a..6beb369 100644 --- a/xplat/src/main/java/gay/pridecraft/joy/block/BlahajBlocks.java +++ b/xplat/src/main/java/gay/pridecraft/joy/block/BlahajBlocks.java @@ -8,18 +8,21 @@ import net.minecraft.block.Blocks; import net.minecraft.item.Item; import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; import java.util.ArrayList; import java.util.List; +import java.util.Map; public final class BlahajBlocks { public static final Block GRAY_SHARK_BLOCK = mkBlock("gray_shark", Blocks.LIGHT_GRAY_WOOL), BLAHAJ_BLOCK = mkBlock("blue_shark", Blocks.CYAN_WOOL), - BLAVINGAD_BLOCK = mkBlock("blue_whale", Blocks.BLUE_WOOL), - BREAD_BLOCK = mkBlock("bread", Blocks.ORANGE_WOOL), - BROWN_BEAR_BLOCK = mkBlock("brown_bear", Blocks.BROWN_WOOL); + BLAVINGAD_BLOCK = mkBlock("blue_whale", Blocks.BLUE_WOOL, CuddlyBlock.WHALE_SHAPES), + BREAD_BLOCK = mkBlock("bread", Blocks.ORANGE_WOOL, CuddlyBlock.BREAD_SHAPES), + BROWN_BEAR_BLOCK = mkBlock("brown_bear", Blocks.BROWN_WOOL, CuddlyBlock.BEAR_SHAPES); public static final Item GRAY_SHARK_ITEM = mkItem(GRAY_SHARK_BLOCK), @@ -62,6 +65,10 @@ public final class BlahajBlocks { } */ } + private static Block mkBlock(String id, Block block, Map shapes) { + return register(id, new CuddlyBlock(AbstractBlock.Settings.copy(block), shapes)); + } + private static Block mkBlock(String id, Block block) { return register(id, new CuddlyBlock(AbstractBlock.Settings.copy(block))); } diff --git a/xplat/src/main/java/gay/pridecraft/joy/block/CuddlyBlock.java b/xplat/src/main/java/gay/pridecraft/joy/block/CuddlyBlock.java index 3345579..874f816 100644 --- a/xplat/src/main/java/gay/pridecraft/joy/block/CuddlyBlock.java +++ b/xplat/src/main/java/gay/pridecraft/joy/block/CuddlyBlock.java @@ -1,6 +1,7 @@ package gay.pridecraft.joy.block; import com.mojang.serialization.MapCodec; +import gay.pridecraft.joy.util.VoxelEmitter; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.HorizontalFacingBlock; @@ -12,18 +13,128 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.shape.VoxelShape; import net.minecraft.world.BlockView; -public class CuddlyBlock extends HorizontalFacingBlock { - protected static final VoxelShape SHAPE = Block.createCuboidShape( - 4.0, 0.0, 4.0, - 12.0, 8.0, 12.0 - ); +import java.util.Map; - public static final MapCodec CODEC = createCodec(CuddlyBlock::new); +public class CuddlyBlock extends HorizontalFacingBlock { + + public static final Map + SHARK_SHAPES = VoxelEmitter.union( + // BASE + VoxelEmitter.ofBlock( + 8 - 2, 0, 0, + 8 + 2, 4, 9 + ), + // MID + VoxelEmitter.ofBlock( + 8 - 1.5, 0.5, 9, + 8 + 1.5, 3.5, 11 + ), + // FIN_RIGHT + VoxelEmitter.ofBlock( + 8 - 3, -1.5, 5.5, + 8 - 2, 1, 9.5 + ), + // FIN_LEFT + VoxelEmitter.ofBlock( + 8 + 2, -1.5, 5.5, + 8 + 3, 1, 9.5 + ), + // FIN_BACK + VoxelEmitter.ofBlock( + 8 - 1, 4, 5.5, + 8 + 1, 7, 8.5 + ), + // TAIL + VoxelEmitter.ofBlock( + 8 - 1, 1, 11, + 8 + 1, 3, 16 + ), + // TAIL_DETAIL + VoxelEmitter.ofBlock( + 8 - .5, 3, 12, + 8 + .5, 3.5, 13 + ), + // TAIL_FIN + VoxelEmitter.ofBlock( + 8 - .5, -.5, 14.5, + 8 + .5, 5.5, 17 + ) + ).toFloorMap(FACING), + + WHALE_SHAPES = VoxelEmitter.union( + // BASE + VoxelEmitter.ofBlock( + 8 - 3, 0, 0, + 8 + 3, 4, 8 + ), + // MID + VoxelEmitter.ofBlock( + 8 - 2, 0.5, 8, + 8 + 2, 3.5, 11 + ), + // FIN_RIGHT + VoxelEmitter.ofBlock( + 8 - 6.5, 1, 3.5, + 8 - 3, 2, 6 + ), + // FIN_LEFT + VoxelEmitter.ofBlock( + 8 + 3, 1, 3.5, + 8 + 6.5, 2, 6 + ), + // FIN_BACK + VoxelEmitter.ofBlock( + 8 - .5, 4, 5.5, + 8 + .5, 6.5, 8 + ), + // TAIL + VoxelEmitter.ofBlock( + 8 - 1.5, 1, 11, + 8 + 1.5, 3, 14 + ), + // TAIL_FIN + VoxelEmitter.ofBlock( + 8 - 4, 1, 14, + 8 + 4, 3, 16 + ) + ).toFloorMap(FACING), + + BREAD_SHAPES = VoxelEmitter.ofBlock( + 8 - 3, 0, -1, + 8 + 3, 6, 16 + ).toFloorMap(FACING), + + BEAR_SHAPES = VoxelEmitter.union( + // BODY + VoxelEmitter.ofBlock( + 8 - 6, 0, 4, + 8 + 6, 10, 15 + ), + // HEAD + VoxelEmitter.ofBlock( + 8 - 4, 10, 7, + 8 + 4, 16, 14 + ), + // SNOUT + VoxelEmitter.ofBlock( + 8 - 2, 10.5, 5, + 8 + 2, 13.5, 7 + ) + ).toFloorMap(FACING); + + private static final MapCodec CODEC = createCodec(CuddlyBlock::new); + + private final Map shapes; + + public CuddlyBlock(Settings settings, Map shapes) { + super(settings); + this.shapes = shapes; + this.setDefaultState(this.getStateManager().getDefaultState() + .with(FACING, Direction.NORTH)); + } public CuddlyBlock(Settings settings) { - super(settings); - this.setDefaultState(this.getStateManager().getDefaultState() - .with(FACING, Direction.NORTH)); + this(settings, SHARK_SHAPES); } @Override @@ -32,13 +143,13 @@ public class CuddlyBlock extends HorizontalFacingBlock { } public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { - return SHAPE; + return shapes.get(state.get(FACING)); } @Override public BlockState getPlacementState(ItemPlacementContext ctx) { return this.getDefaultState() - .with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + .with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); } protected void appendProperties(StateManager.Builder builder) { diff --git a/xplat/src/main/java/gay/pridecraft/joy/util/BasicVoxelEmitter.java b/xplat/src/main/java/gay/pridecraft/joy/util/BasicVoxelEmitter.java new file mode 100644 index 0000000..652c5ed --- /dev/null +++ b/xplat/src/main/java/gay/pridecraft/joy/util/BasicVoxelEmitter.java @@ -0,0 +1,79 @@ +package gay.pridecraft.joy.util; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; + +/** + * VoxelShape emitter that rotates as needed. + * + * @author Ampflower + * @since 1.0.0 + **/ +record BasicVoxelEmitter( + double ax, double ay, double az, + double bx, double by, double bz +) implements VoxelEmitter { + + BasicVoxelEmitter { + if (ax > bx) { + throw new IllegalArgumentException("x min > x max"); + } + if (ay > by) { + throw new IllegalArgumentException("y min > y max"); + } + if (az > bz) { + throw new IllegalArgumentException("z min > z max"); + } + } + + /** + * Identity of the emitter + */ + public VoxelShape floorNorth() { + return VoxelShapes.cuboid(ax, ay, az, bx, by, bz); + } + + public VoxelShape floorSouth() { + return VoxelShapes.cuboid(1 - bx, ay, 1 - bz, 1 - ax, by, 1 - az); + } + + public VoxelShape floorEast() { + return VoxelShapes.cuboid(1 - bz, ay, ax, 1 - az, by, bx); + } + + public VoxelShape floorWest() { + return VoxelShapes.cuboid(az, ay, 1 - bx, bz, by, 1 - ax); + } + + public VoxelShape ceilingNorth() { + return VoxelShapes.cuboid(ax, 1 - by, az, bx, 1 - ay, bz); + } + + public VoxelShape ceilingSouth() { + return VoxelShapes.cuboid(1 - bx, 1 - by, 1 - bz, 1 - ax, 1 - ay, 1 - az); + } + + public VoxelShape ceilingEast() { + return VoxelShapes.cuboid(1 - bz, 1 - by, ax, 1 - az, 1 - ay, bx); + } + + public VoxelShape ceilingWest() { + return VoxelShapes.cuboid(az, 1 - by, 1 - bx, bz, 1 - ay, 1 - ax); + } + + public VoxelShape north() { + return VoxelShapes.cuboid(ax, az, 1 - by, bx, bz, 1 - ay); + } + + public VoxelShape south() { + return VoxelShapes.cuboid(1 - bx, az, ay, 1 - ax, bz, by); + } + + public VoxelShape east() { + return VoxelShapes.cuboid(ay, az, ax, by, bz, bx); + } + + public VoxelShape west() { + return VoxelShapes.cuboid(1 - by, az, 1 - bx, 1 - ay, bz, 1 - ax); + } +} diff --git a/xplat/src/main/java/gay/pridecraft/joy/util/UnionVoxelEmitter.java b/xplat/src/main/java/gay/pridecraft/joy/util/UnionVoxelEmitter.java new file mode 100644 index 0000000..9062b0b --- /dev/null +++ b/xplat/src/main/java/gay/pridecraft/joy/util/UnionVoxelEmitter.java @@ -0,0 +1,100 @@ +package gay.pridecraft.joy.util; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * @author Ampflower + * @since 1.0.0 + **/ +record UnionVoxelEmitter(List emitters) implements VoxelEmitter { + UnionVoxelEmitter(VoxelEmitter... emitters) { + this(toBasic(Arrays.asList(emitters))); + } + + UnionVoxelEmitter(Iterable emitters) { + this(toBasic(emitters)); + } + + static List toBasic(Iterable emitters) { + final var set = new HashSet(); + for (final var emitter : emitters) { + if (emitter instanceof UnionVoxelEmitter union) { + set.addAll(union.emitters()); + } else { + set.add(emitter); + } + } + return List.copyOf(set); + } + + static VoxelShape union(Stream shapes) { + final var list = shapes.toList(); + if (list.isEmpty()) { + return VoxelShapes.empty(); + } + final var init = list.get(0); + if (list.size() == 1) { + return init; + } + return VoxelShapes.union(init, list.subList(1, list.size()).toArray(VoxelShape[]::new)); + } + + private Stream stream(Function map) { + return emitters.stream().map(map); + } + + public VoxelShape floorNorth() { + return union(stream(VoxelEmitter::floorNorth)); + } + + public VoxelShape floorSouth() { + return union(stream(VoxelEmitter::floorSouth)); + } + + public VoxelShape floorEast() { + return union(stream(VoxelEmitter::floorEast)); + } + + public VoxelShape floorWest() { + return union(stream(VoxelEmitter::floorWest)); + } + + public VoxelShape ceilingNorth() { + return union(stream(VoxelEmitter::ceilingNorth)); + } + + public VoxelShape ceilingSouth() { + return union(stream(VoxelEmitter::ceilingSouth)); + } + + public VoxelShape ceilingEast() { + return union(stream(VoxelEmitter::ceilingEast)); + } + + public VoxelShape ceilingWest() { + return union(stream(VoxelEmitter::ceilingWest)); + } + + public VoxelShape north() { + return union(stream(VoxelEmitter::north)); + } + + public VoxelShape south() { + return union(stream(VoxelEmitter::south)); + } + + public VoxelShape east() { + return union(stream(VoxelEmitter::east)); + } + + public VoxelShape west() { + return union(stream(VoxelEmitter::west)); + } +} diff --git a/xplat/src/main/java/gay/pridecraft/joy/util/VoxelEmitter.java b/xplat/src/main/java/gay/pridecraft/joy/util/VoxelEmitter.java new file mode 100644 index 0000000..cbf96a0 --- /dev/null +++ b/xplat/src/main/java/gay/pridecraft/joy/util/VoxelEmitter.java @@ -0,0 +1,138 @@ +package gay.pridecraft.joy.util; + +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; + +import java.util.EnumMap; + +/** + * @author Ampflower + * @since 1.0.0 + **/ +public sealed interface VoxelEmitter permits BasicVoxelEmitter, UnionVoxelEmitter { + double BLOCK = 16; + + /** + * Expected: Floor north box in 1x1x1 bound + */ + static VoxelEmitter of(final double ax, final double ay, final double az, + final double bx, final double by, final double bz) { + return new BasicVoxelEmitter(ax, ay, az, bx, by, bz); + } + + /** + * Expected: Floor north box in 16x16x16 bound + */ + static VoxelEmitter ofBlock(final double ax, final double ay, final double az, + final double bx, final double by, final double bz) { + return new BasicVoxelEmitter( + ax / BLOCK, ay / BLOCK, az / BLOCK, + bx / BLOCK, by / BLOCK, bz / BLOCK + ); + } + + static VoxelEmitter union(VoxelEmitter... emitters) { + if (emitters.length == 1) { + return emitters[0]; + } + + return new UnionVoxelEmitter(emitters); + } + + static VoxelEmitter union(Iterable emitters) { + return new UnionVoxelEmitter(emitters); + } + + VoxelShape floorNorth(); + + VoxelShape floorSouth(); + + VoxelShape floorEast(); + + VoxelShape floorWest(); + + VoxelShape ceilingNorth(); + + VoxelShape ceilingSouth(); + + VoxelShape ceilingEast(); + + VoxelShape ceilingWest(); + + VoxelShape north(); + + VoxelShape south(); + + VoxelShape east(); + + VoxelShape west(); + + default EnumMap toWallMap(Direction... directions) { + final var ret = new EnumMap(Direction.class); + for (final Direction direction : directions) { + ret.put(direction, ofWall(direction)); + } + return ret; + } + + + default EnumMap toFloorMap(Direction... directions) { + final var ret = new EnumMap(Direction.class); + for (final Direction direction : directions) { + ret.put(direction, ofFloor(direction)); + } + return ret; + } + + default EnumMap toCeilingMap(Direction... directions) { + final var ret = new EnumMap(Direction.class); + for (final Direction direction : directions) { + ret.put(direction, ofCeiling(direction)); + } + return ret; + } + + default EnumMap toWallMap(DirectionProperty property) { + return toWallMap(property.getValues().toArray(Direction[]::new)); + } + + default EnumMap toFloorMap(DirectionProperty property) { + return toFloorMap(property.getValues().toArray(Direction[]::new)); + } + + default EnumMap toCeilingMap(DirectionProperty property) { + return toCeilingMap(property.getValues().toArray(Direction[]::new)); + } + + default VoxelShape ofWall(Direction direction) { + return switch (direction) { + case DOWN -> floorNorth(); + case UP -> ceilingNorth(); + case NORTH -> north(); + case SOUTH -> south(); + case WEST -> west(); + case EAST -> east(); + }; + } + + default VoxelShape ofFloor(Direction direction) { + return switch (direction) { + case UP -> ceilingNorth(); + case DOWN, NORTH -> floorNorth(); + case SOUTH -> floorSouth(); + case WEST -> floorWest(); + case EAST -> floorEast(); + }; + } + + default VoxelShape ofCeiling(Direction direction) { + return switch (direction) { + case DOWN -> floorNorth(); + case UP, NORTH -> ceilingNorth(); + case SOUTH -> ceilingSouth(); + case WEST -> ceilingWest(); + case EAST -> ceilingEast(); + }; + } +}