1 module isodi.raylib.resources.decoration; 2 3 import raylib; 4 5 import std.string; 6 import std.random; 7 import std.typecons; 8 import std.exception; 9 10 import isodi.bind; 11 import isodi.cell; 12 import isodi.pack; 13 import isodi.raylib.cell; 14 import isodi.raylib.display; 15 import isodi.raylib.internal; 16 17 18 @safe: 19 20 21 private struct DecoPart { 22 uint x, y; 23 Texture2D texture; 24 const(ResourceOptions)* options; 25 float scale; 26 float direction; 27 } 28 29 /// A decoration resource. 30 struct Decoration { 31 32 /// Owner object. 33 RaylibCell cell; 34 35 /// Loaded decorations 36 DecoPart[] parts; 37 38 /// Create the tile and load textures. 39 this(RaylibCell cell, Pack.Resource!string[] resources) { 40 41 this.cell = cell; 42 43 Mt19937_64 rng; 44 45 foreach (i, resource; resources) { 46 47 // Load the texture 48 auto texture = cell.display.loadTexture(resource.match); 49 50 // Get direction of the decoration piece 51 rng.seed(cell.seed + 100 + 10*i); 52 const direction = uniform(0, 360, rng); 53 54 // Create the decoration 55 parts ~= DecoPart( 56 randomPosition(texture, resource.options, cell.seed + 101 + 10*i).expand, 57 texture, resource.options, 58 cast(float) cell.display.cellSize / resource.options.tileSize, 59 direction, 60 ); 61 62 } 63 64 } 65 66 /// Get a random position for the decoration piece. 67 private Tuple!(int, int) randomPosition(ref Texture2D texture, const ResourceOptions* options, ulong seed) { 68 69 // Get the tile and decoration size 70 const tileSize = cell.tile.texture.width; 71 const hardArea = options.hardArea; // TODO: default value 72 73 // Create an RNG 74 Mt19937_64 rng; 75 76 // Get a random position for the top-left corner of the hard area 77 rng.seed(seed); 78 const int x = uniform(0, tileSize - hardArea[2], rng); 79 80 rng.seed(seed + 1); 81 const int y = uniform(0, tileSize - hardArea[3], rng); 82 83 // Get a position in the bounds 84 return tuple( 85 x - cast(int) hardArea[0], 86 y - cast(int) hardArea[1], 87 ); 88 89 } 90 91 /// Draw the decoration 92 void draw() @trusted { 93 94 const cellSize = cell.display.cellSize; 95 96 foreach (deco; parts) { 97 98 rlPushMatrix(); 99 scope (exit) rlPopMatrix(); 100 101 import std.conv : to; 102 103 const angleDelimiter = 360 / deco.options.angles; 104 const rotation = cell.display.camera.angle.x + deco.direction; 105 const angle = to!uint(rotation / angleDelimiter + 0.5); 106 107 const atlasWidth = deco.texture.width / deco.options.angles; 108 const tileScale = cell.tile.scale; 109 110 // I tried to use a billboard here before but it didn't display at all. Idk why. 111 // Also billboard are affected by camera Y angle which we do not want, so they can't be used. 112 113 // Translate to the correct position 114 rlTranslatef(cell.visualPosition.toTuple3(cellSize).expand); 115 116 // Place within the tile 117 rlTranslatef( 118 deco.x * tileScale, 119 0, 120 deco.y * tileScale - cellSize, 121 ); 122 123 // Scale the tile to fit cell size 124 rlScalef(deco.scale, deco.scale, deco.scale); 125 126 // Undo the center 127 rlTranslatef(atlasWidth/2.0, 0, 0); 128 129 // Rotate to counter the camera 130 rlRotatef(cell.display.camera.angle.x, 0, 1, 0); 131 rlRotatef(-cell.display.camera.angle.y, 1, 0, 0); 132 133 // Make sure to center before rotating 134 rlTranslatef(atlasWidth/-2.0, -deco.texture.height, 0); 135 // We can assume centering is safe, because anchors are set per whole texture, not per angle, enforcing 136 // centering. Y shoud be good too, it just pulls the texture to the ground. 137 138 // Draw 139 deco.texture.DrawTextureRec( 140 Rectangle( 141 atlasWidth * angle, 0, 142 atlasWidth, deco.texture.height 143 ), 144 Vector2(), 145 cell.color 146 ); 147 148 } 149 150 } 151 152 }