1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module atelier.render.sprite;
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.drawable;
15 import atelier.render.drawable;
16 
17 /// Renders a **Texture** with its own properties.
18 final class Sprite {
19     @property {
20         /// Is the drawable loaded ?
21         bool isValid() const {
22             return drawable !is null;
23         }
24 
25         /// Get the anchored and scaled center of the sprite.
26         Vec2f center() const {
27             return anchor * size * scale;
28         }
29     }
30 
31     /// Texture being used.
32     Drawable drawable;
33 
34     /// Mirroring property.
35     Flip flip = Flip.none;
36 
37     /// Scale of the sprite.
38     Vec2f scale = Vec2f.one;
39 
40     /// Size of the sprite.
41     Vec2f size = Vec2f.zero;
42 
43     /// Relative center of the sprite.
44     Vec2f anchor = Vec2f.half;
45 
46     /// Texture region being rendered (the source size).
47     Vec4i clip;
48 
49     /// Angle in which the sprite will be rendered.
50     float angle = 0f;
51 
52     /// Color added to the sprite.
53     Color color = Color.white;
54 
55     /// Alpha
56     float alpha = 1f;
57 
58     /// Blending algorithm.
59     Blend blend = Blend.alpha;
60 
61     /// Default ctor.
62     this() {
63     }
64 
65     /// Copy another sprite.
66     this(Sprite sprite) {
67         drawable = sprite.drawable;
68         flip = sprite.flip;
69         scale = sprite.scale;
70         size = sprite.size;
71         anchor = sprite.anchor;
72         clip = sprite.clip;
73         angle = sprite.angle;
74         color = sprite.color;
75         alpha = sprite.alpha;
76         blend = sprite.blend;
77     }
78 
79     /// Default sprite that takes the whole Texture.
80     this(Drawable drawable_, Flip flip_ = Flip.none) {
81         drawable = drawable_;
82         if (!drawable) {
83             clip = Vec4i.zero;
84             size = Vec2f.zero;
85         }
86         else {
87             clip = Vec4i(0, 0, drawable.width, drawable.height);
88             size = to!Vec2f(clip.zw);
89         }
90         flip = flip_;
91     }
92 
93     /// Sprite that takes a clipped region of a Texture.
94     this(Drawable drawable_, Vec4i clip_, Flip flip_ = Flip.none) {
95         drawable = drawable_;
96         clip = clip_;
97         size = to!Vec2f(clip.zw);
98         flip = flip_;
99     }
100 
101     /// Reset the sprite to take the whole specified Texture.
102     Sprite opAssign(Drawable drawable_) {
103         drawable = drawable_;
104         if (!drawable) {
105             clip = Vec4i.zero;
106             size = Vec2f.zero;
107         }
108         else {
109             clip = Vec4i(0, 0, drawable.width, drawable.height);
110             size = to!Vec2f(clip.zw);
111         }
112         return this;
113     }
114 
115     /// Set the sprite's size to fit inside the specified size.
116     void fit(Vec2f size_) {
117         size = to!Vec2f(clip.zw).fit(size_);
118     }
119 
120     /// Set the sprite's size to contain the specified size.
121     void contain(Vec2f size_) {
122         size = to!Vec2f(clip.zw).contain(size_);
123     }
124 
125     /// Render the sprite there.
126     void draw(const Vec2f position) {
127         assert(drawable, "Texture is null");
128         Vec2f finalSize = size * scale * transformScale();
129         //if (isVisible(position, finalSize)) {
130         drawable.color = color;
131         drawable.blend = blend;
132         drawable.alpha = alpha;
133         drawable.draw(transformRenderSpace(position), finalSize, clip, angle, flip, anchor);
134         //}
135     }
136 
137     /// Ditto
138     void drawUnchecked(const Vec2f position) {
139         assert(drawable, "Texture is null");
140         Vec2f finalSize = size * scale * transformScale();
141         drawable.color = color;
142         drawable.blend = blend;
143         drawable.alpha = alpha;
144         drawable.draw(transformRenderSpace(position), finalSize, clip, angle, flip, anchor);
145     }
146 
147     /// Ditto
148     void drawRotated(const Vec2f position) {
149         assert(drawable, "Texture is null");
150         Vec2f finalSize = size * scale * transformScale();
151         Vec2f dist = (anchor - Vec2f.half) * size * scale;
152         dist.rotate(angle);
153         drawable.color = color;
154         drawable.blend = blend;
155         drawable.alpha = alpha;
156         drawable.draw(transformRenderSpace(position - dist), finalSize, clip, angle, flip);
157     }
158 
159     /// Ditto
160     void draw(const Vec2f pivot, float pivotDistance, float pivotAngle) {
161         assert(drawable, "Texture is null");
162         Vec2f finalSize = size * scale * transformScale();
163         drawable.color = color;
164         drawable.blend = blend;
165         drawable.alpha = alpha;
166         drawable.draw(transformRenderSpace(pivot + Vec2f.angled(pivotAngle) * pivotDistance),
167                 finalSize, clip, angle, flip, anchor);
168     }
169 
170     /// Ditto
171     void draw(const Vec2f pivot, const Vec2f pivotOffset, float pivotAngle) {
172         assert(drawable, "Texture is null");
173         Vec2f finalSize = size * scale * transformScale();
174         drawable.color = color;
175         drawable.blend = blend;
176         drawable.alpha = alpha;
177         drawable.draw(transformRenderSpace(pivot + pivotOffset.rotated(pivotAngle)),
178                 finalSize, clip, angle, flip, anchor);
179     }
180 
181     /// Is this inside the sprite region ? \
182     /// Note: Does not take angle into account. may not work properly.
183     bool isInside(const Vec2f position) const {
184         Vec2f halfSize = size * scale * transformScale() * 0.5f;
185         return position.isBetween(-halfSize, halfSize);
186     }
187 }