Last active
December 2, 2020 08:35
-
-
Save avanderw/c0b7dc39a539039f0c6b2f0958072a86 to your computer and use it in GitHub Desktop.
Mutable 2D Vector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.avdw; | |
/** | |
* Single class mutable 2D vector. | |
* | |
* @version 2020-12-02 Started versioning | |
*/ | |
public class Vector2D { | |
protected static final Double EPSILON = 0.0000001; | |
protected static final Double EPSILON_SQR = EPSILON * EPSILON; | |
public double x; | |
public double y; | |
/** | |
* @param args empty, Vector2D, [ x, y ] | |
*/ | |
public Vector2D(Object... args) { | |
switch (args.length) { | |
case 0: | |
x = 0D; | |
y = 0D; | |
break; | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x = that.x; | |
this.y = that.y; | |
break; | |
} | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x = ((Number) args[0]).doubleValue(); | |
y = ((Number) args[1]).doubleValue(); | |
break; | |
} | |
default: | |
throw new RuntimeException(); | |
} | |
} | |
public Vector2D set(Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x = that.x; | |
this.y = that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x = ((Number) args[0]).doubleValue(); | |
y = ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D add(Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x += that.x; | |
this.y += that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x += ((Number) args[0]).doubleValue(); | |
y += ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D subtract(Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x -= that.x; | |
this.y -= that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x -= ((Number) args[0]).doubleValue(); | |
y -= ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D multiply(Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x *= that.x; | |
this.y *= that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x *= ((Number) args[0]).doubleValue(); | |
y *= ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D divide(Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
this.x /= that.x; | |
this.y /= that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x /= ((Number) args[0]).doubleValue(); | |
y /= ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D zero() { | |
x = 0D; | |
y = 0D; | |
return this; | |
} | |
public Vector2D scale(Double scale) { | |
return multiply(scale, scale); | |
} | |
public Double length() { | |
return Math.sqrt(x * x + y * y); | |
} | |
public Double lengthSqr() { | |
return x * x + y * y; | |
} | |
public Vector2D length(Double length) { | |
if (isZero()) { | |
if (Math.abs(length) < EPSILON) { | |
return this; | |
} else { | |
throw new RuntimeException("don't have a direction to scale in"); | |
} | |
} else { | |
return scale(length / length()); | |
} | |
} | |
public Vector2D normalise() { | |
return length(1D); | |
} | |
public Double distance(Object... args) { | |
double xd = 0D, yd = 0D; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
xd = this.x - that.x; | |
yd = this.y - that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
xd = x - ((Number) args[0]).doubleValue(); | |
yd = y - ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return Math.sqrt(xd * xd + yd * yd); | |
} | |
public Double distanceSqr(Object... args) { | |
double xd = 0D, yd = 0D; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
xd = this.x - that.x; | |
yd = this.y - that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
xd = x - ((Number) args[0]).doubleValue(); | |
yd = y - ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return xd * xd + yd * yd; | |
} | |
public Boolean equals(Object... args) { | |
boolean equal = true; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
equal = equal && Math.abs(this.x - that.x) > EPSILON; | |
equal = equal && Math.abs(this.y - that.y) > EPSILON; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
equal = equal && Math.abs(x - ((Number) args[0]).doubleValue()) > EPSILON; | |
equal = equal && Math.abs(y - ((Number) args[1]).doubleValue()) > EPSILON; | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return equal; | |
} | |
public Boolean isNormalized() { | |
return Math.abs(length() - 1) < EPSILON; | |
} | |
public Boolean isZero() { | |
return Math.abs(x) < EPSILON && Math.abs(y) < EPSILON; | |
} | |
public Boolean isNear(Object... args) { | |
return distanceSqr(args) < EPSILON_SQR; | |
} | |
public Boolean isWithin(Double epsilon, Object... args) { | |
return distanceSqr(args) < epsilon * epsilon; | |
} | |
public Boolean isValid() { | |
return !Double.isNaN(x) && !Double.isNaN(y) && Double.isFinite(x) && Double.isFinite(y); | |
} | |
public Double dot(Object... args) { | |
double dot = Double.NaN; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
dot = this.x * that.x + this.y * that.y; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
dot = x * ((Number) args[0]).doubleValue() + y * ((Number) args[1]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return dot; | |
} | |
public Double cross(Object... args) { | |
double cross = Double.NaN; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
cross = this.x * that.y - this.y * that.x; | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
cross = x * ((Number) args[1]).doubleValue() - y * ((Number) args[0]).doubleValue(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return cross; | |
} | |
public Boolean isNormalTo(Object... args) { | |
return dot(args) < EPSILON; | |
} | |
public Double angle() { | |
double ang = Math.atan2(y, x); | |
if (ang < 0) { | |
ang += Math.PI + Math.PI; | |
} | |
return ang; | |
} | |
public Vector2D angle(Double radians) { | |
final Double origLength = length(); | |
x = origLength * Math.cos(radians); | |
y = origLength * Math.sin(radians); | |
return this; | |
} | |
public Double angleBetween(Object... args) { | |
double angle = Double.NaN; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
angle = that.angle() - this.angle(); | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
angle = new Vector2D(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()).angle() - this.angle(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
if (angle > Math.PI) { | |
angle -= 2 * Math.PI; | |
} else if (angle < -Math.PI) { | |
angle += 2 * Math.PI; | |
} | |
return angle; | |
} | |
public Vector2D rotate(Double radians) { | |
final Double s = Math.sin(radians); | |
final Double c = Math.cos(radians); | |
double newX = x * c - y * s; | |
double newY = x * s + y * c; | |
x = newX; | |
y = newY; | |
return this; | |
} | |
public Vector2D normalRight() { | |
return new Vector2D(-y, x); | |
} | |
public Vector2D normalLeft() { | |
return new Vector2D(y, -x); | |
} | |
public Vector2D negate() { | |
x = -x; | |
y = -y; | |
return this; | |
} | |
public Vector2D lerp(Double t, Object... args) { | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
x = x + t * (that.x - x); | |
y = y + t * (that.y - y); | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
x = x + t * (((Number) args[0]).doubleValue() - x); | |
y = y + t * (((Number) args[1]).doubleValue() - y); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D slerp(Double t, Object... args) { | |
final Double cosTheta = dot(args); | |
final double theta = Math.acos(cosTheta); | |
final double sinTheta = Math.sin(theta); | |
final Double w1 = Math.sin((1 - t) * theta) / sinTheta; | |
final Double w2 = Math.sin(t * theta) / sinTheta; | |
final Vector2D to = new Vector2D(); | |
to.set(args); | |
scale(w1).add(to.scale(w2)); | |
return this; | |
} | |
public Vector2D reflect(Object... args) { | |
double d; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
d = 2 * (x * that.x + y * that.y); | |
subtract(d * that.x, d * that.y); | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
d = 2 * (x * ((Number) args[0]).doubleValue() + y * ((Number) args[1]).doubleValue()); | |
subtract(d * ((Number) args[0]).doubleValue(), d * ((Number) args[1]).doubleValue()); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
return this; | |
} | |
public Vector2D project(Object... args) { | |
double scalar = Double.NaN; | |
Vector2D projVector = null; | |
switch (args.length) { | |
case 1: | |
if (args[0] instanceof Vector2D) { | |
Vector2D that = (Vector2D) args[0]; | |
projVector = that; | |
scalar = dot(projVector) / projVector.lengthSqr(); | |
} | |
break; | |
case 2: | |
if (args[0] instanceof Number && args[1] instanceof Number) { | |
projVector = new Vector2D(args[0], args[1]); | |
scalar = dot(projVector) / projVector.lengthSqr(); | |
} | |
break; | |
default: | |
throw new RuntimeException(); | |
} | |
set(projVector); | |
scale(scalar); | |
return this; | |
} | |
public Vector2D offsetPolar(Double radius, Double angle) { | |
x += radius * Math.cos(angle); | |
y += radius * Math.sin(angle); | |
return this; | |
} | |
public Vector2D copy() { | |
return new Vector2D(this); | |
} | |
public static void swap(Vector2D a, Vector2D b) { | |
final Vector2D tmp = a.copy(); | |
a.set(b); | |
b.set(tmp); | |
} | |
@Override | |
public String toString() { | |
return "[" + x + "," + y + "]"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment