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 }