Skip to main content
added 2334 characters in body
Source Link

EDIT: after reading @Theraot's answer, and finding out that non-static classes can also have a static constructor, i've arrived at the following stucture:

delegate IntersectResult IntersectAction<in T, in U>(T a, U b);
interface IShape
{
    IntersectResult Intersects(IShape shape, out bool flipped);
    IntersectResult Intersects<T>(Shape<T> shape, out bool flipped);
}
abstract class Shape<T> : IShape
{
    public static class Interactor<U>
    {
        public static IntersectAction<T, U> Intersect { get; set; }
    }
    public abstract T Instance { get; } // shape instance     

    public IntersectResult Intersects(IShape shape, out bool flipped)
    {
        IntersectResult r = shape.Intersects<T>(this, out flipped); // call the other shape's intersect method, specifying our type
        flipped = !flipped; // results are flipped since we're calling the other shape's method
        return r;
    }
    public IntersectResult Intersects<T2>(Shape<T2> shape, out bool flipped)
    {
        flipped = false;
        if (Interactor<T2>.Intersect != null)
            return Interactor<T2>.Intersect(Instance, shape.Instance);
        if (Shape<T2>.Interactor<T>.Intersect != null)
        {
            flipped = true; // results are flipped since we're calling the other shape's method
            return Shape<T2>.Interactor<T>.Intersect(shape.Instance, Instance);
        }
        return new IntersectResult();
    }
}    
class ShapeA : Shape<ShapeA>
{
    static ShapeA()
    {
        Interactor<ShapeA>.Intersect = MathCol.AintersectsA;
        Interactor<ShapeB>.Intersect = MathCol.AintersectsB;
    }
    public override ShapeA Instance { get { return this; } }        
}

I've decided to nest the static Interactor class inside the Shape because it didn't feel right having it completely decoupled from the Shape class.
I'm still not happy about public abstract T Instance { get; } but i guess it is required if I'm doing the work in the abstract class.
I'll leave the question open for a bit to see if someone else might have some more insight on the whole matter.


EDIT: after reading @Theraot's answer, and finding out that non-static classes can also have a static constructor, i've arrived at the following stucture:

delegate IntersectResult IntersectAction<in T, in U>(T a, U b);
interface IShape
{
    IntersectResult Intersects(IShape shape, out bool flipped);
    IntersectResult Intersects<T>(Shape<T> shape, out bool flipped);
}
abstract class Shape<T> : IShape
{
    public static class Interactor<U>
    {
        public static IntersectAction<T, U> Intersect { get; set; }
    }
    public abstract T Instance { get; } // shape instance     

    public IntersectResult Intersects(IShape shape, out bool flipped)
    {
        IntersectResult r = shape.Intersects<T>(this, out flipped); // call the other shape's intersect method, specifying our type
        flipped = !flipped; // results are flipped since we're calling the other shape's method
        return r;
    }
    public IntersectResult Intersects<T2>(Shape<T2> shape, out bool flipped)
    {
        flipped = false;
        if (Interactor<T2>.Intersect != null)
            return Interactor<T2>.Intersect(Instance, shape.Instance);
        if (Shape<T2>.Interactor<T>.Intersect != null)
        {
            flipped = true; // results are flipped since we're calling the other shape's method
            return Shape<T2>.Interactor<T>.Intersect(shape.Instance, Instance);
        }
        return new IntersectResult();
    }
}    
class ShapeA : Shape<ShapeA>
{
    static ShapeA()
    {
        Interactor<ShapeA>.Intersect = MathCol.AintersectsA;
        Interactor<ShapeB>.Intersect = MathCol.AintersectsB;
    }
    public override ShapeA Instance { get { return this; } }        
}

I've decided to nest the static Interactor class inside the Shape because it didn't feel right having it completely decoupled from the Shape class.
I'm still not happy about public abstract T Instance { get; } but i guess it is required if I'm doing the work in the abstract class.
I'll leave the question open for a bit to see if someone else might have some more insight on the whole matter.

Source Link

Generic shape design

I've been trying to come up with a generic shape design that would support shape-shape interactions (like Intersect), based on the shape's type.

So a common class/interface was needed, but a problem arises from shape types since not all shapes have common properties (eg. Circle is composed of a point and a radius, while polygons are composed of multiple points) so each pair of shape types has to have their own Intersect method.

After a lot of reading up on the subject of generics (and the like), i've come up with the following structure:

Collection class for storing delegates to shape Intersect methods

class IRdelegateCollection<T> // IntersectResult delegate collection
{
    public delegate IntersectResult IntersectAction<in U>(T a, U b);

    Dictionary<Type, Delegate> _jumpTable;
    public IRdelegateCollection()
    {
        _jumpTable = new Dictionary<Type, Delegate>();
    }
    public IRdelegateCollection(Dictionary<Type, Delegate> jumpTable)
    {
        _jumpTable = jumpTable;
    }
    public void Set<U>(IntersectAction<U> action)
    {
        if (_jumpTable.ContainsKey(typeof(U)))
            _jumpTable[typeof(U)] = action;
        else
            _jumpTable.Add(typeof(U), action);
    }
    public IntersectAction<U> Get<U>()
    {
        if (!_jumpTable.ContainsKey(typeof(U)))
        {
            return null;
        }
        return _jumpTable[typeof(U)] as IntersectAction<U>;
    }
}

Interfaces

interface IShape
{
    IntersectResult Intersects(IShape shape, out bool flipped);
    IntersectResult Intersects<T>(IShape<T> shape, out bool flipped);
}
interface IShape<T> : IShape
{
    T Instance { get; }
    IRdelegateCollection<T> IC { get; }
}

Base Shape class (all shapes will derive from this, although i guess they don't have to as long as they have their own IShape interface implementation)

abstract class Shape<T> : IShape<T>
{
    public abstract T Instance { get; } // shape instance
    public abstract IRdelegateCollection<T> IC { get; } // shape type's intersection delegate collection                

    public IntersectResult Intersects(IShape shape, out bool flipped)
    {
        IntersectResult r = shape.Intersects<T>(this, out flipped); // call the other shape's intersect method, specifying our type
        flipped = !flipped; // results are flipped since we're calling the other shape's method
        return r;
    }
    public IntersectResult Intersects<T2>(IShape<T2> shape, out bool flipped)
    {
        flipped = false;
        var del1vs2 = IC.Get<T2>();
        if (del1vs2 != null) // we have the intersection delegate
            return del1vs2(Instance, shape.Instance);

        var del2vs1 = shape.IC.Get<T>(); // we dont have the delegate, try get the other shape's intersection delegate
        if (del2vs1 != null)
        {
            flipped = true; // we're returning the results of other shape's delegate, mark it as flipped
            return del2vs1(shape.Instance, Instance);
        }

        // neither of the two shapes have the intersection delegate
        return new IntersectResult();
    }        
}

Some concrete shape definitions. (ShapeA does not have delegate for ShapeA-ShapeB intersection, but since ShapeB has one for ShapeB-ShapeA thats the one that gets called when intersecting ShapeB on ShapeA)

class ShapeA : Shape<ShapeA>
{
    static IRdelegateCollection<ShapeA> _ic =
        new IRdelegateCollection<ShapeA>(
            new Dictionary<Type, Delegate>()
            {
                { typeof(ShapeA), new IRdelegateCollection<ShapeA>.IntersectAction<ShapeA>(MathCol.AIntersectsA) }
            }
        );
    public override ShapeA Instance { get { return this; } }
    public override IRdelegateCollection<ShapeA> IC { get { return _ic; } }
}
class ShapeB : Shape<ShapeB>
{
    static IRdelegateCollection<ShapeB> _ic =
       new IRdelegateCollection<ShapeB>(
           new Dictionary<Type, Delegate>()
           {
                    { typeof(ShapeB), new IRdelegateCollection<ShapeB>.IntersectAction<ShapeB>(MathCol.BIntersectsB) },
                    { typeof(ShapeA), new IRdelegateCollection<ShapeB>.IntersectAction<ShapeA>(MathCol.BIntersectsA) }
           }
       );
    public override ShapeB Instance { get { return this; } }
    public override IRdelegateCollection<ShapeB> IC { get { return _ic; } }
}

With this kind of structure i can have a list of shapes and be able to intersect them appropriately without having to manually check their types.

ShapeA a = new ShapeA();
ShapeB b = new ShapeB();
bool flipped = false;

// These are called via .Intersects<T>(T shape, out bool flipped)
a.Intersects(a, out flipped); //calls AintersectsA, flipped: False
a.Intersects(b, out flipped); //calls BintersectsA, flipped: True
b.Intersects(a, out flipped); //calls BintersectsA, flipped: False
b.Intersects(b, out flipped); //calls BintersectsB, flipped: False

List<IShape> shapes = new List<IShape>();
shapes.Add(a);
shapes.Add(b);
// These are called via .Intersects(IShape shape, out bool flipped)
shapes[0].Intersects(shapes[0], out flipped); //calls AintersectsA, flipped: True
shapes[0].Intersects(shapes[1], out flipped); //calls BintersectsA, flipped: True
shapes[1].Intersects(shapes[0], out flipped); //calls BintersectsA, flipped: False
shapes[1].Intersects(shapes[1], out flipped); //calls BintersectsB, flipped: True

So there it is, now what i would like to know is:

  1. Is this design appropriate for my requirement (having base shape that can intersect other shapes based on their subtype)?
  2. The double interface - is this considered bad design (since IShape's method accepts IShape<T> argument and IShape<T> is derived from IShape)? Note: i've also tried an alternative design where the IShape.Intersect<T> parameter was the abstract class Shape<T> but that didn't seem right as well.
  3. I've had constraints where T : IShape on both the interface, abstract class Shape<T> and IRdelegateCollection<T> since i was trying to avoid the T Instance which is used in the Intersection<T>() method, but the compiler still had problems converting Shape<T> to T.
  • Is there a way to use the sahpes directly in the delegate invokation of the Shape<T>.Intersects<T2> method? (preferably: del1v2(this, shape) - was throwing the above mentioned error, even with the constraints all over the place)
  • Should there be constraints of where T : IShape on all the Ts? (There does not appear to be any advantage to constraining it)
  1. Class IRdelegateCollection<T> uses a "jump table". My implementation needs a _jumpTable.ContainsKey(typeof(U)) check for both setting and getting. Is there a better way of making this kind of jump table?