ge211
ge211_sprites.cpp
1 #include "ge211_sprites.h"
2 #include "ge211_error.h"
3 
4 #include <SDL.h>
5 #include <SDL_image.h>
6 #include <SDL_ttf.h>
7 
8 #include <cmath>
9 
10 namespace ge211 {
11 
12 using namespace detail;
13 
14 Sprite_set::Sprite_set() {}
15 
16 Sprite_set&
17 Sprite_set::add_sprite(const Sprite& sprite, Position xy, int z,
18  const Transform& t)
19 {
20  sprites_.emplace_back(sprite, xy, z, t);
21  return *this;
22 }
23 
25 {
26  return add_sprite(sprite, xy, z, Transform{});
27 }
28 
29 namespace detail {
30 
31 Placed_sprite::Placed_sprite(const Sprite& sprite, Position xy,
32  int z, const Transform& transform) noexcept
33  : sprite{&sprite}, xy{xy}, z{z}, transform{transform}
34 { }
35 
36 void Placed_sprite::render(Renderer& dst) const
37 {
38  sprite->render(dst, xy, transform);
39 }
40 
41 bool operator<(const Placed_sprite& s1, const Placed_sprite& s2) noexcept
42 {
43  return s1.z > s2.z;
44 }
45 
46 Dimensions Texture_sprite::dimensions() const
47 {
48  return get_texture_().dimensions();
49 }
50 
51 void Texture_sprite::render(Renderer& renderer,
52  Position position,
53  const Transform& transform) const
54 {
55  if (transform.is_identity())
56  renderer.copy(get_texture_(), position);
57  else
58  renderer.copy(get_texture_(), position, transform);
59 }
60 
61 void Texture_sprite::prepare(const Renderer& renderer) const
62 {
63  renderer.prepare(get_texture_());
64 }
65 
66 delete_ptr<SDL_Surface> Render_sprite::create_surface_(Dimensions dimensions)
67 {
68  SDL_Surface* surface =
69  SDL_CreateRGBSurfaceWithFormat(0,
70  dimensions.width,
71  dimensions.height,
72  32,
73  SDL_PIXELFORMAT_RGBA32);
74  if (surface) {
75  return {surface, &SDL_FreeSurface};
76  }
77 
78  throw Host_error{"Could not create sprite surface"};
79 }
80 
81 Render_sprite::Render_sprite(Dimensions dimensions)
82  : texture_{create_surface_(dimensions)}
83 { }
84 
85 const Texture& Render_sprite::get_texture_() const
86 {
87  return texture_;
88 }
89 
90 SDL_Surface* Render_sprite::as_surface()
91 {
92  SDL_Surface* result = texture_.as_surface();
93  if (result) return result;
94 
95  throw Ge211_logic_error{"Render_sprite::as_surface: already a texture"};
96 }
97 
98 void Render_sprite::fill_surface(Color color)
99 {
100  auto surface = as_surface();
101  SDL_FillRect(surface, nullptr, color.to_sdl_(surface->format));
102 }
103 
104 void Render_sprite::fill_rectangle(Rectangle rect, Color color)
105 {
106  auto surface = as_surface();
107  SDL_Rect rect_buf = rect;
108  SDL_FillRect(surface, &rect_buf, color.to_sdl_(surface->format));
109 }
110 
111 void Render_sprite::set_pixel(Position xy, Color color)
112 {
113  fill_rectangle({xy.x, xy.y, 1, 1}, color);
114 }
115 
116 } // end namespace detail
117 
118 namespace sprites {
119 
120 static Dimensions check_rectangle_dimensions(Dimensions dims)
121 {
122  if (dims.width <= 0 || dims.height <= 0) {
123  throw Client_logic_error(
124  "Rectangle_sprite: width and height must both be positive");
125  }
126 
127  return dims;
128 }
129 
130 Rectangle_sprite::Rectangle_sprite(Dimensions dims, Color color)
131  : Render_sprite{check_rectangle_dimensions(dims)}
132 {
133  fill_surface(color);
134 }
135 
137 {
138  *this = Rectangle_sprite{dimensions(), color};
139 }
140 
141 static Dimensions compute_circle_dimensions(int radius)
142 {
143  if (radius <= 0) {
144  throw Client_logic_error("Circle_sprite: radius must be positive");
145  }
146 
147  return {radius * 2, radius * 2};
148 }
149 
151  : Render_sprite{compute_circle_dimensions(radius)}
152 {
153  const int cx = radius;
154  const int cy = radius;
155 
156  for (int y = 0; y < radius; ++y) {
157  for (int x = 0; x < radius; ++x) {
158  if (x * x + y * y < radius * radius) {
159  set_pixel({cx + x, cy + y}, color);
160  set_pixel({cx + x, cy - y - 1}, color);
161  set_pixel({cx - x - 1, cy + y}, color);
162  set_pixel({cx - x - 1, cy - y - 1}, color);
163  }
164  }
165  }
166 }
167 
169 {
170  *this = Circle_sprite{radius_(), color};
171 }
172 
173 int Circle_sprite::radius_() const
174 {
175  return dimensions().width >> 1;
176 }
177 
178 Texture
179 Image_sprite::load_texture_(const std::string& filename)
180 {
181  File_resource file(filename);
182  SDL_Surface* raw = IMG_Load_RW(file.get_raw(), 0);
183  if (raw) return Texture(raw);
184 
185  throw Image_error::could_not_load(filename);
186 }
187 
188 Image_sprite::Image_sprite(const std::string& filename)
189  : texture_{load_texture_(filename)} {}
190 
191 const Texture& Image_sprite::get_texture_() const
192 {
193  return texture_;
194 }
195 
196 Texture
197 Text_sprite::create_texture(const Builder& config)
198 {
199  SDL_Surface* raw;
200 
201  std::string message = config.message();
202 
203  if (message.empty())
204  return Texture{};
205 
206  if (config.word_wrap() > 0) {
207  raw = TTF_RenderUTF8_Blended_Wrapped(
208  config.font().get_raw_(),
209  message.c_str(),
210  config.color().to_sdl_(),
211  static_cast<uint32_t>(config.word_wrap()));
212  } else {
213  auto render = config.antialias() ?
214  &TTF_RenderUTF8_Blended :
215  &TTF_RenderUTF8_Solid;
216  raw = render(config.font().get_raw_(),
217  message.c_str(),
218  config.color().to_sdl_());
219  }
220 
221  if (!raw)
222  throw Host_error{"Could not render text: “" + message + "”"};
223  else
224  return Texture{raw};
225 }
226 
227 Text_sprite::Text_sprite(const Text_sprite::Builder& config)
228  : texture_{create_texture(config)} {}
229 
231  : texture_{} {}
232 
233 Text_sprite::Text_sprite(const std::string& message,
234  const Font& font)
235  : Text_sprite{Builder{font}.message(message)} {}
236 
237 const Texture& Text_sprite::get_texture_() const
238 {
239  assert_initialized_();
240  return texture_;
241 }
242 
243 void Text_sprite::assert_initialized_() const
244 {
245  if (texture_.empty())
246  throw Client_logic_error{"Attempt to render empty Text_sprite"};
247 }
248 
250  : message_{}, font_{&font}, color_{Color::white()}, antialias_{true},
251  word_wrap_{0} {}
252 
254 {
255  message_.str(message);
256  return *this;
257 }
258 
260 {
261  font_ = &font;
262  return *this;
263 }
264 
266 {
267  color_ = color;
268  return *this;
269 }
270 
272 {
273  antialias_ = antialias;
274  return *this;
275 }
276 
278 {
279  if (word_wrap < 0) word_wrap = 0;
280  word_wrap_ = static_cast<uint32_t>(word_wrap);
281  return *this;
282 }
283 
285 {
286  return Text_sprite{*this};
287 }
288 
289 std::string Text_sprite::Builder::message() const
290 {
291  return message_.str();
292 }
293 
295 {
296  return *font_;
297 }
298 
300 {
301  return color_;
302 }
303 
305 {
306  return antialias_;
307 }
308 
310 {
311  return static_cast<int>(word_wrap_);
312 }
313 
315 {
316  texture_ = create_texture(config);
317 }
318 
319 bool Text_sprite::empty() const
320 {
321  return texture_.empty();
322 }
323 
324 Text_sprite::operator bool() const
325 {
326  return !empty();
327 }
328 
330 {
331  since_.reset();
332 }
333 
334 void Multiplexed_sprite::render(detail::Renderer& renderer,
335  Position position,
336  Transform const& transform) const
337 {
338  const Sprite& selection = select_(since_.elapsed_time());
339  selection.render(renderer, position, transform);
340 }
341 
342 } // end namespace sprites
343 
344 }
int word_wrap() const
Gets the wrapping width that will be used.
Builder(Font const &)
Constructs a new Text_sprite::Builder with the given Font.
void recolor(Color)
Changes the color of this circle sprite.
Text_sprite build() const
Builds the configured Text_sprite.
void reset()
Resets the age of the sprite to 0.
Coordinate width
The width of the object.
A Sprite that renders as a solid rectangle.
bool antialias() const
Gets whether anti-aliasing will be used.
The game engine namespace.
Definition: ge211.h:17
Circle_sprite(int radius, Color=Color::white())
Constructs a circle sprite from its radius and optionally a Color, which defaults to white...
Font const & font() const
Gets the font that will be used.
Represents a font that can be used to render a sprites::Text_sprite.
virtual Dimensions dimensions() const =0
Returns the current dimensions of this Sprite.
Image_sprite(std::string const &filename)
Constructs an image sprite, given the filename of the image to display.
Sprite_set & add_sprite(Sprite const &, Position, int z=0)
Adds the given sprite at the given x–y geometry::Position and optional z coordinate, which defaults to 0.
Color color() const
Gets the color that will be used.
A sprite is an image that knows how to render itself to the screen at a given location, under a particular transformation.
Definition: ge211_sprites.h:33
A Sprite that renders as a solid circle.
Represents the dimensions of an object, or more generally, the displacement between two Basic_positio...
Definition: ge211_forward.h:73
A position in the T-valued Cartesian plane.
Definition: ge211_forward.h:74
std::string message() const
Gets the configured message.
A rendering transform, which can scale, flip, and rotate.
A Sprite that displays text.
bool empty() const
Is this Text_sprite empty? (If so, you shouldn&#39;t try to use it.)
static constexpr Color white() noexcept
Solid white.
Definition: ge211_color.h:57
void reconfigure(Builder const &)
Resets this text sprite with the configuration from the given Builder.
Text_sprite()
Constructs an empty text sprite.
An exception that indicates that a logic error was performed by the client.
Definition: ge211_error.h:46
For representing colors.
Definition: ge211_color.h:22
A collection of positioned sprites ready to be rendered to the screen.
Basic_dimensions< int > Dimensions
Type alias for the most common use of Basic_dimensions, which is with a coordinate type of int...
Definition: ge211_forward.h:77
void recolor(Color)
Changes the color of this rectangle sprite.
Builder-style API for configuring and constructing Text_sprites.