Just regarding the headline of your question, this is you create one dimensional arrays of two-dimensional arrays in C++:
std::vector<std::vector<std::vector<Entity>>> v;
That's a multidimensional where each sub-array can have a size that is independent of each other.
Note that C++ has also fixed-size-arrays (one-dimensional, too):
std::array<std::array<std::array<int,16>,16,16>>
array_16_of_array_16_of_array_16_of_int;
Generally, prefer standard-containers over direct arrays. This opens you up for a world of algorithms (e.g. the algorithm
header) and helper methods (.size()
).
Unfortunately, C++ has no builtin multidimensional containers, but some libraries provide them, or you could make your own. A basic implementation may look like this:
template <typename T>
class surface {
public:
typedef size_t size_type;
surface(size_type w, size_type h) : width_(w), height_(h), data_(w*h) {}
size_type width() const { return width_; }
size_type height() const { return height_; }
size_type size() const { return width_*height_; }
// These are for when you need to loop over all elements
// without interest in coordinates.
T operator[] (size_type i) const { return data_[i]; }
T& operator[] (size_type i) { return data_[i]; }
T operator() (size_type x, size_type y) const { return data_[y*width_+x]; }
T& operator() (size_type x, size_type y) { return data_[y*width_+x]; }
T at(size_type i) const {
if (i>=size()) throw std::out_of_range(....);
return (*this)[i];
}
T& at(size_type i) {
if (i>=size()) throw std::out_of_range(....);
return (*this)[i];
}
T at(size_type x, size_type y) const {
if (x>=width_ || y>=height_) throw std::out_of_range(....);
return (*this)(x,y);
}
T& at(size_type x, size_type y) {
if (x>=width_ || y>=height_) throw std::out_of_range(....);
return (*this)(x,y);
}
private:
size_type width_, height_;
std::vector<T> data_;
};
...
surface<Foo> x(256,256);
x(16,12) = Foo();
x[16+12*256] = Foo();
Depending on the size and access ordering on your array, you may use other indexers (e.g. Morton/Z-Order indexing).
template <typename T, typename Indexer=LinearIndexer>
...
T operator() (size_type x, size_type y) const { return data_[Indexer()(x,y)]; }
...
surface<int, Morton> foo(102400, 102400);
And possibly you even templatize the container too allow for exotic cases like containers that can just-in-time-load/save from/to disk.