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 });