1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module atelier.render.tileset;
7 
8 import std.conv;
9 
10 import bindbc.sdl, bindbc.sdl.image;
11 
12 import atelier.core;
13 import atelier.render.window;
14 import atelier.render.sprite;
15 import atelier.render.drawable;
16 
17 /// Series of aligned tiles.
18 final class Tileset {
19     @property {
20         /// loaded ?
21         bool isLoaded() const {
22             return drawable.isLoaded;
23         }
24         /// Number of tiles
25         int maxtiles() const {
26             return _maxtiles;
27         }
28         /// Ditto
29         int maxtiles(int newMaxTiles) {
30             _maxtiles = newMaxTiles;
31             if (_maxtiles <= 0 || _maxtiles > columns * lines)
32                 _maxtiles = columns * lines;
33             return _maxtiles;
34         }
35     }
36 
37     /// The maximum number of tile, cannot be more than columns * lines, cannot be less than 0.
38     private int _maxtiles;
39 
40     /// Drawable region of the first tile (the source size).
41     Vec4i clip;
42 
43     /// Spacing between each tiles.
44     Vec2i margin;
45 
46     /// Number of tiles on the horizontal axis.
47     int columns = 1, /// Number of tiles on the vertical axis.
48         lines = 1;
49 
50     /// Source material.
51     Drawable drawable;
52 
53     /// Destination size.
54     Vec2f size = Vec2f.zero, scale = Vec2f.one;
55 
56     /// Angle in which the sprite will be rendered.
57     float angle = 0f;
58 
59     /// Mirroring property.
60     Flip flip = Flip.none;
61 
62     /// Anchor.
63     Vec2f anchor = Vec2f.half;
64 
65     /// Color added to the tile.
66     Color color = Color.white;
67 
68     /// Opacity of the tile.
69     float alpha = 1f;
70 
71     /// Blending algorithm.
72     Blend blend = Blend.alpha;
73 
74     /// Ctor
75     this() {
76     }
77 
78     /// Copy ctor
79     this(Tileset tileset) {
80         _maxtiles = tileset._maxtiles;
81         clip = tileset.clip;
82         margin = tileset.margin;
83         columns = tileset.columns;
84         lines = tileset.lines;
85         drawable = tileset.drawable;
86         size = tileset.size;
87         scale = tileset.scale;
88         angle = tileset.angle;
89         flip = tileset.flip;
90         anchor = tileset.anchor;
91         color = tileset.color;
92         alpha = tileset.alpha;
93         blend = tileset.blend;
94     }
95 
96     /// Ctor
97     this(Drawable newDrawable, Vec4i newClip, int newColumns, int newLines, int newMaxTiles = -1) {
98         drawable = newDrawable;
99         clip = newClip;
100         columns = newColumns;
101         lines = newLines;
102         size = to!Vec2f(clip.zw);
103         maxtiles(newMaxTiles);
104     }
105 
106     /// Create a new Sprite from the tile id.
107     Sprite getSprite(int id) {
108         if (id >= _maxtiles)
109             id = _maxtiles - 1;
110         if (id < 0)
111             id = 0;
112         Vec2i coord = Vec2i(id % columns, id / columns);
113         Vec4i spriteClip = Vec4i(clip.x + coord.x * clip.z, clip.y + coord.y * clip.w,
114                 clip.z, clip.w);
115         Sprite sprite = new Sprite(drawable, spriteClip);
116         sprite.flip = flip;
117         sprite.blend = blend;
118         sprite.color = color;
119         sprite.alpha = alpha;
120         sprite.anchor = anchor;
121         sprite.angle = angle;
122         sprite.size = size;
123         return sprite;
124     }
125 
126     /// Return each tile as a Sprite.
127     Sprite[] asSprites() {
128         Sprite[] sprites;
129         foreach (id; 0 .. _maxtiles)
130             sprites ~= getSprite(id);
131         return sprites;
132     }
133 
134     /// Set the sprite's size to fit inside the specified size.
135     void fit(Vec2f size_) {
136         size = to!Vec2f(clip.zw).fit(size_);
137     }
138 
139     /// Render a tile
140     void drawRotated(int id, const Vec2f position) {
141         if (id >= _maxtiles)
142             id = _maxtiles - 1;
143         if (id < 0)
144             id = 0;
145 
146         Vec2i coord = Vec2i(id % columns, id / columns);
147         if (coord.y > lines)
148             throw new Exception("Tileset id out of bounds");
149 
150         Vec2f finalSize = scale * size * transformScale();
151         Vec2f dist = (anchor - Vec2f.half).rotated(angle) * size * scale;
152 
153         Vec4i currentClip = Vec4i(clip.x + coord.x * (clip.z + margin.x),
154                 clip.y + coord.y * (clip.w + margin.y), clip.z, clip.w);
155         drawable.color = color;
156         drawable.blend = blend;
157         drawable.alpha = alpha;
158         drawable.draw(transformRenderSpace(position - dist), finalSize, currentClip, angle, flip);
159     }
160 
161     /// Ditto
162     void draw(int id, const Vec2f position) {
163         if (id >= _maxtiles)
164             id = _maxtiles - 1;
165         if (id < 0)
166             id = 0;
167 
168         Vec2f finalSize = scale * size * transformScale();
169         Vec2i coord = Vec2i(id % columns, id / columns);
170         if (coord.y > lines)
171             throw new Exception("Tileset id out of bounds");
172         Vec4i currentClip = Vec4i(clip.x + coord.x * (clip.z + margin.x),
173                 clip.y + coord.y * (clip.w + margin.y), clip.z, clip.w);
174         drawable.color = color;
175         drawable.blend = blend;
176         drawable.alpha = alpha;
177         drawable.draw(transformRenderSpace(position), finalSize, currentClip, angle, flip, anchor);
178     }
179 }