use foot print class instead of hardcoded foot print
This commit is contained in:
@@ -5,8 +5,8 @@ import com.gregtechceu.gtceu.api.registry.GTRegistries;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
@@ -22,6 +22,8 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import com.imbgt.ibg.block.ModBlocks;
|
||||
import com.imbgt.ibg.block.entity.ModBlockEntities;
|
||||
import com.imbgt.ibg.block.entity.client.AnimatedBlockRenderer;
|
||||
import com.imbgt.ibg.util.FootprintSpec;
|
||||
import com.imbgt.ibg.util.Footprints;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
import software.bernie.geckolib.GeckoLib;
|
||||
@@ -49,7 +51,16 @@ public class IBG {
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||
}
|
||||
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {}
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
Footprints.register(new ResourceLocation("gtceu", "lv_lathe"),
|
||||
FootprintSpec.builder()
|
||||
.addPart(-1, 0, 0)
|
||||
.masterOutline(Shapes.box(0, 0, 0, 1, 14. / 16., 1))
|
||||
.partOutline(Shapes.box(0, 0, 0, 1, 14. / 16., 1))
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerStarting(ServerStartingEvent event) {}
|
||||
@@ -63,8 +74,6 @@ public class IBG {
|
||||
var latheDef = GTRegistries.MACHINES.get(
|
||||
new ResourceLocation("gtceu", "lv_lathe"));
|
||||
|
||||
latheDef.setShape(Block.box(-16, 0, 0, 32 - 16, 14, 16));
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
BlockEntityRendererProvider<BlockEntity> provider = (BlockEntityRendererProvider) (ctx -> new AnimatedBlockRenderer<>(
|
||||
ctx));
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.imbgt.ibg.block.custom;
|
||||
|
||||
import com.gregtechceu.gtceu.api.block.MetaMachineBlock;
|
||||
import com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity;
|
||||
import com.imbgt.ibg.util.Footprints;
|
||||
import com.imbgt.ibg.util.FootprintSpec;
|
||||
import com.gregtechceu.gtceu.api.block.MetaMachineBlock;
|
||||
|
||||
import net.minecraft.client.particle.ParticleEngine;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@@ -25,6 +28,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import net.minecraftforge.client.extensions.common.IClientBlockExtensions;
|
||||
|
||||
@@ -42,7 +46,7 @@ public class PartBlock extends Block implements EntityBlock {
|
||||
}
|
||||
|
||||
private static BlockPos masterPos(BlockGetter level, BlockPos pos) {
|
||||
BlockEntity be = (level instanceof Level l) ? l.getBlockEntity(pos) : null;
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
return (be instanceof PartBE ml) ? ml.getMasterPos() : null;
|
||||
}
|
||||
|
||||
@@ -81,11 +85,15 @@ public class PartBlock extends Block implements EntityBlock {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: give the part a slice-shaped outline/collision on its own cell
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState s, BlockGetter g, BlockPos p,
|
||||
CollisionContext c) {
|
||||
return Block.box(0, 0, 0, 16, 16, 16); // or custom slice if you want
|
||||
return Block.box(0, 0, 0, 16, 16, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getCollisionShape(BlockState s, BlockGetter g, BlockPos p, CollisionContext c) {
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,7 +132,7 @@ public class PartBlock extends Block implements EntityBlock {
|
||||
public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState state) {
|
||||
BlockPos m = masterPos(level, pos);
|
||||
if (m != null) {
|
||||
BlockEntity be = ((Level) level).getBlockEntity(m);
|
||||
BlockEntity be = level.getBlockEntity(m);
|
||||
if (be instanceof MetaMachineBlockEntity mm)
|
||||
return mm.getDefinition().asStack();
|
||||
}
|
||||
@@ -147,62 +155,41 @@ public class PartBlock extends Block implements EntityBlock {
|
||||
@Override
|
||||
public void neighborChanged(BlockState state, Level level, BlockPos pos,
|
||||
Block neighborBlock, BlockPos fromPos, boolean isMoving) {
|
||||
IBG.LOGGER.info("coucou");
|
||||
if (level.isClientSide)
|
||||
return;
|
||||
if (level.isClientSide) return;
|
||||
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (!(be instanceof PartBE part))
|
||||
return;
|
||||
if (!(be instanceof PartBE part)) return;
|
||||
|
||||
BlockPos master = part.getMasterPos();
|
||||
if (master == null) {
|
||||
IBG.LOGGER.info("nul");
|
||||
// // No master? Clean up.
|
||||
level.setBlock(pos, Blocks.AIR.defaultBlockState(),
|
||||
Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE |
|
||||
Block.UPDATE_SUPPRESS_DROPS);
|
||||
Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only react when the master changed (rotation or removal)
|
||||
if (!fromPos.equals(master))
|
||||
return;
|
||||
// Only react when the master changed (rotation/removal)
|
||||
if (!fromPos.equals(master)) return;
|
||||
|
||||
BlockState masterState = level.getBlockState(master);
|
||||
if (masterState.isAir()) {
|
||||
IBG.LOGGER.info("hmmm");
|
||||
|
||||
// Master gone: remove self silently
|
||||
level.setBlock(pos, Blocks.AIR.defaultBlockState(),
|
||||
Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute where this part *should* be for the master’s current facing
|
||||
Direction facing = masterState.getValue(
|
||||
((MetaMachineBlock) masterState.getBlock())
|
||||
.getRotationState().property);
|
||||
if (!(masterState.getBlock() instanceof MetaMachineBlock mm)) return;
|
||||
var rot = mm.getRotationState();
|
||||
if (rot == null || !masterState.hasProperty(rot.property)) return;
|
||||
Direction facing = masterState.getValue(rot.property);
|
||||
|
||||
BlockPos expected = computePartPosForFacing(master, facing); // your offset logic
|
||||
// Validate against the full footprint
|
||||
FootprintSpec spec = Footprints.get(mm.getDefinition().getId());
|
||||
if (spec == null) return;
|
||||
|
||||
if (!pos.equals(expected)) {
|
||||
IBG.LOGGER.info("{} - {}", pos, expected);
|
||||
// This part is stale (old position). Remove it silently; the new part is placed
|
||||
// by your existing placement path.
|
||||
boolean isExpected = Footprints.partPositions(master, facing, spec).contains(pos);
|
||||
if (!isExpected) {
|
||||
level.setBlock(pos, Blocks.AIR.defaultBlockState(),
|
||||
Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_SUPPRESS_DROPS);
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockPos computePartPosForFacing(BlockPos master, Direction facing) {
|
||||
Direction right = switch (facing) {
|
||||
case NORTH -> Direction.WEST;
|
||||
case EAST -> Direction.NORTH;
|
||||
case SOUTH -> Direction.EAST;
|
||||
case WEST -> Direction.SOUTH;
|
||||
default -> Direction.EAST; // pick a sensible default if needed
|
||||
};
|
||||
return master.relative(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import com.imbgt.ibg.block.ModBlocks;
|
||||
import com.imbgt.ibg.block.entity.PartBE;
|
||||
import com.imbgt.ibg.util.Footprints;
|
||||
import com.imbgt.ibg.IBG;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -52,7 +55,7 @@ public abstract class MetaMachineBlockMixin extends AppearanceBlock {
|
||||
|
||||
@Inject(method = "getStateForPlacement", at = @At("HEAD"), cancellable = true)
|
||||
private void ibg$latheFootprintCheck(BlockPlaceContext ctx,
|
||||
CallbackInfoReturnable<BlockState> cir) {
|
||||
CallbackInfoReturnable<BlockState> cir) {
|
||||
MetaMachineBlock self = (MetaMachineBlock) (Object) this;
|
||||
var id = self.getDefinition().getId();
|
||||
if (!("gtceu".equals(id.getNamespace()) && "lv_lathe".equals(id.getPath())))
|
||||
@@ -62,64 +65,73 @@ public abstract class MetaMachineBlockMixin extends AppearanceBlock {
|
||||
BlockPos base = ctx.getClickedPos();
|
||||
|
||||
// Determine facing like MetaMachineBlock does:
|
||||
Direction facing;
|
||||
var rot = self.getDefinition().getRotationState();
|
||||
if (rot == RotationState.Y_AXIS)
|
||||
facing = Direction.UP; // adjust if needed
|
||||
else
|
||||
facing = ctx.getHorizontalDirection().getOpposite();
|
||||
Direction facing = ctx.getHorizontalDirection().getOpposite();
|
||||
|
||||
// Your footprint: master + one part at +clockwise (adjust if your model
|
||||
// differs)
|
||||
BlockPos partPos = base.relative(facing.getCounterClockWise());
|
||||
|
||||
// If the part cell cannot be replaced => CANCEL placement
|
||||
if (!level.getBlockState(partPos).canBeReplaced(ctx)) {
|
||||
cir.setReturnValue(null); // makes placement fail cleanly
|
||||
var spec = Footprints.get(self.getDefinition().getId());
|
||||
if (spec == null)
|
||||
return;
|
||||
var partPositions = Footprints.partPositions(base, facing, spec);
|
||||
for (BlockPos p : partPositions) {
|
||||
if (!level.getBlockState(p).canBeReplaced()) {
|
||||
cir.setReturnValue(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Inject(method = "onPlace", at = @At("TAIL"))
|
||||
private void ibg$placePart(BlockState st, Level lvl, BlockPos pos, BlockState old, boolean moved, CallbackInfo ci) {
|
||||
if (lvl.isClientSide)
|
||||
return;
|
||||
var def = getDefinition().getId();
|
||||
if (!("gtceu".equals(def.getNamespace()) && "lv_lathe".equals(def.getPath())))
|
||||
var id = getDefinition().getId();
|
||||
var spec = Footprints.get(id);
|
||||
if (spec == null)
|
||||
return;
|
||||
|
||||
var rot = ((IMachineBlock) (Object) this).getRotationState();
|
||||
var facing = rot == RotationState.NONE ? Direction.NORTH : st.getValue(rot.property);
|
||||
|
||||
// Example footprint: extend one cell to +X relative to facing (adjust as
|
||||
// needed)
|
||||
BlockPos partPos = pos.relative(facing.getCounterClockWise());
|
||||
|
||||
if (lvl.getBlockState(partPos).canBeReplaced()) {
|
||||
lvl.setBlockAndUpdate(partPos, ModBlocks.PART.get().defaultBlockState());
|
||||
var be = lvl.getBlockEntity(partPos);
|
||||
if (be instanceof PartBE part)
|
||||
part.setMasterPos(pos);
|
||||
} else {
|
||||
// Optional: rollback place if you require free space
|
||||
// lvl.removeBlock(pos, false);
|
||||
Direction facing = (rot == null || rot == RotationState.NONE || !st.hasProperty(rot.property)) ? Direction.NORTH
|
||||
: st.getValue(rot.property);
|
||||
for (BlockPos p : Footprints.partPositions(pos, facing, spec)) {
|
||||
if (lvl.getBlockState(p).canBeReplaced()) {
|
||||
lvl.setBlockAndUpdate(p, ModBlocks.PART.get().defaultBlockState());
|
||||
var be = lvl.getBlockEntity(p);
|
||||
if (be instanceof PartBE part)
|
||||
part.setMasterPos(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onRemove", at = @At("HEAD"))
|
||||
private void ibg$removePart(BlockState st, Level lvl, BlockPos pos, BlockState newState, boolean moving,
|
||||
CallbackInfo ci) {
|
||||
CallbackInfo ci) {
|
||||
if (lvl.isClientSide || st.getBlock() == newState.getBlock())
|
||||
return;
|
||||
var def = getDefinition().getId();
|
||||
if (!("gtceu".equals(def.getNamespace()) && "lv_lathe".equals(def.getPath())))
|
||||
var id = getDefinition().getId();
|
||||
var spec = com.imbgt.ibg.util.Footprints.get(id);
|
||||
if (spec == null)
|
||||
return;
|
||||
|
||||
var rot = ((IMachineBlock) (Object) this).getRotationState();
|
||||
var facing = rot == RotationState.NONE ? Direction.NORTH : st.getValue(rot.property);
|
||||
BlockPos partPos = pos.relative(facing.getCounterClockWise());
|
||||
|
||||
if (lvl.getBlockState(partPos).is(ModBlocks.PART.get())) {
|
||||
lvl.removeBlock(partPos, false);
|
||||
Direction facing = (rot == null || rot == RotationState.NONE || !st.hasProperty(rot.property)) ? Direction.NORTH
|
||||
: st.getValue(rot.property);
|
||||
for (BlockPos p : Footprints.partPositions(pos, facing, spec)) {
|
||||
if (lvl.getBlockState(p).is(ModBlocks.PART.get())) {
|
||||
lvl.removeBlock(p, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getShape", at = @At("HEAD"), cancellable = true)
|
||||
private void ibg$combinedOutline(BlockState state, BlockGetter level, BlockPos pos,
|
||||
CollisionContext ctx, CallbackInfoReturnable<VoxelShape> cir) {
|
||||
var spec = com.imbgt.ibg.util.Footprints.get(getDefinition().getId());
|
||||
if (spec == null)
|
||||
return;
|
||||
var rot = ((IMachineBlock) (Object) this).getRotationState();
|
||||
if (rot == null || !state.hasProperty(rot.property))
|
||||
return;
|
||||
var facing = state.getValue(rot.property);
|
||||
var shape = Footprints.combinedOutline(facing, spec);
|
||||
// var shape = Footprints.rotateOutline(spec.masterOutline(), facing);
|
||||
cir.setReturnValue(shape);
|
||||
}
|
||||
}
|
||||
|
||||
88
src/main/java/com/imbgt/ibg/util/FootprintSpec.java
Normal file
88
src/main/java/com/imbgt/ibg/util/FootprintSpec.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.imbgt.ibg.util;
|
||||
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Declarative footprint for a multiblock: relative part offsets and local outlines. */
|
||||
public final class FootprintSpec {
|
||||
|
||||
/** Relative positions (in block cells) of parts, defined for facing = NORTH. */
|
||||
private final List<Cell> parts;
|
||||
/** Per-cell outline for the master, in local [0,1] coords. Used for selection outline only. */
|
||||
private final VoxelShape masterOutline;
|
||||
/** Per-cell outline for each part, in local [0,1] coords. Used for selection outline only. */
|
||||
private final VoxelShape partOutline;
|
||||
|
||||
private FootprintSpec(List<Cell> parts, VoxelShape masterOutline, VoxelShape partOutline) {
|
||||
this.parts = List.copyOf(parts);
|
||||
this.masterOutline = masterOutline;
|
||||
this.partOutline = partOutline;
|
||||
}
|
||||
|
||||
public List<Cell> parts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public VoxelShape masterOutline() {
|
||||
return masterOutline;
|
||||
}
|
||||
|
||||
public VoxelShape partOutline() {
|
||||
return partOutline;
|
||||
}
|
||||
|
||||
/** Builder with sane defaults. */
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final List<Cell> parts = new ArrayList<>();
|
||||
private VoxelShape master = Shapes.box(0, 0, 0, 1, 1, 1);
|
||||
private VoxelShape part = Shapes.box(0, 0, 0, 1, 1, 1);
|
||||
|
||||
/** Add a part offset in block cells, relative to master, for facing = NORTH. */
|
||||
public Builder addPart(int dx, int dy, int dz) {
|
||||
parts.add(new Cell(dx, dy, dz));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set master outline shape in local [0,1] coords. */
|
||||
public Builder masterOutline(VoxelShape shape) {
|
||||
this.master = shape;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set part outline shape in local [0,1] coords. */
|
||||
public Builder partOutline(VoxelShape shape) {
|
||||
this.part = shape;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FootprintSpec build() {
|
||||
return new FootprintSpec(parts, normalize(master), normalize(part));
|
||||
}
|
||||
|
||||
private static VoxelShape normalize(VoxelShape s) {
|
||||
if (s == null) return Shapes.box(0, 0, 0, 1, 1, 1);
|
||||
// Ensure boxes are snapped to doubles and merged.
|
||||
VoxelShape out = Shapes.empty();
|
||||
for (AABB a : s.toAabbs()) out = Shapes.or(out, Shapes.create(a));
|
||||
return out.optimize();
|
||||
}
|
||||
}
|
||||
|
||||
/** Integer cell offset. */
|
||||
public static record Cell(int dx, int dy, int dz) {
|
||||
|
||||
public static List<Cell> none() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/main/java/com/imbgt/ibg/util/Footprints.java
Normal file
126
src/main/java/com/imbgt/ibg/util/Footprints.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package com.imbgt.ibg.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Registry + helpers to compute world positions and a continuous outline shape.
|
||||
*/
|
||||
public final class Footprints {
|
||||
|
||||
private static final Map<ResourceLocation, FootprintSpec> REG = new HashMap<>();
|
||||
|
||||
private Footprints() {
|
||||
}
|
||||
|
||||
/** Register or replace a footprint for a machine id. Call during setup. */
|
||||
public static void register(ResourceLocation machineId, FootprintSpec spec) {
|
||||
REG.put(machineId, spec);
|
||||
}
|
||||
|
||||
/** @return null when no footprint registered. */
|
||||
public static FootprintSpec get(ResourceLocation machineId) {
|
||||
return REG.get(machineId);
|
||||
}
|
||||
|
||||
/** All part world positions for a given master and facing. */
|
||||
public static List<BlockPos> partPositions(BlockPos master, Direction facing, FootprintSpec spec) {
|
||||
return spec.parts().stream()
|
||||
.map(c -> master.offset(rotateDx(c.dx(), c.dz(), facing), c.dy(), rotateDz(c.dx(), c.dz(), facing)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static VoxelShape rotateOutline(VoxelShape shape, Direction facing) {
|
||||
return rotateY(shape, quarters(facing));
|
||||
}
|
||||
|
||||
/**
|
||||
* A single continuous selection outline for the whole multiblock, anchored at
|
||||
* the master.
|
||||
*/
|
||||
public static VoxelShape combinedOutline(Direction facing, FootprintSpec spec) {
|
||||
int q = quarters(facing);
|
||||
VoxelShape result = rotateY(spec.masterOutline(), q);
|
||||
|
||||
for (var cell : spec.parts()) {
|
||||
// rotate the cell offset then translate the part shape by 1 * offset
|
||||
int rx = rotateDx(cell.dx(), cell.dz(), facing);
|
||||
int rz = rotateDz(cell.dx(), cell.dz(), facing);
|
||||
VoxelShape rotatedPart = rotateY(spec.partOutline(), q);
|
||||
result = Shapes.or(result, move(rotatedPart, rx, cell.dy(), rz));
|
||||
}
|
||||
return result.optimize();
|
||||
}
|
||||
|
||||
// ---------- math helpers ----------
|
||||
|
||||
/**
|
||||
* Rotate a local shape by 0/90/180/270 degrees around Y, within a single cell's
|
||||
* [0,1] space.
|
||||
*/
|
||||
private static VoxelShape rotateY(VoxelShape shape, int quarters) {
|
||||
if (quarters == 0)
|
||||
return shape;
|
||||
VoxelShape out = Shapes.empty();
|
||||
for (AABB a : shape.toAabbs()) {
|
||||
AABB r = switch (quarters & 3) {
|
||||
// X,Z in [0,1]; rotate around center at origin of the cell
|
||||
case 1 -> new AABB(1 - a.maxZ, a.minY, a.minX, 1 - a.minZ, a.maxY, a.maxX); // 90°
|
||||
case 2 -> new AABB(1 - a.maxX, a.minY, 1 - a.maxZ, 1 - a.minX, a.maxY, 1 - a.minZ); // 180°
|
||||
case 3 -> new AABB(a.minZ, a.minY, 1 - a.maxX, a.maxZ, a.maxY, 1 - a.minX); // 270°
|
||||
default -> a;
|
||||
};
|
||||
out = Shapes.or(out, Shapes.create(r));
|
||||
}
|
||||
return out.optimize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a shape by world-space units. For cross-cell outline we use
|
||||
* multiples of 1.
|
||||
*/
|
||||
private static VoxelShape move(VoxelShape s, double dx, double dy, double dz) {
|
||||
VoxelShape out = Shapes.empty();
|
||||
for (AABB a : s.toAabbs())
|
||||
out = Shapes.or(out, Shapes.create(a.move(dx, dy, dz)));
|
||||
return out;
|
||||
}
|
||||
|
||||
private static int rotateDx(int dx, int dz, Direction facing) {
|
||||
return switch (quarters(facing)) {
|
||||
case 0 -> dx; // NORTH
|
||||
case 1 -> -dz; // EAST
|
||||
case 2 -> -dx; // SOUTH
|
||||
case 3 -> dz; // WEST
|
||||
default -> dx;
|
||||
};
|
||||
}
|
||||
|
||||
private static int rotateDz(int dx, int dz, Direction facing) {
|
||||
return switch (quarters(facing)) {
|
||||
case 0 -> dz; // NORTH
|
||||
case 1 -> dx; // EAST
|
||||
case 2 -> -dz; // SOUTH
|
||||
case 3 -> -dx; // WEST
|
||||
default -> dz;
|
||||
};
|
||||
}
|
||||
|
||||
private static int quarters(Direction facing) {
|
||||
return switch (facing) {
|
||||
case NORTH -> 0;
|
||||
case EAST -> 1;
|
||||
case SOUTH -> 2;
|
||||
case WEST -> 3;
|
||||
default -> 0; // treat UP/DOWN as NORTH
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user