5
\$\begingroup\$

I am working on an XNA project, and since procedural generation is likely to be used, I wanted to eventually create a helper class to work with data to facilitate that.

Design Goal:

Originally, I wanted to generate and modify a heightmap in a simple but efficient manner. The most straightforward approach to handle this would be a with a float[,].

Right now, I have a Grid<T> which simply holds the data in a more convenient manner, but I want to introduce a way to transform it somehow - slicing, rotations, etc. The ability to add and multiply different heightmaps together would be beneficial as well.

My Code:

Knowing that C# is optimized for single-dimensional arrays, I decided to make a simple wrapper class that contains an internal jagged array, but have it look and behave like a 2-dimensional array.

I first made it into an interface because doing so allows me to implement it however I want, which allows for future optimizations which won't break any dependent code:

// Presents itself as a simplified 2-dimensional array
public interface IGrid<T> : IEnumerable<T>, IEnumerable
{
    Point Size { get; }
    T this[int x, int y] { get; set; }
}

And the current (simplified) implementation is:

// Functions as a jagged array under the hood
public class Grid<T> : IGrid<T>
{
    private readonly T[][] _values;

    // Implement IGrid<T>
    public Point Size { get; private set; }
    public T this[int x, int y]
    {
        get { return _values[y][x]; }
        set { _values[y][x] = value; }
    }

    // Constructor
    public Grid(int x, int y)
    {
        Size = new Point(x, y);
        _values = new T[y][];
        for (int i = 0; i < y; i++)
            _values[i] = new T[x];
    }

    // Provide IEnumerator, override ToString(), etc...
}

My approach for the transformation bits is similar:

public interface ITransformable<T>
{
    T Slice(int x, int y, int width, int height);
    T Slice(Rectangle value);
    T Transpose();
    T Flip(bool horizontal, bool vertical);
    T Rotate(bool clockwise);
}

And here is where I am stumbling. I'm not actually sure what I want to do with it!

  • Attach it to IGrid<T>. It's no longer about simply storing the data, but transforming it as well.
  • Attach it to Grid<T>. The fact that a grid can transform the data is an implementation detail.
  • Create a derived class Mesh<T> which does everything Grid<T> does, but also implements ITransformable<T>.
  • Create a derived class Mesh<T>, but remove ITransformable<T> and move its methods into this class.

Thoughts:

Since generic types do not support operator overloading (+, -, /, *), it means that if I want a Heightmap to support addition in the form of Heightmap m = a + b, it would need a specific implementation, such as public class Heightmap : Mesh<float>, which then supplies the implementation of the operators.

Based on that, my entire class chain might resemeble:

public interface IGrid<T> { ... }
public interface IMesh<T> : IGrid<T> { ... }
public class Grid<T> : IGrid<T> { ... }
public class Mesh<T> : Grid<T>, IMesh<T> { ... }
public class Heightmap : Mesh<float> { ... }
\$\endgroup\$
3
\$\begingroup\$

It's not really clear what you are asking, probably because you are still working out your design, but you seem to be on the right track.

Try to think about what each option means, e.g.

IGrid<T> : ITransformable<T>

Your saying that all Grid's are transformable

Grid<T> : IGrid<T>, ITransformable<T>

Your saying that this implementation is a transformable grid.

Mesh<T> : Grid<T>, ITransformable<T>

Your saying that a Mesh is a transformable Grid.

My personal preference would probably be this:

class GridTransformer<T> : ITranformable<T>
{
    public GridTransformer(IGrid<T> grid)
    {
        Grid = grid;
    }

    private Grid<T> Grid { get; private set; }

    // ITranformable methods here
}

That way you can do this:

var grid = new Grid<int>(5, 5);
var transformer = new GridTransformer(grid);

transformer.Flip(true, false);

And thus, splitting the responsibilites of each class into what they do best.

\$\endgroup\$
3
\$\begingroup\$

I think you should use aggregation.

It's either:

class Mesh<T> : ITransformable<T>
{
    public Mesh(IGrid<T> grid, ....)
    {
        Grid = grid;
        ......
    }

    public IGrid<T> Grid { get; private set; }
    .....
}

or:

class Transformer<T> : ITransformer<T>
{
    public T Slice(IGrid<T>, int x, int y, int width, int height)
    {
        ...
    }
    ....
}

It's hard to tell which is better without good understanding of your design (which I don't have). But generally I would prefer the second option.

Also, your IGrid implementation looks really weird. You are saying that you want to implement 2D array using 1D array, yet you use T[][] _values; in your implementation. So, what's the point? There is also a System.Windows.Size class, so I don't understand why would you want to use a Point.

\$\endgroup\$
  • \$\begingroup\$ Ah. I wasn't sure how on-topic my design/intent would be so I (foolishly) omitted it ... give me a second and I'll update my question. \$\endgroup\$ – Kyle Baran Jun 26 '14 at 11:31
  • \$\begingroup\$ Alright, it's been updated. To answer your specific questions: I'm using XNA, so its actually XNA.Point I'm using, which is preferred. Regarding the array implementation, jagged arrays are an array of arrays, so it does qualify as a single-dimensional array in a sense; in respect to the article I linked, it's still much faster than a traditional 2D array. \$\endgroup\$ – Kyle Baran Jun 26 '14 at 12:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.