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> { ... }
asked Jun 26, 2014 at 11:00
\$\endgroup\$

2 Answers 2

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.

answered Jul 1, 2014 at 4:52
\$\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.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
answered Jun 26, 2014 at 11:25
\$\endgroup\$
2
  • \$\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\$ Commented Jun 26, 2014 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\$ Commented Jun 26, 2014 at 12:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.