1 module isodi.utils; 2 3 import raylib; 4 import core.stdc.stdlib; 5 6 import std.string; 7 import std.random; 8 import std.functional; 9 10 11 @safe: 12 13 14 /// Integer vector. 15 /// 16 /// Note: Internally this might be later translated to a float anyway, so it might not be precise. 17 struct Vector2I { 18 19 align (4): 20 int x, y; 21 22 Vector2I opBinary(string op)(Vector2I vec) const => Vector2I( 23 mixin("x" ~ op ~ "vec.x"), 24 mixin("y" ~ op ~ "vec.y"), 25 ); 26 27 /// Get hash of the position. 28 size_t toHash() const nothrow @nogc => y + 0x9e3779b9 + (x << 6) + (x >>> 2); 29 // Taken from https://github.com/dlang/phobos/blob/master/std/typecons.d#L1234 30 // Which in turn takes from https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine 31 32 } 33 34 struct RectangleI { 35 36 align (4): 37 int x, y; 38 int width, height; 39 40 /// Convert the rectangle to a shader rectangle relative to texture size. 41 Rectangle toShader(Vector2 textureSize) @nogc const => Rectangle( 42 x / textureSize.x, 43 y / textureSize.y, 44 width / textureSize.x, 45 height / textureSize.y, 46 ); 47 48 } 49 50 /// Perform a reinterpret cast. 51 /// 52 /// Note: Using `cast()` for reinterpret casts is not legal for values, per spec. DMD does allow that, but LDC segfaults. 53 To reinterpret(To, From)(From from) 54 in { 55 static assert(From.sizeof == To.sizeof, "From and To type size mismatch"); 56 } 57 do { 58 59 union Union { 60 From before; 61 To after; 62 } 63 64 // Write the value to the union and read as the target 65 return Union(from).after; 66 67 } 68 69 /// Make a simple struct out of a static array. Used to apply `.tupleof` on the arrays in DMD 2.099. 70 auto structof(T, size_t size)(T[size] value) { 71 72 struct StructOf { 73 static foreach (i; 0..size) { 74 mixin(format!"T t%s;"(i)); 75 } 76 } 77 return value.reinterpret!StructOf; 78 79 } 80 81 /// Multiplying matrices the proper way 82 Matrix mul(Matrix[] ms...) @trusted @nogc nothrow { 83 84 auto result = ms[0]; 85 86 foreach (m; ms[1..$]) { 87 88 result = MatrixMultiply(result, m); 89 90 } 91 92 return result; 93 94 } 95 96 /// Assign a single chunk of values to an array, assuming the array is made up of fixed size chunks. 97 void assignChunk(T)(T[] range, size_t index, T[] values...) @nogc pure { 98 99 foreach (i, value; values) { 100 101 range[values.length * index + i] = value; 102 103 } 104 105 } 106 107 /// 108 @system 109 unittest { 110 111 int[6] foo; 112 113 foreach (i; 0 .. foo.length/2) { 114 115 foo[].assignChunk(i, 1, 2); 116 117 } 118 119 assert(foo == [1, 2, 1, 2, 1, 2]); 120 121 } 122 123 /// Assign a single chunk of values to an array, altering the array first. 124 template assignChunk(alias fun) { 125 126 void assignChunk(T, U)(T[] range, size_t index, U[] values...) @nogc pure { 127 128 alias funnier = unaryFun!fun; 129 130 foreach (i, value; values) { 131 132 range[values.length * index + i] = funnier(value); 133 134 } 135 136 } 137 138 } 139 140 /// 141 @system 142 unittest { 143 144 int[6] foo; 145 146 foreach (i; 0 .. foo.length/2) { 147 148 foo[].assignChunk!(a => cast(int) i*2 + a)(i, 1, 2); 149 150 } 151 152 assert(foo == [1, 2, 3, 4, 5, 6]); 153 154 } 155 156 /// Assign multiple copies of the same value to the array, indexing it by chunks of the same size. 157 void assign(T)(T[] range, size_t index, size_t count, T value) @nogc pure { 158 159 const start = count * index; 160 range[start .. start + count] = value; 161 162 } 163 164 /// Iterate on file ancestors, starting from and including the requested file and ending on the root. 165 struct DeepAncestors { 166 167 const string path; 168 169 int opApply(scope int delegate(string) @trusted dg) { 170 171 auto dir = path[]; 172 173 while (dir.length) { 174 175 // Remove trailing slashes 176 dir = dir.stripRight("/"); 177 178 // Yield the content 179 auto result = dg(dir); 180 if (result) return result; 181 182 // Get the position on which the segment ends 183 auto segmentEnd = dir.lastIndexOf("/"); 184 185 // Stop if this is the last segment 186 if (segmentEnd == -1) break; 187 188 // Remove last path segment 189 dir = dir[0 .. segmentEnd]; 190 191 } 192 193 // Push empty path 194 return dg(""); 195 196 } 197 198 } 199 200 /// Iterate on file ancestors starting from root, ending on and including the file itself. 201 struct Ancestors { 202 203 const wstring path; 204 205 int opApply(int delegate(wstring) @trusted dg) { 206 207 wstring current; 208 209 auto result = dg(""w); 210 if (result) return result; 211 212 // Check each value 213 foreach (ch; path) { 214 215 // Encountered a path separator 216 if (ch == '/' || ch == '\\') { 217 218 // Yield the current values 219 result = dg(current); 220 if (result) return result; 221 current ~= "/"; 222 223 } 224 225 // Add the character 226 else current ~= ch; 227 228 } 229 230 // Yield the full path 231 if (current.length && current[$-1] != '/') { 232 233 result = dg(current); 234 return result; 235 236 } 237 238 return 0; 239 240 } 241 242 } 243 244 /// Get a random number in inclusive range. 245 T randomNumber(T, RNG)(T min, T max, ref RNG rng) @trusted 246 in (min <= max, "minimum value must be lesser or equal to max") 247 do { 248 249 import std.conv; 250 251 // This is a copy-paste from https://github.com/dlang/phobos/blob/v2.100.0/std/random.d#L2218 252 // but made @nogc 253 254 auto upperDist = unsigned(max - min) + 1u; 255 assert(upperDist != 0); 256 257 alias UpperType = typeof(upperDist); 258 static assert(UpperType.min == 0); 259 260 UpperType offset, rnum, bucketFront; 261 do { 262 263 rnum = uniform!UpperType(rng); 264 offset = rnum % upperDist; 265 bucketFront = rnum - offset; 266 } 267 268 // While we're in an unfair bucket... 269 while (bucketFront > (UpperType.max - (upperDist - 1))); 270 271 return cast(T) (min + offset); 272 273 } 274 275 /// Generate a random variant in the given atlas. 276 /// Param: 277 /// atlasSize = Size of the atlas used. 278 /// resultSize = Expected size of the result 279 /// seed = Seed to use 280 /// Returns: Offset of the variant texture in the given atlas. 281 Vector2I randomVariant(Vector2I atlasSize, Vector2I resultSize, ulong seed) @nogc { 282 283 auto rng = Mt19937_64(seed); 284 285 // Get the grid dimensions 286 const gridSize = atlasSize / resultSize; 287 288 // Get the variant number 289 const tileVariantCount = gridSize.x * gridSize.y; 290 assert(tileVariantCount > 0, "Invalid atlasSize or resultSize, all parameters must be positive"); 291 292 const variant = randomNumber(0, tileVariantCount-1, rng); 293 294 // Get the offset 295 return resultSize * Vector2I( 296 variant % gridSize.x, 297 variant / gridSize.x, 298 ); 299 300 } 301 302 /// Awful workaround to get writefln in @nogc :D 303 /// Yes, it does allocate in the GC. 304 package debug template writefln(T...) { 305 306 void writefln(Args...)(Args args) @nogc @system { 307 308 // We can GC-allocate in writeln, no need to care about that 309 scope auto fundebug = delegate { 310 311 import io = std.stdio; 312 io.writefln!T(args); 313 314 }; 315 (cast(void delegate() @nogc) fundebug)(); 316 317 } 318 319 }