1 /**
2     Multi-dimensional vector.
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9 
10     Authors:
11         Luna Nielsen
12         Mireille Arseneault
13 */
14 module inochi2d.core.vector2d;
15 import numem;
16 
17 public import inochi2d.core.slice2d;
18 
19 /**
20     A contiguous 2D vector.
21 
22     The data is stored row-major.
23 */
24 struct vector2d(T) {
25 private:
26 @nogc:
27     T[] _data = null;
28     size_t _rows = 0;
29     size_t _columns = 0;
30 
31 public:
32 
33     /**
34         Type of the data stored within.
35     */
36     alias DT = T;
37 
38     /**
39         Pointer to the start of the data.
40     */
41     @property inout(T)* ptr() inout => _data.ptr;
42 
43     /**
44         Total length of this vector's memory block.
45     */
46     @property size_t length() const => _data.length;
47 
48     /**
49         Slice to the underlying memory block.
50     */
51     @property inout(T)[] data() inout => _data[];
52 
53     /**
54         The number of rows in this vector.
55     */
56     @property size_t rows() const => _rows;
57 
58     /**
59         The number of columns in this vector.
60     */
61     @property size_t columns() const => _columns;
62 
63     /**
64         Construct a new vector of the given size.
65 
66         Params:
67             rows =      The number of rows in the vector.
68             columns =   The number of columns in the vector.
69     */
70     this(size_t rows, size_t columns) {
71         resize(rows, columns);
72     }
73 
74     /**
75         Copy-construct a new vector.
76         
77         Params:
78             other =     The vector to copy during construction.
79     */
80     this(ref return scope inout vector2d other) {
81         _data = cast(T[])other._data.nu_dup;
82         _rows = other._rows;
83         _columns = other._columns;
84     }
85 
86     /**
87         Move-construct a new vector.
88         
89         Params:
90             other =     The vector to move during construction.
91     */
92     this(return scope vector2d other) {
93         _data = other._data;
94         _rows = other._rows;
95         _columns = other._columns;
96 
97         other._data = null;
98         other._rows = 0;
99         other._columns = 0;
100     }
101 
102     ~this() {
103         nu_freea(_data);
104     }
105 
106     /**
107         Resize this 2D vector.
108 
109         Params:
110             rows =  The new row size of this vector.
111             cols =  The new column size of this vector.
112     */
113     void resize(size_t rows, size_t cols) {
114 
115         // NOTE:    When resizing from empty to non-empty, we can skip the
116         //          other steps.
117         if ((_rows == 0 || _columns == 0) && (rows > 0 && cols > 0)) {
118             this._rows = rows;
119             this._columns = cols;
120             this._data = nu_malloca!T(_rows * _columns);
121             return;
122         }
123 
124         // Get which row/column pair is the smallest, then use that
125         // for copying data over to the new array.
126         size_t mRows = nu_min(cast(size_t)_rows, cast(size_t)rows);
127         size_t mCols = nu_min(cast(size_t)_columns, cast(size_t)cols);
128 
129         auto oldData = _data;
130         auto newData = nu_malloca!T(rows * cols);
131         foreach (x; 0 .. mRows) {
132             foreach (y; 0 .. mCols) {
133                 newData[(rows * y) + x] = oldData[(_rows * y) + x];
134             }
135         }
136 
137         this._rows = rows;
138         this._columns = cols;
139         this._data = newData;
140         nu_freea(oldData);
141     }
142 
143     /**
144         Clear the contents of the vector.
145     */
146     void clear() {
147         _rows = 0;
148         _columns = 0;
149         nu_freea(_data);
150         _data = null;
151     }
152 
153     /**
154         Index this vector.
155 
156         Params:
157             row =       The row to index.
158             column =    The column to index.
159 
160         Returns:
161             The item at the given row and column index.
162     */
163     ref inout(T) opIndex(size_t row, size_t column) inout {
164         assert(row <= _rows, "Row index outside bounds of vector.");
165         assert(column <= _columns, "Column index outside bounds of vector.");
166         return _data[(_columns * row) + column];
167     }
168 
169     /**
170         Index this vector.
171 
172         Params:
173             row =       The row to index.
174             columns =   The columns to index.
175 
176         Returns:
177             The slice of items at the given row/column span.
178     */
179     slice2d!T opIndex(size_t row, size_t[2] columns) {
180         const a = (_columns * row) + columns[0];
181         const b = (_columns * (row + 1)) + columns[1];
182         return slice2d!T(_data[a .. b], _columns, 1, columns[1] - columns[0]);
183     }
184 
185     /**
186         Index this vector.
187 
188         Params:
189             rows =      The rows to index.
190             column =    The column to index.
191 
192         Returns:
193             The slice of items at the given row/column span.
194     */
195     slice2d!T opIndex(size_t[2] rows, size_t column) {
196         const a = (_columns * rows[0]) + column;
197         const b = (_columns * rows[1]) + column + 1;
198         return slice2d!T(_data[a .. b], _columns, rows[1] - rows[0], 1);
199     }
200 
201     /**
202         Index this vector.
203 
204         Params:
205             rows =      The rows to index.
206             columns =   The columns to index.
207 
208         Returns:
209             The slice of items at the given row/column span.
210     */
211     slice2d!T opIndex(size_t[2] rows, size_t[2] columns) {
212         const a = (_columns * rows[0]) + columns[0];
213         const b = (_columns * rows[1]) + columns[1];
214         return slice2d!T(_data[a .. b], _columns, rows[1] - rows[0], columns[1] - columns[0]);
215     }
216 
217     /**
218         Slices this vector.
219 
220         Returns:
221             The slice of the entire contents of this vector.
222     */
223     slice2d!T opIndex() {
224         return slice2d!T(_data, _columns, _rows, _columns);
225     }
226 
227     /**
228         Assign to the value at the given index.
229     */
230     auto opIndexAssign(T value, size_t row, size_t column) {
231         assert(row <= _rows, "Row index outside bounds of array.");
232         assert(column <= _columns, "Column index outside bounds of array.");
233         return _data[(_columns * row) + column] = value;
234     }
235 
236     /**
237         Mass-assign to the values at the given span.
238     */
239     auto opIndexAssign(T value, size_t row, size_t[2] columns) {
240         auto slice = this[row, columns];
241         return slice[] = value;
242     }
243 
244     /**
245         Mass-assign to the values at the given span.
246     */
247     auto opIndexAssign(T value, size_t[2] rows, size_t column) {
248         auto slice = this[rows, column];
249         return slice[] = value;
250     }
251 
252     /**
253         Mass-assign to the values at the given span.
254     */
255     auto opIndexAssign(T value, size_t[2] rows, size_t[2] columns) {
256         auto slice = this[rows, columns];
257         return slice[] = value;
258     }
259 
260     /**
261         Mass-assign to all values in this vector.
262     */
263     auto opIndexAssign(T value) {
264         _data[] = value;
265         return value;
266     }
267 
268     /**
269         Mass-assign a slice to all values of this vector
270     */
271     auto opIndexAssign(slice2d!T value) {
272         auto slice = this[];
273         return slice[] = value;
274     }
275 
276     /**
277         Mass-assign a slice to a region of this vector.
278     */
279     auto opIndexAssign(slice2d!T value, size_t row, size_t[2] columns) {
280         auto slice = this[row, columns];
281         return slice[] = value;
282     }
283 
284     /**
285         Mass-assign a slice to a region of this vector.
286     */
287     auto opIndexAssign(slice2d!T value, size_t[2] rows, size_t column) {
288         auto slice = this[rows, column];
289         return slice[] = value;
290     }
291 
292     /**
293         Mass-assign a slice to a region of this vector.
294     */
295     auto opIndexAssign(slice2d!T value, size_t[2] rows, size_t[2] columns) {
296         auto slice = this[rows, columns];
297         return slice[] = value;
298     }
299 
300     /**
301         Obtain a pair of indices describing a span of rows.
302 
303         Params:
304             a = The lower bound.
305             b = The upper bound.
306 
307         Returns:
308             A "tuple" of our bounds.
309     */
310     size_t[2] opSlice(size_t dim : 0)(size_t a, size_t b) const {
311         assert(a <= b, "Slice bounds given in the wrong order");
312         return [a, b];
313     }
314 
315     /**
316         Obtain a pair of indices describing a span of columns.
317 
318         Params:
319             a = The lower bound.
320             b = The upper bound.
321 
322         Returns:
323             A "tuple" of our bounds.
324     */
325     size_t[2] opSlice(size_t dim : 1)(size_t a, size_t b) const {
326         assert(a <= b, "Slice bounds given in the wrong order");
327         return [a, b];
328     }
329 
330     /**
331         Obtain the number of rows in this vector using the dollar operator.
332 
333         Returns:
334             The number of rows in this vector.
335     */
336     size_t opDollar(size_t dim : 0)() const {
337         return _rows;
338     }
339 
340     /**
341         Obtain the number of columns in this vector using the dollar operator.
342 
343         Returns:
344             The number of columns in this vector.
345     */
346     size_t opDollar(size_t dim : 1)() const {
347         return _columns;
348     }
349 }
350 
351 @("vector2d")
352 unittest {
353     vector2d!int ints;
354     ints.resize(4, 4);
355     ints[2, 2] = 24;
356 
357     ints.resize(8, 8);
358     assert(ints[2, 2] == 24);
359 }