using System; using System.ComponentModel; using System.Xml.Serialization; namespace JuicyGraphics.Mathematics { using Exceptions; [ImmutableObject(true), Serializable] public struct vec3 : IComparable, IComparable, IEquatable, IFormattable { private readonly double _x; private readonly double _y; private readonly double _z; public vec3(double x, double y, double z) { _x = x; _y = y; _z = z; } public vec3(double[] xyz) { if (xyz.Length == 3) { _x = xyz[0]; _y = xyz[1]; _z = xyz[2]; } else { throw new ArgumentException(THREE_COMPONENTS); } } public vec3(vec3 v1) { _x = v1.x; _y = v1.y; _z = v1.z; } public double x { get { return _x; } } public double y { get { return _y; } } public double z { get { return _z; } } public vec2 xx { get { return new vec2(_x, _x); } } public vec2 xy { get { return new vec2(_x, _y); } } public vec2 yx { get { return new vec2(_y, _x); } } public vec2 yy { get { return new vec2(_y, _y); } } public vec3 xxx { get { return new vec3(_x, _x, _x); } } public vec3 xxy { get { return new vec3(_x, _x, _y); } } public vec3 xxz { get { return new vec3(_x, _x, _z); } } public vec3 xyx { get { return new vec3(_x, _y, _x); } } public vec3 xyy { get { return new vec3(_x, _y, _y); } } public vec3 xyz { get { return new vec3(_x, _y, _z); } } public vec3 xzx { get { return new vec3(_x, _z, _x); } } public vec3 xzy { get { return new vec3(_x, _z, _y); } } public vec3 xzz { get { return new vec3(_x, _z, _z); } } public vec3 yxx { get { return new vec3(_y, _x, _x); } } public vec3 yxy { get { return new vec3(_y, _x, _y); } } public vec3 yxz { get { return new vec3(_y, _x, _z); } } public vec3 yyx { get { return new vec3(_y, _y, _x); } } public vec3 yyy { get { return new vec3(_y, _y, _y); } } public vec3 yyz { get { return new vec3(_y, _y, _z); } } public vec3 yzx { get { return new vec3(_y, _z, _x); } } public vec3 yzy { get { return new vec3(_y, _z, _y); } } public vec3 yzz { get { return new vec3(_y, _z, _z); } } public vec3 zxx { get { return new vec3(_z, _x, _x); } } public vec3 zxy { get { return new vec3(_z, _x, _y); } } public vec3 zxz { get { return new vec3(_z, _x, _z); } } public vec3 zyx { get { return new vec3(_z, _y, _x); } } public vec3 zyy { get { return new vec3(_z, _y, _y); } } public vec3 zyz { get { return new vec3(_z, _y, _z); } } public vec3 zzx { get { return new vec3(_z, _z, _x); } } public vec3 zzy { get { return new vec3(_z, _z, _y); } } public vec3 zzz { get { return new vec3(_z, _z, _z); } } public vec3 normal { get { return normalize(); } } public double magnitude { get { return Math.Sqrt(sumComponentSqrs()); } } [XmlIgnore] public double[] array { get { return new[] { _x, _y, _z }; } } public double this[int index] { get { switch (index) { case 0: return x; case 1: return y; case 2: return z; default: throw new ArgumentException(THREE_COMPONENTS, "index"); } } } public static vec3 operator +(vec3 v1, vec3 v2) { return new vec3( v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); } public static vec3 operator -(vec3 v1, vec3 v2) { return new vec3( v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } public static vec3 operator *(vec3 v1, double s2) { return new vec3( v1.x * s2, v1.y * s2, v1.z * s2); } public static vec3 operator *(double s1, vec3 v2) { return v2 * s1; } public static vec3 operator /(vec3 v1, double s2) { return new vec3( v1.x / s2, v1.y / s2, v1.z / s2); } public static vec3 operator -(vec3 v1) { return new vec3( -v1.x, -v1.y, -v1.z); } public static vec3 operator +(vec3 v1) { return new vec3( +v1.x, +v1.y, +v1.z); } public static bool operator <(vec3 v1, vec3 v2) { return v1.sumComponentSqrs() < v2.sumComponentSqrs(); } public static bool operator >(vec3 v1, vec3 v2) { return v1.sumComponentSqrs() > v2.sumComponentSqrs(); } public static bool operator <=(vec3 v1, vec3 v2) { return v1.sumComponentSqrs() <= v2.sumComponentSqrs(); } public static bool operator >=(vec3 v1, vec3 v2) { return v1.sumComponentSqrs() >= v2.sumComponentSqrs(); } public static bool operator ==(vec3 v1, vec3 v2) { return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; } public static bool operator !=(vec3 v1, vec3 v2) { return !(v1 == v2); } public static vec3 scale(vec3 vector, double magnitude) { if (magnitude < 0) { throw new ArgumentOutOfRangeException("magnitude", magnitude, NEGATIVE_magnitude); } if (vector == new vec3(0, 0, 0)) { throw new ArgumentException(ORIGIN_VECTOR_magnitude, "vector"); } return vector * (magnitude / vector.magnitude); } public vec3 scale(double magnitude) { return vec3.scale(this, magnitude); } public static vec3 crossProduct(vec3 v1, vec3 v2) { return new vec3( v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); } public vec3 crossProduct(vec3 other) { return crossProduct(this, other); } public static double dotProduct(vec3 v1, vec3 v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } public double dotProduct(vec3 other) { return dotProduct(this, other); } public static double mixedProduct(vec3 v1, vec3 v2, vec3 v3) { return dotProduct(crossProduct(v1, v2), v3); } public double mixedProduct(vec3 other_v1, vec3 other_v2) { return dotProduct(crossProduct(this, other_v1), other_v2); } public static vec3 normalize(vec3 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 vec3 normalizeOrDefault(vec3 v1) { v1 = normalizeSpecialCasesOrOrigional(v1); if (v1.magnitude == 0) { return origin; } if (v1.isNaN()) { return NaN; } return normalizeOrNaN(v1); } public vec3 normalize() { return normalize(this); } public vec3 normalizeOrDefault() { return normalizeOrDefault(this); } private static vec3 normalizeOrNaN(vec3 v1) { double inverse = 1 / v1.magnitude; return new vec3( v1.x * inverse, v1.y * inverse, v1.z * inverse); } private static vec3 normalizeSpecialCasesOrOrigional(vec3 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; var z = v1.z == 0 ? 0 : v1.z == -0 ? -0 : double.IsPositiveInfinity(v1.z) ? 1 : double.IsNegativeInfinity(v1.z) ? -1 : double.NaN; return new vec3(x, y, z); } return v1; } public static vec3 interpolate(vec3 v1, vec3 v2, double control, bool allowExtrapolation) { if (!allowExtrapolation && (control > 1 || control < 0)) { throw new ArgumentOutOfRangeException( "control", control, INTERPOLATION_RANGE + "\n" + ARGUMENT_VALUE + control); } return new vec3( v1.x * (1 - control) + v2.x * control, v1.y * (1 - control) + v2.y * control, v1.z * (1 - control) + v2.z * control); } public static vec3 interpolate(vec3 v1, vec3 v2, double control) { return interpolate(v1, v2, control, false); } public vec3 interpolate(vec3 other, double control) { return interpolate(this, other, control); } public vec3 interpolate(vec3 other, double control, bool allowExtrapolation) { return interpolate(this, other, control); } public static double distance(vec3 v1, vec3 v2) { return Math.Sqrt( (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y) + (v1.z - v2.z) * (v1.z - v2.z)); } public double distance(vec3 other) { return distance(this, other); } public static double angle(vec3 v1, vec3 v2) { if (v1 == v2) { return 0; } return Math.Acos( Math.Min(1.0f, normalizeOrDefault(v1).dotProduct(normalizeOrDefault(v2)))); } public double angle(vec3 other) { return angle(this, other); } public static vec3 max(vec3 v1, vec3 v2) { return v1 >= v2 ? v1 : v2; } public vec3 max(vec3 other) { return max(this, other); } public static vec3 min(vec3 v1, vec3 v2) { return v1 <= v2 ? v1 : v2; } public vec3 min(vec3 other) { return min(this, other); } public static vec3 yaw(vec3 v1, double rad) { return rotateY(v1, rad); } public vec3 yaw(double rad) { return yaw(this, rad); } public static vec3 pitch(vec3 v1, double rad) { return rotateX(v1, rad); } public vec3 pitch(double rad) { return pitch(this, rad); } public static vec3 roll(vec3 v1, double rad) { return rotateZ(v1, rad); } public vec3 roll(double rad) { return roll(this, rad); } public static vec3 rotateX(vec3 v1, double rad) { double x = v1.x; double y = (v1.y * Math.Cos(rad)) - (v1.z * Math.Sin(rad)); double z = (v1.y * Math.Sin(rad)) + (v1.z * Math.Cos(rad)); return new vec3(x, y, z); } public vec3 rotateX(double rad) { return rotateX(this, rad); } public static vec3 rotateY(vec3 v1, double rad) { double x = (v1.z * Math.Sin(rad)) + (v1.x * Math.Cos(rad)); double y = v1.y; double z = (v1.z * Math.Cos(rad)) - (v1.x * Math.Sin(rad)); return new vec3(x, y, z); } public vec3 rotateY(double rad) { return rotateY(this, rad); } public static vec3 rotateZ(vec3 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)); double z = v1.z; return new vec3(x, y, z); } public vec3 rotateZ(double rad) { return rotateZ(this, rad); } public static vec3 rotateX(vec3 v1, double yOff, double zOff, double rad) { double x = v1.x; double y = (v1.y * Math.Cos(rad)) - (v1.z * Math.Sin(rad)) + (yOff * (1 - Math.Cos(rad)) + zOff * Math.Sin(rad)); double z = (v1.y * Math.Sin(rad)) + (v1.z * Math.Cos(rad)) + (zOff * (1 - Math.Cos(rad)) - yOff * Math.Sin(rad)); return new vec3(x, y, z); } public vec3 rotateX(double yOff, double zOff, double rad) { return rotateX(this, yOff, zOff, rad); } public static vec3 rotateY(vec3 v1, double xOff, double zOff, double rad) { double x = (v1.z * Math.Sin(rad)) + (v1.x * Math.Cos(rad)) + (xOff * (1 - Math.Cos(rad)) - zOff * Math.Sin(rad)); double y = v1.y; double z = (v1.z * Math.Cos(rad)) - (v1.x * Math.Sin(rad)) + (zOff * (1 - Math.Cos(rad)) + xOff * Math.Sin(rad)); return new vec3(x, y, z); } public vec3 rotateY(double xOff, double zOff, double rad) { return rotateY(this, xOff, zOff, rad); } public static vec3 rotateZ(vec3 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)); double z = v1.z; return new vec3(x, y, z); } public vec3 rotateZ(double xOff, double yOff, double rad) { return rotateZ(this, xOff, yOff, rad); } public static vec3 projection(vec3 v1, vec3 v2) { return new vec3(v2 * (v1.dotProduct(v2) / Math.Pow(v2.magnitude, 2))); } public vec3 projection(vec3 direction) { return projection(this, direction); } public static vec3 rejection(vec3 v1, vec3 v2) { return v1 - v1.projection(v2); } public vec3 rejection(vec3 direction) { return rejection(this, direction); } public vec3 reflection(vec3 reflector) { this = vec3.reflection(this, reflector); return this; } public static vec3 reflection(vec3 v1, vec3 v2) { if (Math.Abs(Math.Abs(v1.angle(v2)) - Math.PI / 2) < Double.Epsilon) { return -v1; } vec3 retval = new vec3(2 * v1.projection(v2) - v1); return retval.scale(v1.magnitude); } public static Double abs(vec3 v1) { return v1.magnitude; } public double abs() { return magnitude; } public static double sumComponents(vec3 v1) { return v1.x + v1.y + v1.z; } public double sumComponents() { return sumComponents(this); } public static double sumComponentSqrs(vec3 v1) { vec3 v2 = sqrComponents(v1); return v2.sumComponents(); } public double sumComponentSqrs() { return sumComponentSqrs(this); } public static vec3 powComponents(vec3 v1, double power) { return new vec3( Math.Pow(v1.x, power), Math.Pow(v1.y, power), Math.Pow(v1.z, power)); } public vec3 powComponents(double power) { return powComponents(this, power); } public static vec3 sqrtComponents(vec3 v1) { return new vec3( Math.Sqrt(v1.x), Math.Sqrt(v1.y), Math.Sqrt(v1.z)); } public vec3 sqrtComponents() { return sqrtComponents(this); } public static vec3 sqrComponents(vec3 v1) { return new vec3( v1.x * v1.x, v1.y * v1.y, v1.z * v1.z); } public vec3 sqrComponents() { return sqrComponents(this); } public static vec3 round(vec3 v1) { return new vec3(Math.Round(v1.x), Math.Round(v1.y), Math.Round(v1.z)); } public static vec3 round(vec3 v1, int digits) { return new vec3(Math.Round(v1.x, digits), Math.Round(v1.y, digits), Math.Round(v1.z, digits)); } public static vec3 round(vec3 v1, MidpointRounding mode) { return new vec3(Math.Round(v1.x, mode), Math.Round(v1.y, mode), Math.Round(v1.z, mode)); } public static vec3 round(vec3 v1, int digits, MidpointRounding mode) { return new vec3(Math.Round(v1.x, digits, mode), Math.Round(v1.y, digits, mode), Math.Round(v1.z, digits, mode)); } public vec3 round() { return new vec3(Math.Round(x), Math.Round(y), Math.Round(z)); } public vec3 round(int digits) { return new vec3(Math.Round(x, digits), Math.Round(y, digits), Math.Round(z, digits)); } public vec3 round(MidpointRounding mode) { return new vec3(Math.Round(x, mode), Math.Round(y, mode), Math.Round(z, mode)); } public vec3 round(int digits, MidpointRounding mode) { return new vec3(Math.Round(x, digits, mode), Math.Round(y, digits, mode), Math.Round(z, 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}, z={2} )", x, y, z); output += magnitude + magnitude; return output; } public string ToString(string format, IFormatProvider formatProvider) { if (format == null || format == "") { return string.Format("({0}, {1}, {2})", x, y, z); } 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); case 'z': return z.ToString(remainder, formatProvider); default: return String.Format( "({0}, {1}, {2})", x.ToString(format, formatProvider), y.ToString(format, formatProvider), z.ToString(format, formatProvider)); } } public override int GetHashCode() { unchecked { var hashCode = _x.GetHashCode(); hashCode = (hashCode * 397) ^ _y.GetHashCode(); hashCode = (hashCode * 397) ^ _z.GetHashCode(); return hashCode; } } public override bool Equals(object other) { if (other is vec3) { return other.Equals(this); } else { return false; } } public bool Equals(object other, double tolerance) { if (other is vec3) { return Equals((vec3)other, tolerance); } return false; } public bool Equals(vec3 other) { return x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z); } public bool Equals(vec3 other, double tolerance) { return x.almostEqualsWithAbsTolerance(other.x, tolerance) && y.almostEqualsWithAbsTolerance(other.y, tolerance) && z.almostEqualsWithAbsTolerance(other.z, tolerance); } public int CompareTo(vec3 other) { if (this < other) { return -1; } if (this > other) { return 1; } return 0; } public int CompareTo(object other) { if (other is vec3) { return CompareTo((vec3)other); } throw new ArgumentException( NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(), "other"); } public int CompareTo(vec3 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 vec3) { return CompareTo((vec3)other, tolerance); } throw new ArgumentException( NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(), "other"); } public static bool isUnitVector(vec3 v1, double tolerance) { return v1.magnitude.almostEqualsWithAbsTolerance(1, tolerance); } public static bool isUnitVector(vec3 v1) { return v1.magnitude == 1; } public bool isUnitVector() { return isUnitVector(this); } public bool isUnitVector(double tolerance) { return isUnitVector(this, tolerance); } public static bool isBackFace(vec3 normal, vec3 lineOfSight) { return normal.dotProduct(lineOfSight) < 0; } public bool isBackFace(vec3 lineOfSight) { return isBackFace(this, lineOfSight); } public static bool isPerpendicular(vec3 v1, vec3 v2, double tolerance) { v1 = normalizeSpecialCasesOrOrigional(v1); v2 = normalizeSpecialCasesOrOrigional(v2); if (v1 == zero || v2 == zero) { return false; } return v1.dotProduct(v2).almostEqualsWithAbsTolerance(0, tolerance); } public static bool isPerpendicular(vec3 v1, vec3 v2) { v1 = normalizeSpecialCasesOrOrigional(v1); v2 = normalizeSpecialCasesOrOrigional(v2); if (v1 == zero || v2 == zero) { return false; } return v1.dotProduct(v2).Equals(0); } public bool isPerpendicular(vec3 other) { return isPerpendicular(this, other); } public bool isPerpendicular(vec3 other, double tolerance) { return isPerpendicular(this, other, tolerance); } public static bool isNaN(vec3 v1) { return double.IsNaN(v1.x) || double.IsNaN(v1.y) || double.IsNaN(v1.z); } public bool isNaN() { return isNaN(this); } public static readonly vec3 origin = new vec3(0, 0, 0); public static readonly vec3 xAxis = new vec3(1, 0, 0); public static readonly vec3 yAxis = new vec3(0, 1, 0); public static readonly vec3 zAxis = new vec3(0, 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 THREE_COMPONENTS = "Array must contain exactly three components , (x,y,z)"; private const string INTERPOLATION_RANGE = "Control parameter must be a value between 0 & 1"; private const string NON_VECTOR_COMPARISON = "Cannot compare a vec3 to a non-vec3"; 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 vec3 must be a positive value, (i.e. greater than 0)"; private const string ORIGIN_VECTOR_magnitude = "Cannot change the magnitude of vec3(0,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 vec3 minValue = new vec3(Double.MinValue, Double.MinValue, Double.MinValue); public static readonly vec3 maxValue = new vec3(Double.MaxValue, Double.MaxValue, Double.MaxValue); public static readonly vec3 epsilon = new vec3(Double.Epsilon, Double.Epsilon, Double.Epsilon); public static readonly vec3 zero = origin; public static readonly vec3 NaN = new vec3(double.NaN, double.NaN, double.NaN); } }