From 5c9cca55198758da2daafb65c2037ef9f4aa94bc Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Sat, 12 Oct 2024 14:34:11 +0200 Subject: [PATCH] Initial commit --- build.gradle | 5 + gradle.properties | 6 +- .../gui/widget/ToggleIconButton.java | 71 +++++ .../foundation/utility/Iterate.java | 57 ++++ .../foundation/utility/ShapeBuilder.java | 77 +++++ .../foundation/utility/VecHelper.java | 297 ++++++++++++++++++ .../foundation/utility/VoxelShaper.java | 142 +++++++++ 7 files changed, 653 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/gui/widget/ToggleIconButton.java create mode 100644 src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/Iterate.java create mode 100644 src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/ShapeBuilder.java create mode 100644 src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VecHelper.java create mode 100644 src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VoxelShaper.java diff --git a/build.gradle b/build.gradle index 7436249..f2a3539 100644 --- a/build.gradle +++ b/build.gradle @@ -135,6 +135,8 @@ repositories { } dependencies { + implementation 'org.jetbrains:annotations:22.0.0' + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" @@ -144,6 +146,9 @@ dependencies { implementation fg.deobf("curse.maven:jade-324717:${jade_id}") + if (System.getProperty('idea.sync.active') != 'true') { + annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" + } } // This block of code expands all declared replace properties in the specified resource targets. diff --git a/gradle.properties b/gradle.properties index ae5d6ed..57d9232 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,11 +8,13 @@ forge_version_range=[47,) loader_version_range=[47,) mapping_channel=official mapping_version=1.20.1 +mixin_version = 0.8.5 + modid=mechanical_lemon_ui mod_name=Mechanical Lemon UI mod_license=MIT -mod_version=0.0.2 +mod_version=0.1.3 mod_group_id=com.oierbravo author=oierbravo mod_description=Library @@ -23,4 +25,4 @@ jei_version = 15.2.0.22 curios_minecraft_version = 1.20.1 curios_version = 5.2.0-beta.3 -jade_id = 4654448 \ No newline at end of file +jade_id = 5672013 \ No newline at end of file diff --git a/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/gui/widget/ToggleIconButton.java b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/gui/widget/ToggleIconButton.java new file mode 100644 index 0000000..7746be0 --- /dev/null +++ b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/gui/widget/ToggleIconButton.java @@ -0,0 +1,71 @@ +package com.oierbravo.mechanical_lemon_ui.foundation.gui.widget; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.oierbravo.mechanical_lemon_ui.foundation.gui.widget.AbstractSimiWidget; +import com.oierbravo.mechanical_lemon_ui.foundation.utility.ScreenElement; +import com.oierbravo.mechanical_lemon_ui.register.LibGuiTextures; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class ToggleIconButton extends AbstractSimiWidget { + + protected ScreenElement[] icons; + protected MutableComponent[] labels; + + private int currentIndex; + + public ToggleIconButton(int x, int y, ScreenElement[] icons, MutableComponent[] labels) { + this(x, y, 18, 18, icons,labels,0); + } + public ToggleIconButton(int x, int y, ScreenElement[] icons, MutableComponent[] labels,int currentIndex) { + this(x, y, 18, 18, icons,labels,currentIndex); + } + + public ToggleIconButton(int x, int y, int w, int h, ScreenElement[] icons, MutableComponent[] labels) { + this(x, y, w, h,icons,labels,0); + } + public ToggleIconButton(int x, int y, int w, int h, ScreenElement[] icons, MutableComponent[] labels, int currentIndex) { + super(x, y, w, h); + this.icons = icons; + this.labels = labels; + this.currentIndex = currentIndex; + } + + @Override + public void doRender(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + if (visible) { + isHovered = mouseX >= getX() && mouseY >= getY() && mouseX < getX() + width && mouseY < getY() + height; + + LibGuiTextures button = !active ? LibGuiTextures.BUTTON_DOWN + : isMouseOver(mouseX, mouseY) ? LibGuiTextures.BUTTON_HOVER : LibGuiTextures.BUTTON; + + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + drawBg(graphics, button); + icons[currentIndex].render(graphics, getX() + 1, getY() + 1); + setToolTip(getLabel(currentIndex)); + } + } + + protected void drawBg(GuiGraphics graphics, LibGuiTextures button) { + graphics.blit(button.location, getX(), getY(), button.startX, button.startY, button.width, button.height); + } + + public void setToolTip(Component text) { + toolTip.clear(); + toolTip.add(text); + } + + public void setCurrentIndex(int pIndex){ + this.currentIndex = pIndex; + } + public void setIcons(ScreenElement[] icon) { + this.icons = icons; + } + public void setLabels(MutableComponent[] labels) { + this.labels = labels; + } + public MutableComponent getLabel(int pIndex){ + return this.labels[pIndex]; + } +} diff --git a/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/Iterate.java b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/Iterate.java new file mode 100644 index 0000000..ff1c806 --- /dev/null +++ b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/Iterate.java @@ -0,0 +1,57 @@ +package com.oierbravo.mechanical_lemon_ui.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +public class Iterate { + + public static final boolean[] trueAndFalse = { true, false }; + public static final boolean[] falseAndTrue = { false, true }; + public static final int[] zeroAndOne = { 0, 1 }; + public static final int[] positiveAndNegative = { 1, -1 }; + public static final Direction[] directions = Direction.values(); + public static final Direction[] horizontalDirections = getHorizontals(); + public static final Axis[] axes = Axis.values(); + public static final EnumSet axisSet = EnumSet.allOf(Axis.class); + + private static Direction[] getHorizontals() { + Direction[] directions = new Direction[4]; + for (int i = 0; i < 4; i++) + directions[i] = Direction.from2DDataValue(i); + return directions; + } + + public static Direction[] directionsInAxis(Axis axis) { + switch (axis) { + case X: + return new Direction[] { Direction.EAST, Direction.WEST }; + case Y: + return new Direction[] { Direction.UP, Direction.DOWN }; + default: + case Z: + return new Direction[] { Direction.SOUTH, Direction.NORTH }; + } + } + + public static List hereAndBelow(BlockPos pos) { + return Arrays.asList(pos, pos.below()); + } + + public static List hereBelowAndAbove(BlockPos pos) { + return Arrays.asList(pos, pos.below(), pos.above()); + } + + public static T cycleValue(List list, T current) { + int currentIndex = list.indexOf(current); + if (currentIndex == -1) { + throw new IllegalArgumentException("Current value not found in list"); + } + int nextIndex = (currentIndex + 1) % list.size(); + return list.get(nextIndex); + } +} diff --git a/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/ShapeBuilder.java b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/ShapeBuilder.java new file mode 100644 index 0000000..c0d1c9c --- /dev/null +++ b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/ShapeBuilder.java @@ -0,0 +1,77 @@ +package com.oierbravo.mechanical_lemon_ui.foundation.utility; + +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.function.BiFunction; + +import static net.minecraft.core.Direction.UP; + +public class ShapeBuilder { + private VoxelShape shape; + + public ShapeBuilder(VoxelShape shape) { + this.shape = shape; + } + + public ShapeBuilder add(VoxelShape shape) { + this.shape = Shapes.or(this.shape, shape); + return this; + } + + public ShapeBuilder add(double x1, double y1, double z1, double x2, double y2, double z2) { + return add(cuboid(x1, y1, z1, x2, y2, z2)); + } + + public ShapeBuilder erase(double x1, double y1, double z1, double x2, double y2, double z2) { + this.shape = Shapes.join(shape, cuboid(x1, y1, z1, x2, y2, z2), BooleanOp.ONLY_FIRST); + return this; + } + + public VoxelShape build() { + return shape; + } + + public VoxelShaper build(BiFunction factory, Direction direction) { + return factory.apply(shape, direction); + } + + public VoxelShaper build(BiFunction factory, Direction.Axis axis) { + return factory.apply(shape, axis); + } + + public VoxelShaper forDirectional(Direction direction) { + return build(VoxelShaper::forDirectional, direction); + } + + public VoxelShaper forAxis() { + return build(VoxelShaper::forAxis, Direction.Axis.Y); + } + + public VoxelShaper forHorizontalAxis() { + return build(VoxelShaper::forHorizontalAxis, Direction.Axis.Z); + } + + public VoxelShaper forHorizontal(Direction direction) { + return build(VoxelShaper::forHorizontal, direction); + } + + public VoxelShaper forDirectional() { + return forDirectional(UP); + } + + public static ShapeBuilder shape(VoxelShape shape) { + return new ShapeBuilder(shape); + } + + public static ShapeBuilder shape(double x1, double y1, double z1, double x2, double y2, double z2) { + return shape(cuboid(x1, y1, z1, x2, y2, z2)); + } + + public static VoxelShape cuboid(double x1, double y1, double z1, double x2, double y2, double z2) { + return Block.box(x1, y1, z1, x2, y2, z2); + } +} diff --git a/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VecHelper.java b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VecHelper.java new file mode 100644 index 0000000..17453d3 --- /dev/null +++ b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VecHelper.java @@ -0,0 +1,297 @@ +package com.oierbravo.mechanical_lemon_ui.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; + +public class VecHelper { + + public static final Vec3 CENTER_OF_ORIGIN = new Vec3(.5, .5, .5); + + public static Vec3 rotate(Vec3 vec, Vec3 rotationVec) { + return rotate(vec, rotationVec.x, rotationVec.y, rotationVec.z); + } + + public static Vec3 rotate(Vec3 vec, double xRot, double yRot, double zRot) { + return rotate(rotate(rotate(vec, xRot, Axis.X), yRot, Axis.Y), zRot, Axis.Z); + } + + public static Vec3 rotateCentered(Vec3 vec, double deg, Axis axis) { + Vec3 shift = getCenterOf(BlockPos.ZERO); + return VecHelper.rotate(vec.subtract(shift), deg, axis) + .add(shift); + } + + public static Vec3 rotate(Vec3 vec, double deg, Axis axis) { + if (deg == 0) + return vec; + if (vec == Vec3.ZERO) + return vec; + + float angle = (float) (deg / 180f * Math.PI); + double sin = Mth.sin(angle); + double cos = Mth.cos(angle); + double x = vec.x; + double y = vec.y; + double z = vec.z; + + if (axis == Axis.X) + return new Vec3(x, y * cos - z * sin, z * cos + y * sin); + if (axis == Axis.Y) + return new Vec3(x * cos + z * sin, y, z * cos - x * sin); + if (axis == Axis.Z) + return new Vec3(x * cos - y * sin, y * cos + x * sin, z); + return vec; + } + + public static Vec3 mirrorCentered(Vec3 vec, Mirror mirror) { + Vec3 shift = getCenterOf(BlockPos.ZERO); + return VecHelper.mirror(vec.subtract(shift), mirror) + .add(shift); + } + + public static Vec3 mirror(Vec3 vec, Mirror mirror) { + if (mirror == null || mirror == Mirror.NONE) + return vec; + if (vec == Vec3.ZERO) + return vec; + + double x = vec.x; + double y = vec.y; + double z = vec.z; + + if (mirror == Mirror.LEFT_RIGHT) + return new Vec3(x, y, -z); + if (mirror == Mirror.FRONT_BACK) + return new Vec3(-x, y, z); + return vec; + } + + public static Vec3 lookAt(Vec3 vec, Vec3 fwd) { + fwd = fwd.normalize(); + Vec3 up = new Vec3(0, 1, 0); + double dot = fwd.dot(up); + if (Math.abs(dot) > 1 - 1.0E-3) + up = new Vec3(0, 0, dot > 0 ? 1 : -1); + Vec3 right = fwd.cross(up) + .normalize(); + up = right.cross(fwd) + .normalize(); + double x = vec.x * right.x + vec.y * up.x + vec.z * fwd.x; + double y = vec.x * right.y + vec.y * up.y + vec.z * fwd.y; + double z = vec.x * right.z + vec.y * up.z + vec.z * fwd.z; + return new Vec3(x, y, z); + } + + public static boolean isVecPointingTowards(Vec3 vec, Direction direction) { + return Vec3.atLowerCornerOf(direction.getNormal()) + .dot(vec.normalize()) > 0.125; // slight tolerance to activate perpendicular movement actors + } + + public static Vec3 getCenterOf(Vec3i pos) { + if (pos.equals(Vec3i.ZERO)) + return CENTER_OF_ORIGIN; + return Vec3.atLowerCornerOf(pos) + .add(.5f, .5f, .5f); + } + + public static Vec3 offsetRandomly(Vec3 vec, RandomSource r, float radius) { + return new Vec3(vec.x + (r.nextFloat() - .5f) * 2 * radius, vec.y + (r.nextFloat() - .5f) * 2 * radius, + vec.z + (r.nextFloat() - .5f) * 2 * radius); + } + + public static Vec3 axisAlingedPlaneOf(Vec3 vec) { + vec = vec.normalize(); + return new Vec3(1, 1, 1).subtract(Math.abs(vec.x), Math.abs(vec.y), Math.abs(vec.z)); + } + + public static Vec3 axisAlingedPlaneOf(Direction face) { + return axisAlingedPlaneOf(Vec3.atLowerCornerOf(face.getNormal())); + } + + public static ListTag writeNBT(Vec3 vec) { + ListTag listnbt = new ListTag(); + listnbt.add(DoubleTag.valueOf(vec.x)); + listnbt.add(DoubleTag.valueOf(vec.y)); + listnbt.add(DoubleTag.valueOf(vec.z)); + return listnbt; + } + + public static CompoundTag writeNBTCompound(Vec3 vec) { + CompoundTag compoundTag = new CompoundTag(); + compoundTag.put("V", writeNBT(vec)); + return compoundTag; + } + + public static Vec3 readNBT(ListTag list) { + if (list.isEmpty()) + return Vec3.ZERO; + return new Vec3(list.getDouble(0), list.getDouble(1), list.getDouble(2)); + } + + public static Vec3 readNBTCompound(CompoundTag nbt) { + return readNBT(nbt.getList("V", Tag.TAG_DOUBLE)); + } + + public static void write(Vec3 vec, FriendlyByteBuf buffer) { + buffer.writeDouble(vec.x); + buffer.writeDouble(vec.y); + buffer.writeDouble(vec.z); + } + + public static Vec3 read(FriendlyByteBuf buffer) { + return new Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()); + } + + public static Vec3 voxelSpace(double x, double y, double z) { + return new Vec3(x, y, z).scale(1 / 16f); + } + + public static int getCoordinate(Vec3i pos, Axis axis) { + return axis.choose(pos.getX(), pos.getY(), pos.getZ()); + } + + public static float getCoordinate(Vec3 vec, Axis axis) { + return (float) axis.choose(vec.x, vec.y, vec.z); + } + + public static boolean onSameAxis(BlockPos pos1, BlockPos pos2, Axis axis) { + if (pos1.equals(pos2)) + return true; + for (Axis otherAxis : Axis.values()) + if (axis != otherAxis) + if (getCoordinate(pos1, otherAxis) != getCoordinate(pos2, otherAxis)) + return false; + return true; + } + + public static Vec3 clamp(Vec3 vec, float maxLength) { + return vec.lengthSqr() > maxLength * maxLength ? vec.normalize() + .scale(maxLength) : vec; + } + + public static Vec3 lerp(float p, Vec3 from, Vec3 to) { + return from.add(to.subtract(from) + .scale(p)); + } + + public static Vec3 slerp(float p, Vec3 from, Vec3 to) { + double theta = Math.acos(from.dot(to)); + return from.scale(Mth.sin(1 - p) * theta) + .add(to.scale(Mth.sin((float) (theta * p)))) + .scale(1 / Mth.sin((float) theta)); + } + + public static Vec3 clampComponentWise(Vec3 vec, float maxLength) { + return new Vec3(Mth.clamp(vec.x, -maxLength, maxLength), Mth.clamp(vec.y, -maxLength, maxLength), + Mth.clamp(vec.z, -maxLength, maxLength)); + } + + public static Vec3 componentMin(Vec3 vec1, Vec3 vec2) { + return new Vec3(Math.min(vec1.x, vec2.x), Math.min(vec1.y, vec2.y), Math.min(vec1.z, vec2.z)); + } + + public static Vec3 componentMax(Vec3 vec1, Vec3 vec2) { + return new Vec3(Math.max(vec1.x, vec2.x), Math.max(vec1.y, vec2.y), Math.max(vec1.z, vec2.z)); + } + + public static Vec3 project(Vec3 vec, Vec3 ontoVec) { + if (ontoVec.equals(Vec3.ZERO)) + return Vec3.ZERO; + return ontoVec.scale(vec.dot(ontoVec) / ontoVec.lengthSqr()); + } + + @Nullable + public static Vec3 intersectSphere(Vec3 origin, Vec3 lineDirection, Vec3 sphereCenter, double radius) { + if (lineDirection.equals(Vec3.ZERO)) + return null; + if (lineDirection.lengthSqr() != 1) + lineDirection = lineDirection.normalize(); + + Vec3 diff = origin.subtract(sphereCenter); + double lineDotDiff = lineDirection.dot(diff); + double delta = lineDotDiff * lineDotDiff - (diff.lengthSqr() - radius * radius); + if (delta < 0) + return null; + double t = -lineDotDiff + Math.sqrt(delta); + return origin.add(lineDirection.scale(t)); + } + + + + public static Vec3 bezier(Vec3 p1, Vec3 p2, Vec3 q1, Vec3 q2, float t) { + Vec3 v1 = lerp(t, p1, q1); + Vec3 v2 = lerp(t, q1, q2); + Vec3 v3 = lerp(t, q2, p2); + Vec3 inner1 = lerp(t, v1, v2); + Vec3 inner2 = lerp(t, v2, v3); + Vec3 result = lerp(t, inner1, inner2); + return result; + } + + public static Vec3 bezierDerivative(Vec3 p1, Vec3 p2, Vec3 q1, Vec3 q2, float t) { + return p1.scale(-3 * t * t + 6 * t - 3) + .add(q1.scale(9 * t * t - 12 * t + 3)) + .add(q2.scale(-9 * t * t + 6 * t)) + .add(p2.scale(3 * t * t)); + } + + @Nullable + public static double[] intersectRanged(Vec3 p1, Vec3 q1, Vec3 p2, Vec3 q2, Axis plane) { + Vec3 pDiff = p2.subtract(p1); + Vec3 qDiff = q2.subtract(q1); + double[] intersect = intersect(p1, q1, pDiff.normalize(), qDiff.normalize(), plane); + if (intersect == null) + return null; + if (intersect[0] < 0 || intersect[1] < 0) + return null; + if (intersect[0] * intersect[0] > pDiff.lengthSqr() || intersect[1] * intersect[1] > qDiff.lengthSqr()) + return null; + return intersect; + } + + @Nullable + public static double[] intersect(Vec3 p1, Vec3 p2, Vec3 r, Vec3 s, Axis plane) { + if (plane == Axis.X) { + p1 = new Vec3(p1.y, 0, p1.z); + p2 = new Vec3(p2.y, 0, p2.z); + r = new Vec3(r.y, 0, r.z); + s = new Vec3(s.y, 0, s.z); + } + + if (plane == Axis.Z) { + p1 = new Vec3(p1.x, 0, p1.y); + p2 = new Vec3(p2.x, 0, p2.y); + r = new Vec3(r.x, 0, r.y); + s = new Vec3(s.x, 0, s.y); + } + + Vec3 qminusp = p2.subtract(p1); + double rcs = r.x * s.z - r.z * s.x; + if (Mth.equal(rcs, 0)) + return null; + Vec3 rdivrcs = r.scale(1 / rcs); + Vec3 sdivrcs = s.scale(1 / rcs); + double t = qminusp.x * sdivrcs.z - qminusp.z * sdivrcs.x; + double u = qminusp.x * rdivrcs.z - qminusp.z * rdivrcs.x; + return new double[] { t, u }; + } + + public static double alignedDistanceToFace(Vec3 pos, BlockPos blockPos, Direction face) { + Axis axis = face.getAxis(); + return Math.abs(getCoordinate(pos, axis) - (blockPos.get(axis) + (face.getAxisDirection() == Direction.AxisDirection.POSITIVE ? 1 : 0))); + } + +} diff --git a/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VoxelShaper.java b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VoxelShaper.java new file mode 100644 index 0000000..f85b85d --- /dev/null +++ b/src/main/java/com/oierbravo/mechanical_lemon_ui/foundation/utility/VoxelShaper.java @@ -0,0 +1,142 @@ +package com.oierbravo.mechanical_lemon_ui.foundation.utility; + +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.apache.commons.lang3.mutable.MutableObject; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class VoxelShaper { + + private Map shapes = new HashMap<>(); + + public VoxelShape get(Direction direction) { + return shapes.get(direction); + } + + public VoxelShape get(Axis axis) { + return shapes.get(axisAsFace(axis)); + } + + public static VoxelShaper forHorizontal(VoxelShape shape, Direction facing) { + return forDirectionsWithRotation(shape, facing, Direction.Plane.HORIZONTAL, new HorizontalRotationValues()); + } + + public static VoxelShaper forHorizontalAxis(VoxelShape shape, Axis along) { + return forDirectionsWithRotation(shape, axisAsFace(along), Arrays.asList(Direction.SOUTH, Direction.EAST), + new HorizontalRotationValues()); + } + + public static VoxelShaper forDirectional(VoxelShape shape, Direction facing) { + return forDirectionsWithRotation(shape, facing, Arrays.asList(Iterate.directions), new DefaultRotationValues()); + } + + public static VoxelShaper forAxis(VoxelShape shape, Axis along) { + return forDirectionsWithRotation(shape, axisAsFace(along), + Arrays.asList(Direction.SOUTH, Direction.EAST, Direction.UP), new DefaultRotationValues()); + } + + public VoxelShaper withVerticalShapes(VoxelShape upShape) { + shapes.put(Direction.UP, upShape); + shapes.put(Direction.DOWN, rotatedCopy(upShape, new Vec3(180, 0, 0))); + return this; + } + + public VoxelShaper withShape(VoxelShape shape, Direction facing) { + shapes.put(facing, shape); + return this; + } + + public static Direction axisAsFace(Axis axis) { + return Direction.get(AxisDirection.POSITIVE, axis); + } + + protected static float horizontalAngleFromDirection(Direction direction) { + return (float) ((Math.max(direction.get2DDataValue(), 0) & 3) * 90); + } + + protected static VoxelShaper forDirectionsWithRotation(VoxelShape shape, Direction facing, + Iterable directions, Function rotationValues) { + VoxelShaper voxelShaper = new VoxelShaper(); + for (Direction dir : directions) { + voxelShaper.shapes.put(dir, rotate(shape, facing, dir, rotationValues)); + } + return voxelShaper; + } + + protected static VoxelShape rotate(VoxelShape shape, Direction from, Direction to, + Function usingValues) { + if (from == to) + return shape; + + return rotatedCopy(shape, usingValues.apply(from) + .reverse() + .add(usingValues.apply(to))); + } + + protected static VoxelShape rotatedCopy(VoxelShape shape, Vec3 rotation) { + if (rotation.equals(Vec3.ZERO)) + return shape; + + MutableObject result = new MutableObject<>(Shapes.empty()); + Vec3 center = new Vec3(8, 8, 8); + + shape.forAllBoxes((x1, y1, z1, x2, y2, z2) -> { + Vec3 v1 = new Vec3(x1, y1, z1).scale(16) + .subtract(center); + Vec3 v2 = new Vec3(x2, y2, z2).scale(16) + .subtract(center); + + v1 = VecHelper.rotate(v1, (float) rotation.x, Axis.X); + v1 = VecHelper.rotate(v1, (float) rotation.y, Axis.Y); + v1 = VecHelper.rotate(v1, (float) rotation.z, Axis.Z) + .add(center); + + v2 = VecHelper.rotate(v2, (float) rotation.x, Axis.X); + v2 = VecHelper.rotate(v2, (float) rotation.y, Axis.Y); + v2 = VecHelper.rotate(v2, (float) rotation.z, Axis.Z) + .add(center); + + VoxelShape rotated = blockBox(v1, v2); + result.setValue(Shapes.or(result.getValue(), rotated)); + }); + + return result.getValue(); + } + + protected static VoxelShape blockBox(Vec3 v1, Vec3 v2) { + return Block.box( + Math.min(v1.x, v2.x), + Math.min(v1.y, v2.y), + Math.min(v1.z, v2.z), + Math.max(v1.x, v2.x), + Math.max(v1.y, v2.y), + Math.max(v1.z, v2.z) + ); + } + + protected static class DefaultRotationValues implements Function { + // assume facing up as the default rotation + @Override + public Vec3 apply(Direction direction) { + return new Vec3(direction == Direction.UP ? 0 : (Direction.Plane.VERTICAL.test(direction) ? 180 : 90), + -horizontalAngleFromDirection(direction), 0); + } + } + + protected static class HorizontalRotationValues implements Function { + @Override + public Vec3 apply(Direction direction) { + return new Vec3(0, -horizontalAngleFromDirection(direction), 0); + } + } + +}