Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Way of Implementing these 3 classes in C#: Vector, Direction (unit vector), Point

Tags:

c#

class

struct

  • All Points are Vectors, and all Vectors are Points.
  • All Directions are Vectors, NOT all Vectors are Directions (this shouldn't mean both way conversion shouldn't be allowed).

I want to have the operators overridden once for all preferably since they're all completely identical. In C++ I can just define class Vector { float x,y,z; }, and do typedef Point = Vector, typedef Direction = Vector; In C# there is no equivalent ("using Point=Vector;" sucks as you have to place it in every single document you use, and it's not enforced by the compiler).

I tried to define 3 different classes and override the operators for each, then do implicit type casting which would make the code run slower, etc.

I tried defining just Vector, then Point:Vector and Direction:Vector, this way I only write the operators once but then I can't do implicit type casting Point <-> Vector or Direction <->Vector.

I could simply define the Vector class and use that everywhere, but that would create ambiguity as to weather a variable is supposed to be a position in space (Point), a relative position in space (Vector) or a unit vector (Direction). For example the function:

Vector GetOrthogon(Vector a, Vector b) {
    // ....
}

You can't know whether it's expecting any vectors or unit vectors. In C++ you could do that, so why not in C#?

Note: having structs instead of classes would be ideal if possible.

like image 902
manixrock Avatar asked May 22 '09 11:05

manixrock


2 Answers

Mathematically, Points are Vectors. There are no absolute points in space. Points are defined as vectors from some arbitrary origin. So, I use Vectors for both points and differences between points.

Because a direction is a unit vector, there's no need for a distinction there either. It's like trying to define different static types for the integer 1 and other integers. So I use vectors for both directions and differences between points.

So, define a single Vector type. It'll make your life easier because you'll have fewer classes and overloaded operators/functions to write and test and will be mathematically "purer" (if that matters to you).

like image 179
Nat Avatar answered Oct 13 '22 01:10

Nat


On a purist level, I'd argue that a Vector and a Point are not the same, by algebra:

  • Point + Vector => Point (translation)
  • Vector + Vector => Vector (addition)
  • Point + Point (not defined)

I would have 2 immutable structs with implicit (where they are logically equivalent) or explicit (otherwise) conversion operators. I probably wouldn't have a Direction struct, though... I might have a Direction property on a Vector that scales it to unit, but that is about it...


I've stubbed out a primitive Vector and Point to show the interactions; I haven't filled in all of Point (since it is simpler):

public struct Point {
    public Point(double x, double y) : this() { X = x; Y = y; }
    public double X { get; private set; }
    public double Y { get; private set; }

    // and more ;-p
}

public struct Vector : IEquatable<Vector> {
    public Vector(double x, double y) : this() { X = x; Y = y; }
    public Vector AsUnit() {
        if (X == 0 && Y == 0) throw new InvalidOperationException();
        if (X == 0) return new Vector(0, X > 0 ? 1 : -1);
        if (Y == 0) return new Vector(Y > 0 ? 1 : -1, 0);
        double sqr = Math.Sqrt((X * X) + (Y * Y));
        return new Vector(X / sqr, Y / sqr);
    }
    public double X { get; private set; }
    public double Y { get; private set; }

    public static  explicit operator Point(Vector vector) {
        return new Point(vector.X, vector.Y);
    }
    public static explicit operator Vector(Point point) {
        return new Vector(point.X, point.Y);
    }
    public override string ToString() {
        return "(" + X.ToString() + "," + Y.ToString() + ")";
    }
    public override int GetHashCode() {
        return 17 * X.GetHashCode() + Y.GetHashCode();
    }
    public override bool Equals(object obj) {
        return obj == null ? false : Equals((Vector)obj);
    }
    public bool Equals(Vector vector) {
        return X == vector.X && Y == vector.Y;
    }
    public static bool operator ==(Vector a, Vector b) {
        return a.X == b.X && a.Y == b.Y;
    }
    public static bool operator !=(Vector a, Vector b) {
        return a.X != b.X || a.Y != b.Y;
    }
    public static Point operator +(Point point, Vector vector) {
        return new Point(point.X + vector.X, point.Y + vector.Y);
    }
    public static Point operator -(Point point, Vector vector) {
        return new Point(point.X - vector.X, point.Y - vector.Y);
    }
    public static Vector operator +(Vector a, Vector b) {
        return new Vector(a.X + b.X, a.Y + b.Y);
    }
    public static Vector operator -(Vector a, Vector b) {
        return new Vector(a.X - b.X, a.Y - b.Y);
    }
}
like image 41
Marc Gravell Avatar answered Oct 12 '22 23:10

Marc Gravell