mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-10-25 20:32:24 +00:00
Merge remote-tracking branch 'origin/structure-sockets-and-controller-pos' into dev
This commit is contained in:
11 files changed
+632
-56
No files matched your search
@@ -5,7 +5,11 @@ import static java.lang.Math.abs;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
|
||||
import com.gtnewhorizon.structurelib.coords.CoordinateSystem;
|
||||
import com.gtnewhorizon.structurelib.coords.Position;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
public class IntegerAxisSwap {
|
||||
@@ -91,4 +95,32 @@ public class IntegerAxisSwap {
|
||||
out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2];
|
||||
out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2];
|
||||
}
|
||||
|
||||
public Vector3i translate(Vector3i point) {
|
||||
return new Vector3i(
|
||||
forFirstAxis.get0() * point.x() + forFirstAxis.get1() * point.y() + forFirstAxis.get2() * point.z(),
|
||||
forSecondAxis.get0() * point.x() + forSecondAxis.get1() * point.y() + forSecondAxis.get2() * point.z(),
|
||||
forThirdAxis.get0() * point.x() + forThirdAxis.get1() * point.y() + forThirdAxis.get2() * point.z());
|
||||
}
|
||||
|
||||
public Vector3i inverseTranslate(Vector3i point) {
|
||||
return new Vector3i(
|
||||
forFirstAxis.get0() * point.x() + forSecondAxis.get0() * point.y() + forThirdAxis.get0() * point.z(),
|
||||
forFirstAxis.get1() * point.x() + forSecondAxis.get1() * point.y() + forThirdAxis.get1() * point.z(),
|
||||
forFirstAxis.get2() * point.x() + forSecondAxis.get2() * point.y() + forThirdAxis.get2() * point.z());
|
||||
}
|
||||
|
||||
public <C extends CoordinateSystem<C, ?>> Position<C> translate(Position<?> point) {
|
||||
return new Position<>(
|
||||
forFirstAxis.get0() * point.x() + forFirstAxis.get1() * point.y() + forFirstAxis.get2() * point.z(),
|
||||
forSecondAxis.get0() * point.x() + forSecondAxis.get1() * point.y() + forSecondAxis.get2() * point.z(),
|
||||
forThirdAxis.get0() * point.x() + forThirdAxis.get1() * point.y() + forThirdAxis.get2() * point.z());
|
||||
}
|
||||
|
||||
public <C extends CoordinateSystem<C, ?>> Position<C> inverseTranslate(Position<?> point) {
|
||||
return new Position<>(
|
||||
forFirstAxis.get0() * point.x() + forSecondAxis.get0() * point.y() + forThirdAxis.get0() * point.z(),
|
||||
forFirstAxis.get1() * point.x() + forSecondAxis.get1() * point.y() + forThirdAxis.get1() * point.z(),
|
||||
forFirstAxis.get2() * point.x() + forSecondAxis.get2() * point.y() + forThirdAxis.get2() * point.z());
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import net.minecraftforge.common.util.ForgeDirection;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
|
||||
import com.gtnewhorizon.structurelib.coords.StructureRelativeCoords;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
public enum ExtendedFacing {
|
||||
@@ -166,6 +167,7 @@ public enum ExtendedFacing {
|
||||
|
||||
private final String name;
|
||||
private final IntegerAxisSwap integerAxisSwap;
|
||||
private final StructureRelativeCoords coordinateSystem;
|
||||
|
||||
ExtendedFacing(String name) {
|
||||
this.name = name;
|
||||
@@ -247,6 +249,7 @@ public enum ExtendedFacing {
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
integerAxisSwap = new IntegerAxisSwap(a, b, c);
|
||||
coordinateSystem = new StructureRelativeCoords(integerAxisSwap);
|
||||
}
|
||||
|
||||
public static ExtendedFacing of(ForgeDirection direction, Rotation rotation, Flip flip) {
|
||||
@@ -437,4 +440,8 @@ public enum ExtendedFacing {
|
||||
public ForgeDirection getRelativeForwardInWorld() {
|
||||
return c.getOpposite();
|
||||
}
|
||||
|
||||
public StructureRelativeCoords asCoordinateSystem() {
|
||||
return coordinateSystem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
/**
|
||||
* Controller-relative coordinates. The controller is at 0,0,0. No axis swapping takes place, each axis points in the
|
||||
* same directions as the matching world axis.
|
||||
*/
|
||||
public class ControllerRelativeCoords implements CoordinateSystem<ControllerRelativeCoords, WorldCoords> {
|
||||
|
||||
public final int controllerX, controllerY, controllerZ;
|
||||
|
||||
public ControllerRelativeCoords(int controllerX, int controllerY, int controllerZ) {
|
||||
this.controllerX = controllerX;
|
||||
this.controllerY = controllerY;
|
||||
this.controllerZ = controllerZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<ControllerRelativeCoords> translate(Position<WorldCoords> position) {
|
||||
return translate(position, controllerX, controllerY, controllerZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<WorldCoords> translateInverse(Position<ControllerRelativeCoords> position) {
|
||||
return translateInverse(position, controllerX, controllerY, controllerZ);
|
||||
}
|
||||
|
||||
public static Position<ControllerRelativeCoords> translate(Position<WorldCoords> position, int controllerX,
|
||||
int controllerY, int controllerZ) {
|
||||
position.sub(controllerX, controllerY, controllerZ);
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
|
||||
public static Position<WorldCoords> translateInverse(Position<ControllerRelativeCoords> position, int controllerX,
|
||||
int controllerY, int controllerZ) {
|
||||
position.add(controllerX, controllerY, controllerZ);
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
|
||||
/**
|
||||
* A coordinate system is a statically-checked helper for determining which coordinates a {@link Position} has.
|
||||
* Translation methods never copy a {@link Position} - if a position must remain unchanged, it must be copied
|
||||
* separately. Most coordinate systems will have static methods that allow you to avoid allocations, though some (such
|
||||
* as the ones provided by {@link ExtendedFacing#asCoordinateSystem()}) have no reason to be static.
|
||||
*
|
||||
* @param <Self>
|
||||
* @param <Parent>
|
||||
*/
|
||||
public interface CoordinateSystem<Self extends CoordinateSystem<Self, Parent>, Parent extends CoordinateSystem<Parent, ?>> {
|
||||
|
||||
/**
|
||||
* Translates a position from the parent coordinate system into this one.
|
||||
*/
|
||||
Position<Self> translate(Position<Parent> position);
|
||||
|
||||
/**
|
||||
* Translates a position from this coordinate system into the parent.
|
||||
*/
|
||||
Position<Parent> translateInverse(Position<Self> position);
|
||||
|
||||
/**
|
||||
* Helper method that converts a position's coordinate system. Should not be used unless you know it's correct.
|
||||
*/
|
||||
static <P1 extends CoordinateSystem<P1, ?>, P2 extends CoordinateSystem<P2, ?>> Position<P1> transmute(
|
||||
Position<P2> pos) {
|
||||
// noinspection unchecked
|
||||
return (Position<P1>) pos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
/**
|
||||
* A {@link BlockPos} with a statically-checked coordinate system.
|
||||
*/
|
||||
public class Position<C extends CoordinateSystem<C, ?>> extends BlockPos {
|
||||
|
||||
public Position() {
|
||||
|
||||
}
|
||||
|
||||
public Position(int x, int y, int z) {
|
||||
super(x, y, z);
|
||||
}
|
||||
|
||||
public Position(float x, float y, float z) {
|
||||
super(MathHelper.floor_float(x), MathHelper.floor_float(y), MathHelper.floor_float(z));
|
||||
}
|
||||
|
||||
public Position(double x, double y, double z) {
|
||||
super(MathHelper.floor_double(x), MathHelper.floor_double(y), MathHelper.floor_double(z));
|
||||
}
|
||||
|
||||
public Position(ChunkPosition chunkPosition) {
|
||||
super(chunkPosition);
|
||||
}
|
||||
|
||||
public Position(Vector3i v) {
|
||||
super(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
public Position(Vector3f v) {
|
||||
this(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
public Position(Vec3 v) {
|
||||
this(v.xCoord, v.yCoord, v.zCoord);
|
||||
}
|
||||
|
||||
public Position(Vec3Impl v) {
|
||||
this(v.get0(), v.get1(), v.get2());
|
||||
}
|
||||
|
||||
public Position<C> copy() {
|
||||
return new Position<>(x, y, z);
|
||||
}
|
||||
|
||||
public Vector3i toVector3i() {
|
||||
return new Vector3i(x, y, z);
|
||||
}
|
||||
|
||||
public Vector3f toVector3f() {
|
||||
return new Vector3f(x, y, z);
|
||||
}
|
||||
|
||||
public Vec3 toVec3() {
|
||||
return Vec3.createVectorHelper(x, y, z);
|
||||
}
|
||||
|
||||
public Vec3Impl toVec3Impl() {
|
||||
return new Vec3Impl(x, y, z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
/// The structure definition-local coords. 0,0,0 is the front-most left-most top-most element. Coordinates increase as
|
||||
/// you go down, right, or back.
|
||||
public class StructureDefinitionCoords implements CoordinateSystem<StructureDefinitionCoords, StructureRelativeCoords> {
|
||||
|
||||
public final int offsetX, offsetY, offsetZ;
|
||||
|
||||
public StructureDefinitionCoords(int offsetX, int offsetY, int offsetZ) {
|
||||
this.offsetX = offsetX;
|
||||
this.offsetY = offsetY;
|
||||
this.offsetZ = offsetZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<StructureDefinitionCoords> translate(Position<StructureRelativeCoords> position) {
|
||||
return translate(position, offsetX, offsetY, offsetZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<StructureRelativeCoords> translateInverse(Position<StructureDefinitionCoords> position) {
|
||||
return translateInverse(position, offsetX, offsetY, offsetZ);
|
||||
}
|
||||
|
||||
public static Position<StructureDefinitionCoords> translate(Position<StructureRelativeCoords> position, int offsetX,
|
||||
int offsetY, int offsetZ) {
|
||||
position.add(offsetX, offsetY, offsetZ);
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
|
||||
public static Position<StructureRelativeCoords> translateInverse(Position<StructureDefinitionCoords> position,
|
||||
int offsetX, int offsetY, int offsetZ) {
|
||||
position.sub(offsetX, offsetY, offsetZ);
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
|
||||
|
||||
/**
|
||||
* Coordinates relative to the structure. 0,0,0 is the controller. 0,0,1 is the block immediately behind the controller
|
||||
* (looking at it from its face). 1,0,0 is the block to the right of the controller. 0,1,0 is the block below the
|
||||
* controller.
|
||||
*/
|
||||
public class StructureRelativeCoords implements CoordinateSystem<StructureRelativeCoords, ControllerRelativeCoords> {
|
||||
|
||||
public final IntegerAxisSwap axisSwap;
|
||||
|
||||
public StructureRelativeCoords(IntegerAxisSwap axisSwap) {
|
||||
this.axisSwap = axisSwap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<StructureRelativeCoords> translate(Position<ControllerRelativeCoords> position) {
|
||||
position.set(axisSwap.translate(position));
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<ControllerRelativeCoords> translateInverse(Position<StructureRelativeCoords> position) {
|
||||
position.set(axisSwap.inverseTranslate(position));
|
||||
|
||||
return CoordinateSystem.transmute(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
/**
|
||||
* World block coordinates
|
||||
*/
|
||||
public class WorldCoords implements CoordinateSystem<WorldCoords, WorldCoords> {
|
||||
|
||||
public static final WorldCoords INSTANCE = new WorldCoords();
|
||||
|
||||
private WorldCoords() {}
|
||||
|
||||
@Override
|
||||
public Position<WorldCoords> translate(Position<WorldCoords> position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<WorldCoords> translateInverse(Position<WorldCoords> position) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,11 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -15,6 +18,8 @@ import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.coords.Position;
|
||||
import com.gtnewhorizon.structurelib.coords.StructureDefinitionCoords;
|
||||
|
||||
/**
|
||||
* This is the structure definition of your multi. You will have one of these for each multi.
|
||||
@@ -335,6 +340,49 @@ public interface IStructureDefinition<T> {
|
||||
"customIterate");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller position for a piece. Controller positions are denoted by a tilde.
|
||||
*
|
||||
* @param piece The piece name
|
||||
* @return The controller position, or null if it doesn't exist for the given piece
|
||||
*/
|
||||
Position<StructureDefinitionCoords> getControllerPosition(String piece);
|
||||
|
||||
/**
|
||||
* Gets the coordinate system for a piece. This is used to translate structure definition coordinates into structure
|
||||
* relative coordinates.
|
||||
*
|
||||
* @param piece The piece
|
||||
* @return The coordinate system
|
||||
* @throws IllegalArgumentException When the piece does not exist
|
||||
*/
|
||||
StructureDefinitionCoords getCoordinateSystem(String piece);
|
||||
|
||||
/**
|
||||
* Gets a socket from a piece.
|
||||
*
|
||||
* @param piece The piece name
|
||||
* @param socket The socket character
|
||||
* @return The socket's position
|
||||
* @throws IllegalArgumentException When the piece does not exist
|
||||
* @throws IllegalArgumentException When the socket does not exist
|
||||
* @throws IllegalStateException When more than one socket exists with this character
|
||||
* @see StructureDefinition.Builder#addSocket(char, char)
|
||||
*/
|
||||
Position<StructureDefinitionCoords> getSocket(String piece, char socket);
|
||||
|
||||
/**
|
||||
* Gets all sockets with a given character for a piece.
|
||||
*
|
||||
* @param piece The piece name
|
||||
* @param socket The socket character
|
||||
* @return The list of socket positions, or an empty list if the socket is not present
|
||||
* @throws IllegalArgumentException When the piece does not exist
|
||||
* @see StructureDefinition.Builder#addSocket(char, char)
|
||||
*/
|
||||
@Nonnull
|
||||
List<Position<StructureDefinitionCoords>> getAllSockets(String piece, char socket);
|
||||
|
||||
/**
|
||||
* Low level utility.
|
||||
*
|
||||
|
||||
@@ -4,33 +4,49 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.isAir;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.notAir;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.gtnewhorizon.structurelib.coords.Position;
|
||||
import com.gtnewhorizon.structurelib.coords.StructureDefinitionCoords;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
import it.unimi.dsi.fastutil.chars.Char2CharOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.chars.CharOpenHashSet;
|
||||
|
||||
public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, IStructureElement<T>[]> structures;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
private final Map<String, Position<StructureDefinitionCoords>> controllers;
|
||||
private final Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations;
|
||||
|
||||
public static <B> Builder<B> builder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
|
||||
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
|
||||
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces,
|
||||
Map<String, Position<StructureDefinitionCoords>> controllers,
|
||||
Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations) {
|
||||
this.elements = elements;
|
||||
this.shapes = shapes;
|
||||
this.structures = structures;
|
||||
this.occupiedSpaces = occupiedSpaces;
|
||||
this.controllers = controllers;
|
||||
this.socketLocations = socketLocations;
|
||||
}
|
||||
|
||||
public static class Builder<T> {
|
||||
@@ -41,12 +57,18 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private char d = '\uD000';
|
||||
private final Map<Vec3Impl, Character> navigates;
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String[][]> uncompiledShapes;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
private final Map<String, Position<StructureDefinitionCoords>> controllers = new HashMap<>();
|
||||
private final Char2CharOpenHashMap socketTranslations = new Char2CharOpenHashMap();
|
||||
private final Map<String, ListMultimap<Character, Position<StructureDefinitionCoords>>> socketLocations = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
navigates = new HashMap<>();
|
||||
elements = new HashMap<>();
|
||||
uncompiledShapes = new HashMap<>();
|
||||
shapes = new HashMap<>();
|
||||
occupiedSpaces = new HashMap<>();
|
||||
}
|
||||
@@ -82,7 +104,69 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder<T> addShape(String name, String[][] structurePiece) {
|
||||
uncompiledShapes.put(name, structurePiece);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the unboxed version
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes {@code name} as a socket. Sockets are virtual structure elements that record their position in the
|
||||
* structure for future retrieval. They are transformed into real structure elements and do not need their own
|
||||
* element (see {@code replacement}).
|
||||
*
|
||||
* @param name The socket name character
|
||||
* @param replacement The replacement character
|
||||
* @see IStructureDefinition#getSocket(String, char)
|
||||
* @see IStructureDefinition#getAllSockets(String, char)
|
||||
*/
|
||||
public Builder<T> addSocket(char name, char replacement) {
|
||||
socketTranslations.put(name, replacement);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #addSocket(char, char)} but without the replacement. The structure must have a structure element
|
||||
* with the given character.
|
||||
*
|
||||
* @param name The socket name character
|
||||
*/
|
||||
public Builder<T> addSocket(char name) {
|
||||
// Adding a name -> name entry like this isn't ideal but this is the cleanest solution
|
||||
socketTranslations.put(name, name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStructureDefinition<T> build() {
|
||||
uncompiledShapes.forEach(this::compileShape);
|
||||
|
||||
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
|
||||
|
||||
return new StructureDefinition<>(
|
||||
new HashMap<>(elements),
|
||||
new HashMap<>(shapes),
|
||||
structures,
|
||||
occupiedSpaces,
|
||||
controllers,
|
||||
socketLocations);
|
||||
}
|
||||
|
||||
private Builder<T> compileShape(String name, String[][] structurePiece) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (structurePiece.length > 0) {
|
||||
for (String[] strings : structurePiece) {
|
||||
if (strings.length > 0) {
|
||||
@@ -95,6 +179,11 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
builder.setLength(builder.length() - 1);
|
||||
}
|
||||
|
||||
// Always create a map for this piece, even if it doesn't have any sockets
|
||||
// noinspection UnstableApiUsage
|
||||
socketLocations.put(name, MultimapBuilder.hashKeys().arrayListValues().build());
|
||||
|
||||
Set<Vec3Impl> occupiedSpace = new HashSet<>();
|
||||
// these track the global current location
|
||||
int aa = 0, bb = 0, cc = 0;
|
||||
@@ -103,6 +192,15 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
// I do know these are pretty bad variable naming, but I have no better idea
|
||||
for (int i = 0; i < builder.length(); i++) {
|
||||
char ch = builder.charAt(i);
|
||||
|
||||
// Translate the element char if it's a socket, and record the socket position
|
||||
if (socketTranslations.containsKey(ch)) {
|
||||
socketLocations.get(name).put(ch, new Position<>(aa, bb, cc));
|
||||
|
||||
ch = socketTranslations.get(ch);
|
||||
builder.setCharAt(i, ch);
|
||||
}
|
||||
|
||||
if (ch == ' ') {
|
||||
builder.setCharAt(i, A);
|
||||
ch = A;
|
||||
@@ -110,6 +208,7 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
builder.setCharAt(i, A);
|
||||
ch = A;
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
controllers.put(name, new Position<>(aa, bb, cc));
|
||||
}
|
||||
if (ch == A) {
|
||||
aa++;
|
||||
@@ -177,64 +276,58 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the unboxed version
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStructureDefinition<T> build() {
|
||||
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
|
||||
return new StructureDefinition<>(
|
||||
new HashMap<>(elements),
|
||||
new HashMap<>(shapes),
|
||||
structures,
|
||||
occupiedSpaces);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().chars().mapToObj(c -> (char) c).distinct().map(elements::get)
|
||||
.toArray(IStructureElement[]::new)));
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, IStructureElement<T>[]> compileStructureMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().chars().mapToObj(c -> elements.get((char) c))
|
||||
.toArray(IStructureElement[]::new)));
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
CharOpenHashSet missing = findMissing();
|
||||
|
||||
if (!missing.isEmpty()) {
|
||||
throw new RuntimeException("Missing Structure Element bindings for: " + missing);
|
||||
}
|
||||
|
||||
Map<String, IStructureElement<T>[]> map = new HashMap<>();
|
||||
|
||||
for (Entry<String, String> e : shapes.entrySet()) {
|
||||
char[] chars = e.getValue().toCharArray();
|
||||
IStructureElement<T>[] compiled = new IStructureElement[chars.length];
|
||||
|
||||
int a = 0, b = 0, c = 0;
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char ch = chars[i];
|
||||
|
||||
IStructureElement<T> element = this.elements.get(ch);
|
||||
|
||||
compiled[i] = element;
|
||||
|
||||
if (element.isNavigating()) {
|
||||
a = (element.resetA() ? 0 : a) + element.getStepA();
|
||||
b = (element.resetB() ? 0 : b) + element.getStepB();
|
||||
c = (element.resetC() ? 0 : c) + element.getStepC();
|
||||
} else {
|
||||
a++;
|
||||
}
|
||||
}
|
||||
|
||||
if (map.put(e.getKey(), compiled) != null) {
|
||||
throw new IllegalStateException("Duplicate shape: " + e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private Set<Integer> findMissing() {
|
||||
return shapes.values().stream().flatMapToInt(CharSequence::chars)
|
||||
.filter(i -> !elements.containsKey((char) i)).boxed().collect(Collectors.toSet());
|
||||
private CharOpenHashSet findMissing() {
|
||||
CharOpenHashSet missing = new CharOpenHashSet();
|
||||
|
||||
for (String shape : shapes.values()) {
|
||||
for (char c : shape.toCharArray()) {
|
||||
if (!elements.containsKey(c)) {
|
||||
missing.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,4 +356,61 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
||||
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<StructureDefinitionCoords> getControllerPosition(String piece) {
|
||||
return controllers.get(piece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinitionCoords getCoordinateSystem(String piece) {
|
||||
var controller = controllers.get(piece);
|
||||
|
||||
if (controller == null) {
|
||||
throw new IllegalArgumentException("Invalid piece: " + piece);
|
||||
}
|
||||
|
||||
return new StructureDefinitionCoords(controller.x, controller.y, controller.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position<StructureDefinitionCoords> getSocket(String piece, char socket) {
|
||||
var piecePositions = socketLocations.get(piece);
|
||||
|
||||
if (piecePositions == null) {
|
||||
throw new IllegalArgumentException("Invalid piece: " + piece);
|
||||
}
|
||||
|
||||
var positions = piecePositions.get(socket);
|
||||
|
||||
if (positions.isEmpty()) {
|
||||
throw new IllegalArgumentException("Socket " + socket + " for piece " + piece + " does not exist");
|
||||
}
|
||||
|
||||
if (positions.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Socket " + socket
|
||||
+ " for piece "
|
||||
+ piece
|
||||
+ " has several positions, but expected one and only one");
|
||||
}
|
||||
|
||||
return positions.get(0).copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public List<Position<StructureDefinitionCoords>> getAllSockets(String piece, char socket) {
|
||||
var piecePositions = socketLocations.get(piece);
|
||||
|
||||
if (piecePositions == null) {
|
||||
throw new IllegalArgumentException("Invalid piece: " + piece);
|
||||
}
|
||||
|
||||
var list = new ArrayList<>(piecePositions.get(socket));
|
||||
|
||||
list.replaceAll(Position::copy);
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.gtnewhorizon.structurelib.coords;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
|
||||
import com.gtnewhorizon.structurelib.structure.StructureUtility;
|
||||
|
||||
// spotless:off
|
||||
class CoordinateTests {
|
||||
|
||||
private IStructureDefinition<?> getDefinition() {
|
||||
String[][] main = {
|
||||
{ " ", " ", " ", " ", " " },
|
||||
{ " ", " ", " ", " ", " " },
|
||||
{ " ", " ", " X ", " ", " " },
|
||||
{ " ", " ", " YY ", " ", " " },
|
||||
{ " ~ ", " ", " ", " ", " " }
|
||||
};
|
||||
|
||||
return IStructureDefinition.builder()
|
||||
.addShape("main", StructureUtility.transpose(main))
|
||||
.addSocket('X', ' ')
|
||||
.addSocket('Y', ' ')
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSocketAndCoordSystemTranslationToWorld() {
|
||||
IStructureDefinition<?> def = getDefinition();
|
||||
|
||||
// Note: the position is the same object between all of these transforms, it is never copied, its generic just
|
||||
// changes
|
||||
|
||||
var p = def.getSocket("main", 'X');
|
||||
Assertions.assertEquals(new Position<>(2, 2, 2), p);
|
||||
|
||||
var p2 = def.getCoordinateSystem("main").translateInverse(p);
|
||||
Assertions.assertEquals(new Position<>(0, -2, 2), p2);
|
||||
|
||||
var p3 = ExtendedFacing.NORTH_NORMAL_NONE.asCoordinateSystem().translateInverse(p2);
|
||||
Assertions.assertEquals(new Position<>(0, 2, 2), p3);
|
||||
|
||||
var p4 = ControllerRelativeCoords.translateInverse(p3, 5, 5, 5);
|
||||
Assertions.assertEquals(new Position<>(5, 7, 7), p4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSocketAndCoordSystemTranslationToStructureDef() {
|
||||
IStructureDefinition<?> def = getDefinition();
|
||||
|
||||
// Note: the position is the same object between all of these transforms, it is never copied, its generic just
|
||||
// changes
|
||||
|
||||
var p = new Position<WorldCoords>(5, 7, 7);
|
||||
|
||||
var p2 = ControllerRelativeCoords.translate(p, 5, 5, 5);
|
||||
Assertions.assertEquals(new Position<>(0, 2, 2), p2);
|
||||
|
||||
var p3 = ExtendedFacing.NORTH_NORMAL_NONE.asCoordinateSystem().translate(p2);
|
||||
Assertions.assertEquals(new Position<>(0, -2, 2), p3);
|
||||
|
||||
var p4 = def.getCoordinateSystem("main").translate(p3);
|
||||
Assertions.assertEquals(new Position<>(2, 2, 2), p4);
|
||||
|
||||
Assertions.assertEquals(def.getSocket("main", 'X'), p4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSocketGetters() {
|
||||
IStructureDefinition<?> def = getDefinition();
|
||||
|
||||
Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> def.getSocket("invalid", 'X'));
|
||||
Assertions.assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> def.getSocket("main", 'Y'));
|
||||
Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> def.getSocket("main", 'Z'));
|
||||
|
||||
Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> def.getAllSockets("invalid", 'X'));
|
||||
|
||||
Assertions.assertEquals(
|
||||
Collections.emptyList(),
|
||||
def.getAllSockets("main", 'Z'));
|
||||
Assertions.assertEquals(
|
||||
Arrays.asList(new Position<>(2, 2, 2)),
|
||||
def.getAllSockets("main", 'X'));
|
||||
Assertions.assertEquals(
|
||||
Arrays.asList(new Position<>(2, 3, 2), new Position<>(3, 3, 2)),
|
||||
def.getAllSockets("main", 'Y'));
|
||||
}
|
||||
}
|
||||
// spotless:on
|
||||
Reference in new issue
Block a user