637 lines
19 KiB
C#
637 lines
19 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Xml.Serialization;
|
|
|
|
namespace JuicyGraphics.Mathematics {
|
|
using Exceptions;
|
|
|
|
[ImmutableObject(true), Serializable]
|
|
public struct vec2
|
|
: IComparable, IComparable<vec2>, IEquatable<vec2>, IFormattable {
|
|
|
|
private readonly double _x;
|
|
private readonly double _y;
|
|
|
|
public vec2(double x, double y) {
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
|
|
public vec2(double[] xy) {
|
|
if (xy.Length == 2) {
|
|
_x = xy[0];
|
|
_y = xy[1];
|
|
}
|
|
else {
|
|
throw new ArgumentException(TWO_COMPONENTS);
|
|
}
|
|
}
|
|
|
|
public vec2(vec2 v1) {
|
|
_x = v1.x;
|
|
_y = v1.y;
|
|
}
|
|
|
|
public double x {
|
|
get {
|
|
return _x;
|
|
}
|
|
}
|
|
|
|
public double y {
|
|
get {
|
|
return _y;
|
|
}
|
|
}
|
|
|
|
public vec2 xx {
|
|
get {
|
|
return new vec2(_x, _x);
|
|
}
|
|
}
|
|
|
|
public vec2 xy {
|
|
get {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public vec2 yx {
|
|
get {
|
|
return new vec2(_y, _x);
|
|
}
|
|
}
|
|
|
|
public vec2 yy {
|
|
get {
|
|
return new vec2(_y, _y);
|
|
}
|
|
}
|
|
|
|
public vec2 normal {
|
|
get {
|
|
return normalize();
|
|
}
|
|
}
|
|
|
|
public double magnitude {
|
|
get {
|
|
return Math.Sqrt(sumComponentSqrs());
|
|
}
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public double[] array {
|
|
get {
|
|
return new[] { x, y };
|
|
}
|
|
}
|
|
|
|
public double this[int index] {
|
|
get {
|
|
switch (index) {
|
|
case 0:
|
|
return _x;
|
|
case 1:
|
|
return _y;
|
|
default:
|
|
throw new ArgumentException(TWO_COMPONENTS, "index");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static vec2 operator +(vec2 v1, vec2 v2) {
|
|
return new vec2(
|
|
v1.x + v2.x,
|
|
v1.y + v2.y);
|
|
}
|
|
|
|
public static vec2 operator -(vec2 v1, vec2 v2) {
|
|
return new vec2(
|
|
v1.x - v2.x,
|
|
v1.y - v2.y);
|
|
}
|
|
|
|
public static vec2 operator *(vec2 v1, double s2) {
|
|
return
|
|
new vec2(
|
|
v1.x * s2,
|
|
v1.y * s2);
|
|
}
|
|
|
|
public static vec2 operator *(double s1, vec2 v2) {
|
|
return v2 * s1;
|
|
}
|
|
|
|
public static vec2 operator /(vec2 v1, double s2) {
|
|
return new vec2(
|
|
v1.x / s2,
|
|
v1.y / s2);
|
|
}
|
|
|
|
public static vec2 operator -(vec2 v1) {
|
|
return new vec2(
|
|
-v1.x,
|
|
-v1.y);
|
|
}
|
|
|
|
public static vec2 operator +(vec2 v1) {
|
|
return new vec2(
|
|
+v1.x,
|
|
+v1.y);
|
|
}
|
|
|
|
public static bool operator <(vec2 v1, vec2 v2) {
|
|
return v1.sumComponentSqrs() < v2.sumComponentSqrs();
|
|
}
|
|
|
|
public static bool operator >(vec2 v1, vec2 v2) {
|
|
return v1.sumComponentSqrs() > v2.sumComponentSqrs();
|
|
}
|
|
|
|
public static bool operator <=(vec2 v1, vec2 v2) {
|
|
return v1.sumComponentSqrs() <= v2.sumComponentSqrs();
|
|
}
|
|
|
|
public static bool operator >=(vec2 v1, vec2 v2) {
|
|
return v1.sumComponentSqrs() >= v2.sumComponentSqrs();
|
|
}
|
|
|
|
public static bool operator ==(vec2 v1, vec2 v2) {
|
|
return
|
|
v1.x == v2.x &&
|
|
v1.y == v2.y;
|
|
}
|
|
|
|
public static bool operator !=(vec2 v1, vec2 v2) {
|
|
return !(v1 == v2);
|
|
}
|
|
|
|
public static vec2 scale(vec2 vector, double magnitude) {
|
|
if (magnitude < 0) {
|
|
throw new ArgumentOutOfRangeException("magnitude", magnitude, NEGATIVE_magnitude);
|
|
}
|
|
|
|
if (vector == new vec2(0, 0)) {
|
|
throw new ArgumentException(ORIGIN_VECTOR_magnitude, "vector");
|
|
}
|
|
|
|
return vector * (magnitude / vector.magnitude);
|
|
}
|
|
|
|
public vec2 scale(double magnitude) {
|
|
return vec2.scale(this, magnitude);
|
|
}
|
|
|
|
public static double dotProduct(vec2 v1, vec2 v2) {
|
|
return
|
|
v1.x * v2.x +
|
|
v1.y * v2.y;
|
|
}
|
|
|
|
public double dotProduct(vec2 other) {
|
|
return dotProduct(this, other);
|
|
}
|
|
|
|
public static vec2 normalize(vec2 v1) {
|
|
if (double.IsInfinity(v1.magnitude)) {
|
|
v1 = normalizeSpecialCasesOrOrigional(v1);
|
|
|
|
if (v1.isNaN()) {
|
|
throw new normalizeVectorException(NORMALIZE_Inf);
|
|
}
|
|
}
|
|
|
|
if (v1.magnitude == 0) {
|
|
throw new normalizeVectorException(NORMALIZE_0);
|
|
}
|
|
|
|
if (v1.isNaN()) {
|
|
throw new normalizeVectorException(NORMALIZE_NaN);
|
|
}
|
|
|
|
return normalizeOrNaN(v1);
|
|
}
|
|
|
|
public static vec2 normalizeOrDefault(vec2 v1) {
|
|
v1 = normalizeSpecialCasesOrOrigional(v1);
|
|
|
|
if (v1.magnitude == 0) {
|
|
return origin;
|
|
}
|
|
|
|
if (v1.isNaN()) {
|
|
return NaN;
|
|
}
|
|
|
|
return normalizeOrNaN(v1);
|
|
}
|
|
|
|
public vec2 normalize() {
|
|
return normalize(this);
|
|
}
|
|
|
|
public vec2 normalizeOrDefault() {
|
|
return normalizeOrDefault(this);
|
|
}
|
|
|
|
private static vec2 normalizeOrNaN(vec2 v1) {
|
|
double inverse = 1 / v1.magnitude;
|
|
|
|
return new vec2(
|
|
v1.x * inverse,
|
|
v1.y * inverse);
|
|
}
|
|
|
|
private static vec2 normalizeSpecialCasesOrOrigional(vec2 v1) {
|
|
if (double.IsInfinity(v1.magnitude)) {
|
|
var x = v1.x == 0 ? 0 : v1.x == -0 ? -0 : double.IsPositiveInfinity(v1.x) ? 1 : double.IsNegativeInfinity(v1.x) ? -1 : double.NaN;
|
|
var y = v1.y == 0 ? 0 : v1.y == -0 ? -0 : double.IsPositiveInfinity(v1.y) ? 1 : double.IsNegativeInfinity(v1.y) ? -1 : double.NaN;
|
|
|
|
return new vec2(x, y);
|
|
}
|
|
|
|
return v1;
|
|
}
|
|
|
|
public static vec2 interpolate(vec2 v1, vec2 v2, double control, bool allowExtrapolation) {
|
|
if (!allowExtrapolation && (control > 1 || control < 0)) {
|
|
throw new ArgumentOutOfRangeException(
|
|
"control",
|
|
control,
|
|
INTERPOLATION_RANGE + "\n" + ARGUMENT_VALUE + control);
|
|
}
|
|
|
|
return new vec2(
|
|
v1.x * (1 - control) + v2.x * control,
|
|
v1.y * (1 - control) + v2.y * control);
|
|
}
|
|
|
|
public static vec2 interpolate(vec2 v1, vec2 v2, double control) {
|
|
return interpolate(v1, v2, control, false);
|
|
}
|
|
|
|
public vec2 interpolate(vec2 other, double control) {
|
|
return interpolate(this, other, control);
|
|
}
|
|
|
|
public vec2 interpolate(vec2 other, double control, bool allowExtrapolation) {
|
|
return interpolate(this, other, control);
|
|
}
|
|
|
|
public static double distance(vec2 v1, vec2 v2) {
|
|
return Math.Sqrt(
|
|
(v1.x - v2.x) * (v1.x - v2.x) +
|
|
(v1.y - v2.y) * (v1.y - v2.y));
|
|
}
|
|
|
|
public double distance(vec2 other) {
|
|
return distance(this, other);
|
|
}
|
|
|
|
public static double angle(vec2 v1, vec2 v2) {
|
|
if (v1 == v2) {
|
|
return 0;
|
|
}
|
|
|
|
return
|
|
Math.Acos(
|
|
Math.Min(1.0f, normalizeOrDefault(v1).dotProduct(normalizeOrDefault(v2))));
|
|
}
|
|
|
|
public double angle(vec2 other) {
|
|
return angle(this, other);
|
|
}
|
|
|
|
public static vec2 max(vec2 v1, vec2 v2) {
|
|
return v1 >= v2 ? v1 : v2;
|
|
}
|
|
|
|
public vec2 max(vec2 other) {
|
|
return max(this, other);
|
|
}
|
|
|
|
public static vec2 min(vec2 v1, vec2 v2) {
|
|
return v1 <= v2 ? v1 : v2;
|
|
}
|
|
|
|
public vec2 min(vec2 other) {
|
|
return min(this, other);
|
|
}
|
|
|
|
public static vec2 rotate(vec2 v1, double rad) {
|
|
double x = (v1.x * Math.Cos(rad)) - (v1.y * Math.Sin(rad));
|
|
double y = (v1.x * Math.Sin(rad)) + (v1.y * Math.Cos(rad));
|
|
return new vec2(x, y);
|
|
}
|
|
|
|
public vec2 rotate(double rad) {
|
|
return rotate(this, rad);
|
|
}
|
|
|
|
public static vec2 rotate(vec2 v1, double xOff, double yOff, double rad) {
|
|
double x = (v1.x * Math.Cos(rad)) - (v1.y * Math.Sin(rad)) + (xOff * (1 - Math.Cos(rad)) + yOff * Math.Sin(rad));
|
|
double y = (v1.x * Math.Sin(rad)) + (v1.y * Math.Cos(rad)) + (yOff * (1 - Math.Cos(rad)) - xOff * Math.Sin(rad));
|
|
return new vec2(x, y);
|
|
}
|
|
|
|
public vec2 rotate(double xOff, double yOff, double rad) {
|
|
return rotate(this, xOff, yOff, rad);
|
|
}
|
|
|
|
public static vec2 projection(vec2 v1, vec2 v2) {
|
|
return new vec2(v2 * (v1.dotProduct(v2) / Math.Pow(v2.magnitude, 2)));
|
|
}
|
|
|
|
public vec2 projection(vec2 direction) {
|
|
return projection(this, direction);
|
|
}
|
|
|
|
public static vec2 rejection(vec2 v1, vec2 v2) {
|
|
return v1 - v1.projection(v2);
|
|
}
|
|
|
|
public vec2 rejection(vec2 direction) {
|
|
return rejection(this, direction);
|
|
}
|
|
|
|
public vec2 reflection(vec2 reflector) {
|
|
this = vec2.reflection(this, reflector);
|
|
return this;
|
|
}
|
|
|
|
public static vec2 reflection(vec2 v1, vec2 v2) {
|
|
if (Math.Abs(Math.Abs(v1.angle(v2)) - Math.PI / 2) < Double.Epsilon) {
|
|
return -v1;
|
|
}
|
|
|
|
vec2 retval = new vec2(2 * v1.projection(v2) - v1);
|
|
return retval.scale(v1.magnitude);
|
|
}
|
|
|
|
public static Double abs(vec2 v1) {
|
|
return v1.magnitude;
|
|
}
|
|
|
|
public double abs() {
|
|
return magnitude;
|
|
}
|
|
|
|
public static double sumComponents(vec2 v1) {
|
|
return v1.x + v1.y;
|
|
}
|
|
|
|
public double sumComponents() {
|
|
return sumComponents(this);
|
|
}
|
|
|
|
public static double sumComponentSqrs(vec2 v1) {
|
|
vec2 v2 = sqrComponents(v1);
|
|
return v2.sumComponents();
|
|
}
|
|
|
|
public double sumComponentSqrs() {
|
|
return sumComponentSqrs(this);
|
|
}
|
|
|
|
public static vec2 powComponents(vec2 v1, double power) {
|
|
return new vec2(
|
|
Math.Pow(v1.x, power),
|
|
Math.Pow(v1.y, power));
|
|
}
|
|
|
|
public vec2 powComponents(double power) {
|
|
return powComponents(this, power);
|
|
}
|
|
|
|
public static vec2 sqrtComponents(vec2 v1) {
|
|
return new vec2(
|
|
Math.Sqrt(v1.x),
|
|
Math.Sqrt(v1.y));
|
|
}
|
|
|
|
public vec2 sqrtComponents() {
|
|
return sqrtComponents(this);
|
|
}
|
|
|
|
public static vec2 sqrComponents(vec2 v1) {
|
|
return new vec2(
|
|
v1.x * v1.x,
|
|
v1.y * v1.y);
|
|
}
|
|
|
|
public vec2 sqrComponents() {
|
|
return sqrComponents(this);
|
|
}
|
|
|
|
public static vec2 round(vec2 v1) {
|
|
return new vec2(Math.Round(v1.x), Math.Round(v1.y));
|
|
}
|
|
|
|
public static vec2 round(vec2 v1, int digits) {
|
|
return new vec2(Math.Round(v1.x, digits), Math.Round(v1.y, digits));
|
|
}
|
|
|
|
public static vec2 round(vec2 v1, MidpointRounding mode) {
|
|
return new vec2(Math.Round(v1.x, mode), Math.Round(v1.y, mode));
|
|
}
|
|
|
|
public static vec2 round(vec2 v1, int digits, MidpointRounding mode) {
|
|
return new vec2(Math.Round(v1.x, digits, mode), Math.Round(v1.y, digits, mode));
|
|
}
|
|
|
|
public vec2 round() {
|
|
return new vec2(Math.Round(x), Math.Round(y));
|
|
}
|
|
|
|
public vec2 round(int digits) {
|
|
return new vec2(Math.Round(x, digits), Math.Round(y, digits));
|
|
}
|
|
|
|
public vec2 round(MidpointRounding mode) {
|
|
return new vec2(Math.Round(x, mode), Math.Round(y, mode));
|
|
}
|
|
|
|
public vec2 round(int digits, MidpointRounding mode) {
|
|
return new vec2(Math.Round(x, digits, mode), Math.Round(y, digits, mode));
|
|
}
|
|
|
|
public override string ToString() {
|
|
return ToString(null, null);
|
|
}
|
|
|
|
public string ToVerbString() {
|
|
string output = null;
|
|
|
|
if (isUnitVector()) {
|
|
output += UNIT_VECTOR;
|
|
}
|
|
else {
|
|
output += POSITIONAL_VECTOR;
|
|
}
|
|
|
|
output += string.Format("( x={0}, y={1} )", x, y);
|
|
output += magnitude + magnitude;
|
|
|
|
return output;
|
|
}
|
|
|
|
public string ToString(string format, IFormatProvider formatProvider) {
|
|
if (format == null || format == "") {
|
|
return string.Format("({0}, {1})", x, y);
|
|
}
|
|
|
|
char firstChar = format[0];
|
|
string remainder = null;
|
|
|
|
if (format.Length > 1) {
|
|
remainder = format.Substring(1);
|
|
}
|
|
|
|
switch (firstChar) {
|
|
case 'x': return x.ToString(remainder, formatProvider);
|
|
case 'y': return y.ToString(remainder, formatProvider);
|
|
default:
|
|
return String.Format(
|
|
"({0}, {1})",
|
|
x.ToString(format, formatProvider),
|
|
y.ToString(format, formatProvider));
|
|
}
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = x.GetHashCode();
|
|
hashCode = (hashCode * 397) ^ y.GetHashCode();
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
public override bool Equals(object other) {
|
|
if (other is vec2) {
|
|
return other.Equals(this);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool Equals(object other, double tolerance) {
|
|
if (other is vec2) {
|
|
return Equals((vec2)other, tolerance);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool Equals(vec2 other) {
|
|
return
|
|
x.Equals(other.x) &&
|
|
y.Equals(other.y);
|
|
}
|
|
|
|
public bool Equals(vec2 other, double tolerance) {
|
|
return
|
|
x.almostEqualsWithAbsTolerance(other.x, tolerance) &&
|
|
y.almostEqualsWithAbsTolerance(other.y, tolerance);
|
|
}
|
|
|
|
public int CompareTo(vec2 other) {
|
|
if (this < other) {
|
|
return -1;
|
|
}
|
|
|
|
if (this > other) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public int CompareTo(object other) {
|
|
if (other is vec2) {
|
|
return CompareTo((vec2)other);
|
|
}
|
|
|
|
throw new ArgumentException(
|
|
NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(),
|
|
"other");
|
|
}
|
|
|
|
public int CompareTo(vec2 other, double tolerance) {
|
|
var bothInfinite = double.IsInfinity(sumComponentSqrs()) && double.IsInfinity(other.sumComponentSqrs());
|
|
|
|
if (Equals(other, tolerance) || bothInfinite) {
|
|
return 0;
|
|
}
|
|
|
|
if (this < other) {
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
public int CompareTo(object other, double tolerance) {
|
|
if (other is vec2) {
|
|
return CompareTo((vec2)other, tolerance);
|
|
}
|
|
|
|
throw new ArgumentException(
|
|
NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(),
|
|
"other");
|
|
}
|
|
|
|
public static bool isUnitVector(vec2 v1, double tolerance) {
|
|
return v1.magnitude.almostEqualsWithAbsTolerance(1, tolerance);
|
|
}
|
|
|
|
public static bool isUnitVector(vec2 v1) {
|
|
return v1.magnitude == 1;
|
|
}
|
|
|
|
public bool isUnitVector() {
|
|
return isUnitVector(this);
|
|
}
|
|
|
|
public bool isUnitVector(double tolerance) {
|
|
return isUnitVector(this, tolerance);
|
|
}
|
|
|
|
public static bool isNaN(vec2 v1) {
|
|
return double.IsNaN(v1.x) || double.IsNaN(v1.y);
|
|
}
|
|
|
|
public bool isNaN() {
|
|
return isNaN(this);
|
|
}
|
|
|
|
public static readonly vec2 origin = new vec2(0, 0);
|
|
public static readonly vec2 right = new vec2(1, 0);
|
|
public static readonly vec2 left = new vec2(-1, 0);
|
|
public static readonly vec2 down = new vec2(0, 1);
|
|
public static readonly vec2 up = new vec2(0, -1);
|
|
|
|
|
|
private const string NORMALIZE_NaN = "Cannot normalize a vector when it's magnitude is NaN";
|
|
private const string NORMALIZE_0 = "Cannot normalize a vector when it's magnitude is zero";
|
|
private const string NORMALIZE_Inf = "Cannot normalize a vector when it's magnitude is infinite except under special conditions";
|
|
private const string TWO_COMPONENTS = "Array must contain exactly two components (x, y)";
|
|
private const string INTERPOLATION_RANGE = "Control parameter must be a value between 0 & 1";
|
|
private const string NON_VECTOR_COMPARISON = "Cannot compare a vec2 to a non-vec2";
|
|
private const string ARGUMENT_TYPE = "The argument provided is a type of ";
|
|
private const string ARGUMENT_VALUE = "The argument provided has a value of ";
|
|
private const string ARGUMENT_LENGTH = "The argument provided has a length of ";
|
|
private const string NEGATIVE_magnitude = "The magnitude of a vec2 must be a positive value, (i.e. greater than 0)";
|
|
private const string ORIGIN_VECTOR_magnitude = "Cannot change the magnitude of vec2(0, 0)";
|
|
|
|
private const string UNIT_VECTOR = "Unit vector composing of ";
|
|
private const string POSITIONAL_VECTOR = "Positional vector composing of ";
|
|
private const string MAGNITUDE = " of magnitude ";
|
|
|
|
public static readonly vec2 minValue = new vec2(Double.MinValue, Double.MinValue);
|
|
public static readonly vec2 maxValue = new vec2(Double.MaxValue, Double.MaxValue);
|
|
public static readonly vec2 epsilon = new vec2(Double.Epsilon, Double.Epsilon);
|
|
public static readonly vec2 zero = origin;
|
|
public static readonly vec2 NaN = new vec2(double.NaN, double.NaN);
|
|
}
|
|
} |