Vector.java

/*
 * Copyright © 2014 - 2021 Leipzig University (Database Research Group)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gradoop.flink.model.impl.operators.layouting.util;

import org.gradoop.common.model.impl.pojo.EPGMVertex;
import org.gradoop.flink.model.impl.operators.layouting.LayoutingAlgorithm;

import java.io.Serializable;


/**
 * Simple helper-class for some vector-math.
 * All math-operations will return a new Vector (instead of modifying the existing vector). This
 * prevents strange side-effects when performing complex computations.
 */
public class Vector implements Serializable {

  /**
   * X-coordinate of vector
   */
  private double x;
  /**
   * Y-coordinate of vector
   */
  private double y;

  /**
   * Construct a vector from x and y coordinates
   *
   * @param x X-Coordinate of the new vector
   * @param y Y-Coordinate of the new vector
   */
  public Vector(double x, double y) {
    this.x = x;
    this.y = y;
    check();
  }

  /**
   * Construct new zero-Vector
   */
  public Vector() {
    x = 0d;
    y = 0d;
  }

  /**
   * Ensure that this vector does not contain any NaN-values.
   * Throws IllegalStateException if x or y is NaN.
   */
  private void check() {
    if (Double.isNaN(x) || Double.isNaN(y)) {
      throw new IllegalStateException(
        "A vector should never be NaN. There is probably a bug in " + "your code");
    }
  }

  /**
   * Create a vector from the coordinate-properties of a Vertex
   *
   * @param v The vertex to extract position coordinates from properties (X,Y)
   * @return A matching vector
   */
  public static Vector fromVertexPosition(EPGMVertex v) {
    double x = v.getPropertyValue(LayoutingAlgorithm.X_COORDINATE_PROPERTY).getInt();
    double y = v.getPropertyValue(LayoutingAlgorithm.Y_COORDINATE_PROPERTY).getInt();
    return new Vector(x, y);
  }

  /**
   * Set the coordinate-properties of a vertex to the values of this vector
   *
   * @param v The vertex that will receive the values of this vector as coordinates
   */
  public void setVertexPosition(EPGMVertex v) {
    v.setProperty(LayoutingAlgorithm.X_COORDINATE_PROPERTY, (int) x);
    v.setProperty(LayoutingAlgorithm.Y_COORDINATE_PROPERTY, (int) y);
  }

  /**
   * Substract another vector from this vector and return the result
   *
   * @param other Vector to substract
   * @return this-other
   */
  public Vector sub(Vector other) {
    return new Vector(x - other.x, y - other.y);
  }

  /**
   * Add another vector to this vector and return the result
   *
   * @param other The vector to add
   * @return this+other
   */
  public Vector add(Vector other) {
    return new Vector(x + other.x, y + other.y);
  }

  /**
   * Multiply this vector by a factor and return the result
   *
   * @param factor The factor to multiply this vector with
   * @return this*factor
   */
  public Vector mul(double factor) {
    return new Vector(x * factor, y * factor);
  }

  /**
   * Divide this vector by a factor and return the result
   *
   * @param factor The factor to divide this vector by
   * @return this/factor
   */
  public Vector div(double factor) {
    return new Vector(x / factor, y / factor);
  }

  /**
   * Calculate the euclidean distance between this vector and another vector
   *
   * @param other The other vector
   * @return Math.sqrt(Math.pow ( x - other.x, 2) + Math.pow(y - other.y, 2))
   */
  public double distance(Vector other) {
    return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
  }

  /**
   * Calculate the scalar-product of this vector
   *
   * @param other The other vector
   * @return Skalar-product of this and other
   */
  public double scalar(Vector other) {
    return x * other.x + y * other.y;
  }

  /**
   * Clamp this vector to a given length.
   * The returned vector will have the same orientation as this one, but will have at most a
   * length of maxLen.
   * If maxLen is smaller the the lenght of this Vector this vector (a copy of it) will be returned.
   *
   * @param maxLen maximum lenght of vector
   * @return This vector but constrained to the given length
   */
  public Vector clamped(double maxLen) {
    double len = magnitude();
    if (len == 0) {
      return new Vector(0, 0);
    }
    double newx = (x / len) * Math.min(len, maxLen);
    double newy = (y / len) * Math.min(len, maxLen);
    return new Vector(newx, newy);
  }

  /**
   * Normalize this vector.
   *
   * @return a vector with the same orientation as this one and a length of 1. If this vector is
   * (0,0) then (0,0) will be returned instead.
   */
  public Vector normalized() {
    double len = magnitude();
    if (len == 0) {
      return new Vector(0, 0);
    }
    double newx = x / len;
    double newy = y / len;
    return new Vector(newx, newy);
  }

  /**
   * Get the lenght of this vector
   *
   * @return euclidean length of this vector
   */
  public double magnitude() {
    return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  }

  /**
   * Confine this point to the given bounding-box.
   *
   * @param minX Bounding-box
   * @param maxX Bounding-box
   * @param minY Bounding-box
   * @param maxY Bounding-box
   * @return A vector that does not violate the given bounding box.
   */
  public Vector confined(double minX, double maxX, double minY, double maxY) {
    double newx = Math.min(Math.max(x, minX), maxX);
    double newy = Math.min(Math.max(y, minY), maxY);
    return new Vector(newx, newy);
  }

  /**
   * Calculates the (unsigned) angle between this vector and the given vecotr
   *
   * @param other The other vector
   * @return The angle
   */
  public double angle(Vector other) {
    return Math.acos(scalar(other) / (magnitude() * other.magnitude())) / Math.PI * 180;
  }

  /**
   * Rotate this vector anti-clockwise by the given angle (in degrees)
   *
   * @param angle The angle o rotate this vector by
   * @return The rotated vector
   */
  public Vector rotate(double angle) {
    angle = angle / 180 * Math.PI;
    double newx = x * Math.cos(angle) - y * Math.sin(angle);
    double newy = x * Math.sin(angle) + y * Math.cos(angle);
    return new Vector(newx, newy);
  }

  @Override
  public boolean equals(Object other) {
    if (other != null && other instanceof Vector) {
      Vector otherv = (Vector) other;
      return this.distance(otherv) < 0.000000001;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return ((int) x << 16) + (int) y;
  }

  @Override
  public String toString() {
    return "Vector{" + "x=" + x + ", y=" + y + '}';
  }

  /** X-coordinate of vector
   *
   * @return X coordinate of the vector
   **/
  public double getX() {
    return x;
  }

  /** Set X-coordinate of vector
   * @param x Set X coordinate of the vector
   **/
  public void setX(double x) {
    this.x = x;
    check();
  }

  /** Y-Coordinate of vector
   * @return Y coordinate of the vector
   */
  public double getY() {
    return y;
  }

  /** Set Y-Coordinate of vector
   * @param y Set Y coordinate of the vector
   **/
  public void setY(double y) {
    this.y = y;
    check();
  }

  /**
   * Set x and y at once
   *
   * @param x X to set
   * @param y y to set
   * @return This object for method-chaining
   */
  public Vector set(double x, double y) {
    this.x = x;
    this.y = y;
    check();
    return this;
  }

  /**
   * Copy the values of the other vector into this one
   *
   * @param other The other vector
   * @return this
   */
  public Vector set(Vector other) {
    x = other.x;
    y = other.y;
    check();
    return this;
  }

  /**
   * Reset this vector to 0
   *
   * @return This object for method-chaining
   */
  public Vector reset() {
    x = 0d;
    y = 0d;
    return this;
  }

  /**
   * Copy this object
   *
   * @return A copy of this object
   */
  public Vector copy() {
    return new Vector(x, y);
  }

  //-----------------------------------------------------------------------------------

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Substract another vector from this vector and return the result
   *
   * @param other Vector to substract
   * @return this-other
   */
  public Vector mSub(Vector other) {
    x -= other.x;
    y -= other.y;
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Add another vector to this vector and return the result
   *
   * @param other The vector to add
   * @return this+other
   */
  public Vector mAdd(Vector other) {
    x += other.x;
    y += other.y;
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Multiply this vector by a factor and return the result
   *
   * @param factor The factor to multiply this vector with
   * @return this*factor
   */
  public Vector mMul(double factor) {
    x *= factor;
    y *= factor;
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Divide this vector by a factor and return the result
   *
   * @param factor The factor to divide this vector by
   * @return this/factor
   */
  public Vector mDiv(double factor) {
    x /= factor;
    y /= factor;
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Clamp this vector to a given length.
   * The returned vector will have the same orientation as this one, but will have at most a
   * length of maxLen.
   * If maxLen is smaller the the lenght of this Vector this vector (a copy of it) will be returned.
   *
   * @param maxLen maximum lenght of vector
   * @return This vector but constrained to the given length
   */
  public Vector mClamped(double maxLen) {
    double len = magnitude();
    if (len == 0) {
      return new Vector(0, 0);
    }
    x = (x / len) * Math.min(len, maxLen);
    y = (y / len) * Math.min(len, maxLen);
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Normalize this vector.
   *
   * @return a vector with the same orientation as this one and a length of 1. If this vector is
   * (0,0) then (0,0) will be returned instead.
   */
  public Vector mNormalized() {
    double len = magnitude();
    if (len == 0) {
      x = 0.0;
      y = 0.0;
      return this;
    }
    x /= len;
    y /= len;
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant. Modifies this vector instead of creating a new one. BE CAREFUL!
   * Confine this point to the given bounding-box.
   *
   * @param minX Bounding-box
   * @param maxX Bounding-box
   * @param minY Bounding-box
   * @param maxY Bounding-box
   * @return A vector that does not violate the given bounding box.
   */
  public Vector mConfined(double minX, double maxX, double minY, double maxY) {
    x = Math.min(Math.max(x, minX), maxX);
    y = Math.min(Math.max(y, minY), maxY);
    check();
    return this;
  }

  /**
   * Alternative MUTATING variant.  Rotate this vector anti-clockwise by the given angle (in
   * degrees)
   *
   * @param angle The angle o rotate this vector by
   * @return The rotated vector
   */
  public Vector mRotate(double angle) {
    angle = angle / 180 * Math.PI;
    double newx = x * Math.cos(angle) - y * Math.sin(angle);
    double newy = x * Math.sin(angle) + y * Math.cos(angle);
    x = newx;
    y = newy;
    check();
    return this;
  }

}