1 ///
2 module isodi.cell;
3 
4 import std.traits;
5 import std.format;
6 import std.random;
7 
8 import isodi.pack;
9 import isodi.tests;
10 import isodi.object3d;
11 
12 
13 @safe:
14 
15 
16 /// Represents a single cell in the Isodi 3D space.
17 abstract class Cell : Object3D, WithDrawableResources {
18 
19     mixin Object3D.ImplementConst;
20 
21     /// Type of the cell.
22     const string type;
23 
24     /// Seed to use for RNG calls related to generation of this cell's resources.
25     ///
26     /// It's preferred to sum this with a magic number to ensure unique combinations.
27     const ulong seed;
28 
29     /// Params:
30     ///     display = Display to place the cell in.
31     ///     position = Position of the cell.
32     ///     type = Type of the cell, eg. "grass".
33     this(Display display, const Position position, const string type) {
34 
35         super(display);
36         this._position = position;
37         this._visualPosition = position;
38         this.seed = position.toHash;
39         this.type = type;
40 
41     }
42 
43     /// Create a cell with the current renderer.
44     static Cell make(Display display, const Position position, const string type) {
45 
46         return Renderer.createCell(display, position, type);
47 
48     }
49 
50     ///
51     protected Pack.Resource!string getTile() {
52 
53         // Get a random file
54         return display.packs.randomGlob(
55             type.format!"cells/%s/tile/*.png",
56             Mt19937_64(seed)
57         );
58 
59     }
60 
61     ///
62     protected Pack.Resource!string[4] getSide() @trusted {
63 
64         Mt19937_64 rng;
65         Pack.Resource!string[4] result;
66 
67         // Get possible sides
68         const path = type.format!"cells/%s/side/*.png";
69         auto glob = display.packs.packGlob(path);
70 
71         // Generate each side
72         foreach (side; 0..4) {
73 
74             // Get a random file
75             rng.seed(seed + 1 + side);
76             const file = glob.matches.choice(rng);
77 
78             result[side] = Pack.Resource!string(
79                 file,
80                 glob.pack.getOptions(file)
81             );
82 
83         }
84 
85         return result;
86 
87     }
88 
89     /// Params:
90     ///     tileOptions = Pack options set for the tile resource.
91     protected Pack.Resource!string[] getDecoration(const ResourceOptions* tileOptions) @trusted {
92 
93         Pack.Resource!string[] result;
94 
95         // Create RNG
96         auto rng = Mt19937_64(seed + 5);
97 
98         // Get available space
99         const spaceRange = tileOptions.decorationSpace;
100         uint space = uniform!"[]"(spaceRange[0], spaceRange[1], rng);
101 
102         // While there is space for new decoration
103         while (space) {
104 
105             rng.seed(seed + space + 5);
106 
107             // Get a random file
108             const path = type.format!"cells/%s/decoration/*.png";
109             auto glob = display.packs.packGlob(path);
110             const file = glob.matches.choice(rng);
111             const options = glob.pack.getOptions(file);
112 
113             // Stop if there's no space
114             if (space < options.decorationWeight) break;
115 
116             // Reduce space
117             space -= options.decorationWeight;
118 
119             // Get the option
120             result ~= Pack.Resource!string(file, options);
121 
122         }
123 
124         return result;
125 
126     }
127 
128     /// Get a neighbor in given direction.
129     /// Params:
130     ///     direction = 0 for negative Y, 1 for positive X, 2 for positive Y, 3 for negative X. Calling with other
131     ///         values is invalid.
132     /// Returns:
133     ///     The queried cell within the same layer. `null` if not found
134     Cell getNeighbor(ubyte direction) {
135 
136         return getNeighbor(direction, position);
137 
138     }
139 
140     /// Get a visual neighbour in given direction — this will take cell offset into account.
141     /// Params:
142     ///     direction = 0 for negative Y, 1 for positive X, 2 for positive Y, 3 for negative X. Calling with other
143     ///         values is invalid.
144     /// Returns:
145     ///     The queried cell within the same layer. `null` if not found
146     Cell getVisualNeighbor(ubyte direction) {
147 
148         return getNeighbor(direction, visualPosition);
149 
150     }
151 
152     /// Get the neighbor from a set position
153     private Cell getNeighbor(ubyte direction, Position pos) {
154 
155         auto newPosition = cast() pos;
156 
157         final switch (direction) {
158 
159             case 0:
160                 newPosition.y -= 1;
161                 break;
162             case 1:
163                 newPosition.x += 1;
164                 break;
165             case 2:
166                 newPosition.y += 1;
167                 break;
168             case 3:
169                 newPosition.x -= 1;
170                 break;
171 
172         }
173 
174         // Find the cell
175         return display.getCell(newPosition.toUnique);
176 
177     }
178 
179 }
180 
181 mixin DisplayTest!((display) {
182 
183     display.addCell(position(0, 0, Height(0.2)), "grass");
184     display.addCell(position(0, 1), "grass");
185     display.addCell(position(0, 2), "grass");
186 
187     display.addCell(position(1, 0), "grass");
188     display.addCell(position(1, 1), "grass");
189     display.addCell(position(1, 2), "grass");
190 
191     display.addCell(position(2, 0), "grass");
192     display.addCell(position(2, 1), "grass");
193     display.addCell(position(2, 2), "grass");
194 
195 });
196 
197 mixin DisplayTest!((display) {
198 
199     display.addCell(position(0, 0), "grass");
200     display.addCell(position(1, 0, Height(0.2, 1.2)), "grass");
201     display.addCell(position(2, 0, Height(0.4, 1.4)), "grass");
202     display.addCell(position(3, 0, Height(0.6, 1.6)), "grass");
203     display.addCell(position(4, 0, Height(0.8, 1.8)), "grass");
204 
205     display.addCell(position(0, 1), "grass");
206     display.addCell(position(1, 1, Height(2, 2)), "grass");
207     display.addCell(position(2, 1, Height(4, 4)), "grass");
208     display.addCell(position(3, 1, Height(6, 6)), "grass");
209     display.addCell(position(4, 1, Height(8, 8)), "grass");
210 
211     auto moved = display.addCell(position(5, 0), "grass");
212     moved.offset = position(-5, -1);
213 
214 });
215 
216 mixin DisplayTest!((display) {
217 
218     display.addCell(position(0, 0, Height(5, 1)), "grass");
219     display.addCell(position(1, 0, Height(5, 5)), "grass");
220     display.addCell(position(2, 0, Height(5, 10)), "grass");
221 
222 });