From 99ee5c69786092213195f7427e97cd3a9b1d3ae6 Mon Sep 17 00:00:00 2001
From: EpicKnarvik97 <kristian.knarvik@knett.no>
Date: Thu, 27 Jan 2022 04:28:36 +0100
Subject: [PATCH] Adds the SimpleVectorOperation class which is capable of
 rotating a RelativeBlockVector in 6 directions

---
 .../stargate/SimpleVectorOperation.java       | 191 ++++++++++++++++++
 .../container/RelativeBlockVector.java        |  11 +
 .../net/knarcraft/stargate/portal/Portal.java |   6 +-
 3 files changed, 207 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/net/knarcraft/stargate/SimpleVectorOperation.java

diff --git a/src/main/java/net/knarcraft/stargate/SimpleVectorOperation.java b/src/main/java/net/knarcraft/stargate/SimpleVectorOperation.java
new file mode 100644
index 0000000..31d207c
--- /dev/null
+++ b/src/main/java/net/knarcraft/stargate/SimpleVectorOperation.java
@@ -0,0 +1,191 @@
+package net.knarcraft.stargate;
+
+import org.bukkit.Axis;
+import org.bukkit.block.BlockFace;
+import org.bukkit.util.BlockVector;
+import org.bukkit.util.Vector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simpler version of the vector operation class, but with the same functionality
+ *
+ * @author Kristian Knarvik
+ */
+public class SimpleVectorOperation {
+
+    private static final Map<BlockFace, Double> rotationAngles = new HashMap<>();
+    private static final Map<BlockFace, Vector> rotationAxes = new HashMap<>();
+    private static final Map<BlockFace, Axis> normalAxes = new HashMap<>();
+    private static final BlockFace defaultDirection = BlockFace.SOUTH;
+    private static final Axis defaultVerticalAxis = Axis.Y;
+
+    private final Axis normalAxis;
+    private boolean flipZAxis = false;
+    private final BlockFace facing;
+
+    /**
+     * Instantiates a vector operation to rotate vectors in the direction of a sign face
+     *
+     * <p>Gate structures have their relative location represented by a vector where x = outwards, y = down and
+     * z = right. The vector operation rotates the given vectors so that "outwards" is going the same direction as the
+     * given sign face.</p>
+     *
+     * @param signFace <p>The sign face of a gate's sign</p>
+     */
+    public SimpleVectorOperation(BlockFace signFace) {
+        if (normalAxes.isEmpty()) {
+            initializeIrisNormalAxes();
+            initializeOperations();
+        }
+
+        this.facing = signFace;
+        this.normalAxis = normalAxes.get(signFace);
+    }
+
+    /**
+     * Gets the block face of a sign given upon instantiation
+     *
+     * @return <p>The block face of a sign given upon instantiation</p>
+     */
+    public BlockFace getFacing() {
+        return facing;
+    }
+
+    /**
+     * Gets the normal axis orthogonal to the opening plane
+     *
+     * <p>Said another way, get the axis going directly towards or away from a stargate's entrance.</p>
+     *
+     * @return <p>The normal axis orthogonal to the iris plane</p>
+     */
+    public Axis getNormalAxis() {
+        return normalAxis;
+    }
+
+    /**
+     * Sets whether to flip the Z- axis
+     *
+     * @param flipZAxis <p>Whether to flip the z-axis</p>
+     */
+    public void setFlipZAxis(boolean flipZAxis) {
+        this.flipZAxis = flipZAxis;
+    }
+
+    /**
+     * Performs this vector operation on the given vector
+     *
+     * <p>Inverse operation of doInverse; A vector operation that rotates around the origin, and flips the z-axis.
+     * Does not permute input vector</p>
+     *
+     * @param vector <p>The vector to perform the operation on</p>
+     * @return vector <p>A new vector with the operation applied</p>
+     */
+    public Vector performOperation(Vector vector) {
+        Vector clone = vector.clone();
+        clone.rotateAroundAxis(rotationAxes.get(facing), rotationAngles.get(facing));
+        if (flipZAxis) {
+            clone.setZ(-clone.getZ());
+        }
+        return clone;
+    }
+
+    /**
+     * Performs the reverse of this vector operation on the given vector
+     *
+     * <p>Inverse operation of doOperation; A vector operation that rotates around
+     * the origin and flips the z-axis. Does not permute input vector</p>
+     *
+     * @param vector <p>The vector to perform the inverse operation on</p>
+     * @return vector <p>A new vector with the inverse operation applied</p>
+     */
+    public Vector performInverseOperation(Vector vector) {
+        Vector clone = vector.clone();
+        if (flipZAxis) {
+            clone.setZ(-clone.getZ());
+        }
+        return clone.rotateAroundAxis(rotationAxes.get(facing), -rotationAngles.get(facing));
+    }
+
+    /**
+     * Performs the reverse of this vector operation on the given vector
+     *
+     * <p>Inverse operation of doOperation; A vector operation that rotates around
+     * the origin and flips the z-axis. Does not permute input vector</p>
+     *
+     * @param vector <p>The vector to perform the inverse operation on</p>
+     * @return vector <p>A new vector with the inverse operation applied</p>
+     */
+    public BlockVector performInverseOperation(BlockVector vector) {
+        return performInverseOperation((Vector) vector).toBlockVector();
+    }
+
+    /**
+     * Initializes the operations used for rotating to each block-face
+     */
+    private static void initializeOperations() {
+        Map<Axis, Vector> axisVectors = new HashMap<>();
+        axisVectors.put(Axis.Y, new Vector(0, 1, 0));
+        axisVectors.put(Axis.X, new Vector(1, 0, 0));
+        axisVectors.put(Axis.Z, new Vector(0, 0, 1));
+
+        //Use the cross product to find the correct axis
+        for (BlockFace face : normalAxes.keySet()) {
+            Vector crossProduct = face.getDirection().crossProduct(defaultDirection.getDirection());
+            if (face == defaultDirection || face == defaultDirection.getOppositeFace()) {
+                rotationAxes.put(face, axisVectors.get(defaultVerticalAxis));
+            } else if (Math.abs(crossProduct.getZ()) > 0) {
+                rotationAxes.put(face, axisVectors.get(Axis.Z));
+            } else if (Math.abs(crossProduct.getY()) > 0) {
+                rotationAxes.put(face, axisVectors.get(Axis.Y));
+            } else {
+                rotationAxes.put(face, axisVectors.get(Axis.X));
+            }
+        }
+
+        calculateRotations();
+    }
+
+    /**
+     * Calculates the required rotations based on the default rotation
+     */
+    private static void calculateRotations() {
+        double halfRotation = Math.PI;
+        double quarterRotation = halfRotation / 2;
+
+        Vector defaultDirectionVector = defaultDirection.getDirection();
+        boolean defaultDirectionPositive = defaultDirectionVector.getX() + defaultDirectionVector.getY() +
+                defaultDirectionVector.getZ() > 0;
+
+        for (BlockFace blockFace : normalAxes.keySet()) {
+            if (defaultDirection == blockFace) {
+                //The default direction requires no rotation
+                rotationAngles.put(blockFace, 0d);
+            } else if (defaultDirection.getOppositeFace() == blockFace) {
+                //The opposite direction requires a half rotation
+                rotationAngles.put(blockFace, halfRotation);
+            } else {
+                //All the other used directions require a quarter rotation
+                Vector faceDirectionVector = blockFace.getDirection();
+                boolean faceDirectionPositive = faceDirectionVector.getX() + faceDirectionVector.getY() +
+                        faceDirectionVector.getZ() > 0;
+                double rotation = defaultDirectionPositive && faceDirectionPositive ? quarterRotation : -quarterRotation;
+                rotationAngles.put(blockFace, rotation);
+            }
+        }
+    }
+
+    /**
+     * Initializes the iris normal axes corresponding to each block face
+     */
+    private static void initializeIrisNormalAxes() {
+        normalAxes.put(BlockFace.EAST, Axis.Z);
+        normalAxes.put(BlockFace.WEST, Axis.Z);
+        normalAxes.put(BlockFace.NORTH, Axis.X);
+        normalAxes.put(BlockFace.SOUTH, Axis.X);
+        normalAxes.put(BlockFace.UP, Axis.Y);
+        normalAxes.put(BlockFace.DOWN, Axis.Y);
+    }
+
+}
diff --git a/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java b/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
index 925764d..81bddef 100644
--- a/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
+++ b/src/main/java/net/knarcraft/stargate/container/RelativeBlockVector.java
@@ -1,5 +1,7 @@
 package net.knarcraft.stargate.container;
 
+import org.bukkit.util.Vector;
+
 /**
  * This stores a block location as a vector relative to a position
  *
@@ -70,6 +72,15 @@ public class RelativeBlockVector {
         }
     }
 
+    /**
+     * Gets a relative vector in the real space representing this relative block vector
+     *
+     * @return <p>A vector representing this relative block vector</p>
+     */
+    public Vector toVector() {
+        return new Vector(this.right, -this.down, this.out);
+    }
+
     /**
      * Gets a relative block vector which is this inverted (pointing in the opposite direction)
      *
diff --git a/src/main/java/net/knarcraft/stargate/portal/Portal.java b/src/main/java/net/knarcraft/stargate/portal/Portal.java
index 93e664f..6d1f159 100644
--- a/src/main/java/net/knarcraft/stargate/portal/Portal.java
+++ b/src/main/java/net/knarcraft/stargate/portal/Portal.java
@@ -1,5 +1,6 @@
 package net.knarcraft.stargate.portal;
 
+import net.knarcraft.stargate.SimpleVectorOperation;
 import net.knarcraft.stargate.container.BlockLocation;
 import net.knarcraft.stargate.container.RelativeBlockVector;
 import net.knarcraft.stargate.portal.property.PortalLocation;
@@ -8,6 +9,7 @@ import net.knarcraft.stargate.portal.property.PortalOptions;
 import net.knarcraft.stargate.portal.property.PortalOwner;
 import net.knarcraft.stargate.portal.property.PortalStructure;
 import net.knarcraft.stargate.portal.property.gate.Gate;
+import net.knarcraft.stargate.utility.DirectionHelper;
 import net.md_5.bungee.api.ChatColor;
 import org.bukkit.World;
 import org.bukkit.entity.Player;
@@ -23,6 +25,7 @@ public class Portal {
     private final String cleanName;
     private final String network;
     private final String cleanNetwork;
+    private final SimpleVectorOperation vectorOperation;
 
     private final PortalOwner portalOwner;
     private boolean isRegistered;
@@ -59,6 +62,7 @@ public class Portal {
         this.portalActivator = portalOpener.getPortalActivator();
         this.cleanName = cleanString(name);
         this.cleanNetwork = cleanString(network);
+        this.vectorOperation = new SimpleVectorOperation(DirectionHelper.getBlockFaceFromYaw(portalLocation.getYaw()));
     }
 
     /**
@@ -294,7 +298,7 @@ public class Portal {
      * @return <p>The block at the given relative position</p>
      */
     public BlockLocation getBlockAt(RelativeBlockVector vector) {
-        return getTopLeft().getRelativeLocation(vector, getYaw());
+        return (BlockLocation) getTopLeft().clone().add(vectorOperation.performOperation(vector.toVector()));
     }
 
     /**