1 module isodi.chunk; 2 3 import raylib; 4 5 import std.meta; 6 import std.array; 7 import std.range; 8 import std.format; 9 import std.algorithm; 10 11 import isodi.utils; 12 import isodi.properties; 13 import isodi.isodi_model; 14 15 16 @safe: 17 18 19 /// Represents a chunk of blocks. 20 struct Chunk { 21 22 public { 23 24 /// Properties of the chunk. 25 Properties properties; 26 27 /// Seed to use to generate variants on this chunk. 28 ulong seed; 29 30 /// Mapping of block types to their position in the texture. 31 BlockUV[BlockType] atlas; 32 33 /// Blocks making up the chunk. 34 Block[Vector2I] blocks; 35 36 } 37 38 inout(Block*) find(Vector2I position) inout { 39 40 return position in blocks; 41 42 } 43 44 /// Add multiple items. 45 /// 46 /// * Pass a `BlockPosition` to change the position of the following blocks. 47 /// * Pass a `BlockType` to change the type of the following blocks. 48 /// * Pass a `long` to add a new block with given height. Increment X or Y of the block position, respectively for 49 /// the function used. 50 void addX(T...)(T items) => addImpl!"x"(items); 51 52 /// ditto 53 void addY(T...)(T items) => addImpl!"y"(items); 54 55 void addImpl(string direction, T...)(T items) { 56 57 BlockPosition position; 58 BlockType type; 59 60 foreach (item; items) { 61 62 alias T = typeof(item); 63 64 // Changing position 65 static if (is(T : BlockPosition)) { 66 67 position = item; 68 69 } 70 71 // Changing type 72 else static if (is(T : BlockType)) { 73 74 type = item; 75 76 } 77 78 // Adding block 79 else static if (is(T : long)) { 80 81 auto blockPosition = position; 82 blockPosition.height = item; 83 84 blocks[position.vector] = Block(type, blockPosition); 85 86 // Update the position 87 __traits(getMember, position, direction) += 1; 88 89 } 90 91 else static assert(false, format!"Unrecognized type %s"(typeid(T))); 92 93 } 94 95 } 96 97 /// Make model for the chunk. 98 IsodiModel makeModel(return Texture2D texture) const { 99 100 import core.lifetime; 101 102 const atlasSize = Vector2(texture.width, texture.height); 103 104 // Render data per block 105 const verticesPerBlock = 5*4; 106 const trianglesPerBlock = 5*2; 107 108 // Total data 109 const vertexCount = blocks.length * verticesPerBlock; 110 const triangleCount = blocks.length * trianglesPerBlock; 111 112 // Prepare the model 113 IsodiModel model = { 114 properties: properties, 115 texture: texture, 116 performFold: true, 117 }; 118 model.vertices.reserve = vertexCount; 119 model.variants.length = vertexCount; 120 model.texcoords.reserve = vertexCount; 121 model.triangles.reserve = triangleCount; 122 // TODO: side culling 123 124 // Add each block 125 foreach (i, block; blocks.byValue.enumerate) with (model) { 126 127 const position = Vector3( 128 block.position.x, 129 cast(float) block.position.height / properties.heightSteps, 130 block.position.y, 131 ); 132 133 const depth = cast(float) block.position.depth / properties.heightSteps; 134 135 // Vertices 136 vertices ~= [ 137 138 // Tile 139 position + Vector3(-0.5, 0, 0.5), 140 position + Vector3(0.5, 0, 0.5), 141 position + Vector3(0.5, 0, -0.5), 142 position + Vector3(-0.5, 0, -0.5), 143 144 // North side (negative Z) 145 position + Vector3(0.5, -depth, -0.5), 146 position + Vector3(-0.5, -depth, -0.5), 147 position + Vector3(-0.5, 0, -0.5), 148 position + Vector3(0.5, 0, -0.5), 149 150 // East side (positive X) 151 position + Vector3(0.5, -depth, 0.5), 152 position + Vector3(0.5, -depth, -0.5), 153 position + Vector3(0.5, 0, -0.5), 154 position + Vector3(0.5, 0, 0.5), 155 156 // South side (positive Z) 157 position + Vector3(-0.5, -depth, 0.5), 158 position + Vector3(0.5, -depth, 0.5), 159 position + Vector3(0.5, 0, 0.5), 160 position + Vector3(-0.5, 0, 0.5), 161 162 // West side (negative X) 163 position + Vector3(-0.5, -depth, -0.5), 164 position + Vector3(-0.5, -depth, 0.5), 165 position + Vector3(-0.5, 0, 0.5), 166 position + Vector3(-0.5, 0, -0.5), 167 168 ]; 169 170 // UVs — tile 171 texcoords ~= [ 172 Vector2(0, 1), 173 Vector2(1, 1), 174 Vector2(1, 0), 175 Vector2(0, 0), 176 ]; 177 178 // UVs — sides 179 foreach (j; 1..5) texcoords ~= [ 180 Vector2(0, depth), 181 Vector2(1, depth), 182 Vector2(1, 0), 183 Vector2(0, 0), 184 ]; 185 186 const chunkIndex = i * trianglesPerBlock/2; 187 188 // Get the variants 189 const blockUV = block.type in atlas; 190 assert(blockUV, format!"%s is not present in chunk atlas"(block.type)); 191 192 // Tile variant 193 const tileVariant = blockUV.getTile(block.position.vector, seed).toShader(atlasSize); 194 variants.assign(chunkIndex + 0, 4, tileVariant); 195 196 // Side variant 197 foreach (j; 1..5) { 198 199 const sideVariant = blockUV.getSide(block.position.vector, seed+j).toShader(atlasSize); 200 variants.assign(chunkIndex + j, 4, sideVariant); 201 202 } 203 204 ushort[3] value(int[] offsets) => [ 205 cast(ushort) (i*verticesPerBlock + offsets[0]), 206 cast(ushort) (i*verticesPerBlock + offsets[1]), 207 cast(ushort) (i*verticesPerBlock + offsets[2]), 208 ]; 209 210 // Triangles (2 per rectangle) 211 triangles ~= map!value([ 212 [ 0, 1, 2], [ 0, 2, 3], 213 [ 4, 5, 6], [ 4, 6, 7], 214 [ 8, 9, 10], [ 8, 10, 11], 215 [12, 13, 14], [12, 14, 15], 216 [16, 17, 18], [16, 18, 19], 217 ]).array; 218 219 } 220 221 // Upload the model 222 model.upload(); 223 224 // Return it 225 return model; 226 227 } 228 229 } 230 231 struct BlockPosition { 232 233 int x, y; 234 int height, depth; 235 236 Vector2I vector() @nogc const => Vector2I(x, y); 237 Vector2 vectorf() @nogc const => Vector2(x, y); 238 239 } 240 241 struct Block { 242 243 BlockType type; 244 BlockPosition position; 245 246 } 247 248 struct BlockType { 249 250 /// Global user-defined block ID. 251 ulong typeID; 252 253 } 254 255 /// Texture position data for given block. 256 struct BlockUV { 257 258 RectangleI tileArea; 259 RectangleI sideArea; 260 uint tileSize; 261 uint sideSize; 262 263 /// Get random tile variant within the UV. 264 RectangleI getTile(Vector2I position, ulong seed) @nogc @trusted const 265 in (tileArea.width > 0, "Tile area width must be positive") 266 in (tileArea.height > 0, "Tile area height must be positive") 267 do { 268 269 // Get tile variant to use 270 const variant = randomVariant( 271 Vector2I(tileArea.width, tileArea.height), 272 Vector2I(tileSize, tileSize), 273 seed + position.toHash, 274 ); 275 276 return RectangleI( 277 tileArea.x + variant.x, 278 tileArea.y + variant.y, 279 tileSize, 280 tileSize, 281 ); 282 283 } 284 285 /// Get random side variant within the UV. 286 RectangleI getSide(Vector2I position, ulong seed) @nogc @trusted const { 287 288 // Get tile variant to use 289 const variant = randomVariant( 290 Vector2I(sideArea.width, sideArea.height), 291 Vector2I(tileSize, sideSize), 292 seed + position.toHash, 293 ); 294 295 return RectangleI( 296 sideArea.x + variant.x, 297 sideArea.y + variant.y, 298 tileSize, 299 sideSize, 300 ); 301 302 } 303 304 }