package edu.gatech.cs2340.trydent.math; import java.util.Arrays; /** * <strong>Developers using this library should use the Position and Vector * sub-classes rather than using this class directly. </strong> * <p> * This is the base vector class providing functionality required by points and * vectors. It's written using generics to allow end-users to easily intermingle * Positions and Vectors without code duplication, while keeping the concepts of * Positions and Vectors distinct to avoid code duplication. * <p> * <strong>All mathematical methods in this class, unless expressly stated * otherwise, are in-place (aka mutable), and return a reference to themselves. * </strong> * <p> * This is to enable convenient mathematical expressions, like: * <p> * {@code Position myPosition = new Position(3, 5).add(new Vector(5, 7)).scale(2.0);} * <p> * As well as equivalent expressions like: * * <pre> * Position myPosition = new Position(3, 5); * myPosition.add(new Vector(5, 7)); * myPosition.scale(2.0); * </pre> * * @author Garrett Malmquist * @param <T> * - generic type used for inheritance; developers using this library * need not worry about it. */ public abstract class BaseVector<T extends BaseVector<T>> { private double[] values; // Used by toString() private String stringOpen = "<"; private String stringClose = ">"; private String stringDelimit = ", "; /** * Creates an new vector with the given number of components, initialized to * the zero vector. * * @param components * the dimension of this vector (eg, 2 for 2D) */ public BaseVector(int components) { values = new double[components]; } /** * Creates a new 2D vector. */ public BaseVector() { this(2); // Default to 2D } /** * Creates a copy of this vector. * * @return the copy */ public abstract T copy(); /** * Sets all components to 'value'. * * @param value * floating point value * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T fill(double value) { Arrays.fill(values, value); return (T) this; } /** * Returns the number of components in this vector (aka the dimension of * this vector). Eg, for a 2D vector this will always be '2'. * * @return the number of components */ public int getComponentCount() { return values.length; } /** * Returns the value of the ith component. * * Eg, 'x' is 0, 'y' is 1. * * @param i * index of the desired component * @return the value of the desired component */ public double getComponent(int i) { if (i < 0 || i >= values.length) throw new IllegalComponentException(i); return values[i]; } /** * Sets the value of the ith component. * * @param i * index of the component to set. E.g., 'x' is 0, 'y' is 1. * @param value * the new value of the component */ public void setComponent(int i, double value) { if (i < 0 || i >= values.length) throw new IllegalComponentException(i); values[i] = value; } /** * Returns the x-component. Equivalent to {@code getComponent(0)}. * * @return x */ public double getX() { return values[0]; } /** * Returns the y-component. Equivalent to {@code getComponent(1)}. * * @return y */ public double getY() { return values[1]; } /** * Sets the x-component. Equivalent to {@code setComponent(0, value)}. * * @param v * the new value of x */ public void setX(double v) { values[0] = v; } /** * Sets the y-component. Equivalent to {@code setComponent(1, value)}. * * @param v * the new value of y */ public void setY(double v) { values[1] = v; } /** * Sets the components of this vector equal to the input parameters. * <p> * Eg, {@code set(0.0, 0.5)} would set x=0.0, y=0.5. * * @param components * (a variable number of double variables) * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T set(double... components) { for (int i = 0; i < components.length && i < values.length; i++) { values[i] = components[i]; } return (T) this; } /** * Sets the components of this vector to the components of the other vector. * * @param vector * the other vector. * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T set(BaseVector<?> vector) { for (int i = 0; i < values.length && i < vector.getComponentCount(); i++) { values[i] = vector.getComponent(i); } return (T) this; } /** * Adds the second vector to this vector. I.e.: * <p> * {@code this = this + other} * * @param other * - double values to add * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T add(double... other) { checkComponents(other); for (int i = 0; i < values.length && i < other.length; i++) { values[i] += other[i]; } return (T) this; } /** * Adds the second vector to this vector. I.e.: * <p> * {@code this = this + other} * * @param other * - other vector * @return the reference to this vector for method chaining convenience */ public T add(BaseVector<?> other) { return add(other.values); } /** * Adds the second vector, scaled by 'scale', to this vector. I.e.: * <p> * this = this + (scale * other). * * @param scale * - scalar number * @param other * - other vector * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T add(double scale, BaseVector<?> other) { checkComponents(other); for (int i = 0; i < values.length && i < other.values.length; i++) { values[i] += scale * other.values[i]; } return (T) this; } /** * Subtracts the second vector from this vector. I.e.: * <p> * {@code this = this - other} * * @param other * - double values to subtract * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T subtract(double... other) { checkComponents(other); for (int i = 0; i < values.length && i < other.length; i++) { values[i] -= other[i]; } return (T) this; } /** * Subtracts the second vector from this vector. I.e.: * <p> * {@code this = this - other} * * @param other * - other vector to subtract * @return the reference to this vector for method chaining convenience */ public T subtract(BaseVector<?> other) { return subtract(other.values); } /** * Subtracts the second vector, scaled by 'scale', from this vector. I.e.: * <p> * this = this - (scale * other). * * @param scale * scalar number * @param other * other vector * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T subtract(double scale, BaseVector<?> other) { checkComponents(other); for (int i = 0; i < values.length && i < other.values.length; i++) { values[i] -= scale * other.values[i]; } return (T) this; } /** * Scales this vectors components by the given amounts. If only one number * is passed, it will scale all components by the same value. * <p> * Example usages: * * <pre> * {@code * this.scale(0.5) // scales whole vector by 0.5 * this.scale(0.5, 0.75) // scales x by 0.5 and y by 0.75. * } * </pre> * * @param scale * the values to element-wise multiply this vector by * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T scale(double... scale) { if (scale.length == 1) { for (int i = 0; i < values.length; i++) values[i] *= scale[0]; } else { for (int i = 0; i < values.length && i < scale.length; i++) { values[i] *= scale[i]; } } return (T) this; } /** * Scales the components of this vector by the components of the other * vector. I.e., * * <pre> * {@code * this.x = this.x * other.x; * this.y = this.y * other.y; * } * </pre> * * @param other * the vector to element-wise multiply the components of this * vector by * @return the reference to this vector for method chaining convenience */ public T scale(BaseVector<?> other) { return scale(other.values); } /** * Rotates this vector's x,y components counter-clockwise. * <p> * (In the case of points, rotates in 2D about the origin). * * @param angle * amount to rotate by, in degrees. * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T rotate2D(double angle) { if (magnitudeSquared() == 0) return (T) this; double theta = Math.atan2(getY(), getX()); angle = Math.toRadians(angle); double m = magnitude(); double c = Math.cos(theta + angle); double s = Math.sin(theta + angle); return set(m * c, m * s); } /** * Rotates this vector 90 counter-clockwise in 2D. * * @return the reference to this vector for method chaining convenience */ public T rotate90() { return set(-getY(), getX()); } /** * Returns the magnitude (aka length, aka norm) of this vector. * * @return the magnitude */ public double magnitude() { return Math.sqrt(magnitudeSquared()); } /** * Returns the squared magnitude (magnitude^2) of this vector. * * @return the squared magnitude */ public double magnitudeSquared() { return dot(this); } /** * Normalizes this vector (aka makes it unit length, aka makes its length = * 1). * * @return the reference to this vector for method chaining convenience */ @SuppressWarnings("unchecked") public T normalize() { double m2 = magnitudeSquared(); if (m2 == 0) return (T) this; // Can't normalize the 0-vector. return scale(1.0 / Math.sqrt(m2)); } /** * Returns the linear interpolation between this vector and the other vector * by an amount t. (Advanced functionality). * * @param t * interpolation parameter (0 will give this vector, 1 will give * the other vector, 0.5 is the midpoint.) * @param other * the other vector * @return the interpolated point */ @SuppressWarnings("unchecked") public T lerp(double t, BaseVector<?> other) { checkComponents(other); for (int i = 0; i < values.length && i < other.values.length; i++) { values[i] = (1.0 - t) * values[i] + t * other.values[i]; } return (T) this; } /** * Returns this vector projected onto the given axis. * <p> * Mathematically, this method is {@code V = dot(V, axis) * axis}; * * @param otherAxis * the (assumedly unit) vector to project this vector onto. * @return the reference to this vector for method chaining convenience */ public T projectToAxis(BaseVector<?> otherAxis) { return set(otherAxis.copy().normalize().scale(dot(otherAxis))); } /** * Removes the `otherAxis' component of this vector. * <p> * Mathematically, this method is {@code V = V - dot(V, axis) * axis}; * * @param otherAxis * the (assumedly unit) vector to project this vector off of. * @return the reference to this vector for method chaining convenience */ public T projectOffAxis(BaseVector<?> otherAxis) { return subtract(otherAxis.copy().normalize().scale(dot(otherAxis))); } /** * Returns the dot-product between the two vectors. * * @param other * the other vector * @return the dot-product */ public double dot(BaseVector<?> other) { return dot(other.values); } /** * Returns the dot-product between the two vectors. * * @param other * other vector, specified as a list of doubles. * @return the dot-product */ public double dot(double... other) { checkComponents(other); double total = 0; for (int i = 0; i < values.length && i < other.length; i++) { total += values[i] * other[i]; } return total; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(stringOpen); for (int i = 0; i < values.length; i++) { if (i > 0) sb.append(stringDelimit); double value = values[i]; if (Math.abs(value) < 1e-6) sb.append("0.0"); else { value = Math.round(value * 1.0e6) / (1.0e6); sb.append(value); } } sb.append(stringClose); return sb.toString(); } protected void setStringOpen(String open) { this.stringOpen = open; } protected void setStringClose(String close) { this.stringClose = close; } protected void setStringDelimit(String delimit) { this.stringDelimit = delimit; } private void checkComponents(double... values) { if (values.length != this.values.length) throw new VectorMismatchException("Number of components must be " + this.values.length + ", got " + values.length); } private void checkComponents(BaseVector<?> vector) { checkComponents(vector.values); } }