lxgui
gui_atlas.cpp
1 #include "lxgui/gui_atlas.hpp"
2 
3 #include "lxgui/gui_exception.hpp"
4 #include "lxgui/gui_font.hpp"
5 #include "lxgui/gui_out.hpp"
6 #include "lxgui/gui_renderer.hpp"
7 #include "lxgui/gui_vertex.hpp"
8 #include "lxgui/utils_string.hpp"
9 
10 namespace lxgui::gui {
11 
12 atlas_page::atlas_page(material::filter filt) : filter_(filt) {}
13 
14 std::shared_ptr<material> atlas_page::fetch_material(const std::string& file_name) const {
15  auto iter = texture_list_.find(file_name);
16  if (iter != texture_list_.end()) {
17  if (std::shared_ptr<gui::material> lock = iter->second.lock())
18  return lock;
19  }
20 
21  return nullptr;
22 }
23 
24 std::shared_ptr<gui::material>
25 atlas_page::add_material(const std::string& file_name, const material& mat) {
26  try {
27  const auto rect = mat.get_rect();
28  const auto location = find_location_(rect.width(), rect.height());
29  if (!location.has_value())
30  return nullptr;
31 
32  std::shared_ptr<gui::material> tex = add_material_(mat, location.value());
33  texture_list_[file_name] = tex;
34  return tex;
35  } catch (const std::exception& e) {
36  gui::out << gui::warning << e.what() << std::endl;
37  return nullptr;
38  }
39 }
40 
41 std::shared_ptr<font> atlas_page::fetch_font(const std::string& font_name) const {
42  auto iter = font_list_.find(font_name);
43  if (iter != font_list_.end()) {
44  if (std::shared_ptr<gui::font> lock = iter->second.lock())
45  return lock;
46  }
47 
48  return nullptr;
49 }
50 
51 bool atlas_page::add_font(const std::string& font_name, std::shared_ptr<gui::font> fnt) {
52  try {
53  if (const auto mat = fnt->get_texture().lock()) {
54  const auto rect = mat->get_rect();
55  const auto location = find_location_(rect.width(), rect.height());
56  if (!location.has_value())
57  return false;
58 
59  std::shared_ptr<gui::material> tex = add_material_(*mat, location.value());
60  fnt->update_texture(tex);
61 
62  font_list_[font_name] = std::move(fnt);
63  return true;
64  } else
65  return false;
66  } catch (const std::exception& e) {
67  gui::out << gui::warning << e.what() << std::endl;
68  return false;
69  }
70 }
71 
72 bool atlas_page::empty() const {
73  for (const auto& mat : texture_list_) {
74  if (std::shared_ptr<gui::material> lock = mat.second.lock())
75  return false;
76  }
77 
78  for (const auto& fnt : font_list_) {
79  if (std::shared_ptr<gui::font> lock = fnt.second.lock())
80  return false;
81  }
82 
83  return true;
84 }
85 
86 std::optional<bounds2f> atlas_page::find_location_(float width, float height) const {
87  constexpr float padding = 1.0f; // pixels
88 
89  bounds2f start_quad(0, width, 0, height);
90  if (empty())
91  return start_quad;
92 
93  const float atlas_width = get_width_();
94  const float atlast_height = get_height_();
95 
96  std::vector<bounds2f> occupied_space;
97  occupied_space.reserve(texture_list_.size());
98 
99  float max_width = 0.0f;
100  float max_height = 0.0f;
101 
102  auto apply_padding = [&](bounds2f rect) {
103  rect.right += padding;
104  rect.bottom += padding;
105  return rect;
106  };
107 
108  for (const auto& mat : texture_list_) {
109  if (std::shared_ptr<gui::material> lock = mat.second.lock()) {
110  occupied_space.push_back(apply_padding(lock->get_rect()));
111  max_width = std::max(max_width, occupied_space.back().right);
112  max_height = std::max(max_height, occupied_space.back().bottom);
113  }
114  }
115 
116  for (const auto& fnt : font_list_) {
117  if (std::shared_ptr<gui::font> lock = fnt.second.lock()) {
118  occupied_space.push_back(apply_padding(lock->get_texture().lock()->get_rect()));
119  max_width = std::max(max_width, occupied_space.back().right);
120  max_height = std::max(max_height, occupied_space.back().bottom);
121  }
122  }
123 
124  float best_area = std::numeric_limits<float>::infinity();
125  bounds2f best_quad;
126 
127  for (const auto& rect_source : occupied_space) {
128  auto test_position = [&](const vector2f& pos) {
129  const bounds2f test_quad = start_quad + pos;
130  if (test_quad.right > atlas_width || test_quad.bottom > atlast_height)
131  return;
132 
133  const float new_max_width = std::max(max_width, test_quad.right);
134  const float new_max_height = std::max(max_height, test_quad.bottom);
135  const float new_area = new_max_width * new_max_height;
136 
137  if (new_area >= best_area)
138  return;
139 
140  for (const auto& rect_other : occupied_space) {
141  if (test_quad.overlaps(rect_other))
142  return;
143  }
144 
145  best_area = new_area;
146  best_quad = test_quad;
147  };
148 
149  test_position(rect_source.top_right());
150  test_position(rect_source.bottom_left());
151  }
152 
153  if (std::isfinite(best_area))
154  return best_quad;
155  else
156  return std::nullopt;
157 }
158 
159 atlas::atlas(renderer& rdr, material::filter filt) : renderer_(rdr), filter_(filt) {}
160 
161 std::shared_ptr<gui::material> atlas::fetch_material(const std::string& file_name) const {
162  for (const auto& item : page_list_) {
163  auto tex = item.page->fetch_material(file_name);
164  if (tex)
165  return tex;
166  }
167 
168  return nullptr;
169 }
170 
171 std::shared_ptr<gui::material>
172 atlas::add_material(const std::string& file_name, const material& mat) {
173  try {
174  for (const auto& item : page_list_) {
175  auto tex = item.page->add_material(file_name, mat);
176  if (tex)
177  return tex;
178 
179  if (item.page->empty()) {
180  gui::out << gui::warning << "Could not fit texture '" << file_name
181  << "' on any atlas page." << std::endl;
182  return nullptr;
183  }
184  }
185 
186  add_page_();
187  auto tex = page_list_.back().page->add_material(file_name, mat);
188  if (tex)
189  return tex;
190 
191  return nullptr;
192  } catch (const std::exception& e) {
193  gui::out << gui::warning << e.what() << std::endl;
194  return nullptr;
195  }
196 }
197 
198 std::shared_ptr<gui::font> atlas::fetch_font(const std::string& font_name) const {
199  for (const auto& item : page_list_) {
200  auto fnt = item.page->fetch_font(font_name);
201  if (fnt)
202  return fnt;
203  }
204 
205  return nullptr;
206 }
207 
208 bool atlas::add_font(const std::string& font_name, std::shared_ptr<gui::font> fnt) {
209  try {
210  for (const auto& item : page_list_) {
211  if (item.page->add_font(font_name, fnt))
212  return true;
213 
214  if (item.page->empty()) {
215  gui::out << gui::warning << "Could not fit font '" << font_name
216  << "' on any atlas page." << std::endl;
217  return false;
218  }
219  }
220 
221  add_page_();
222 
223  return page_list_.back().page->add_font(font_name, std::move(fnt));
224  } catch (const std::exception& e) {
225  gui::out << gui::warning << e.what() << std::endl;
226  return false;
227  }
228 }
229 
230 std::size_t atlas::get_page_count() const {
231  return page_list_.size();
232 }
233 
234 void atlas::add_page_() {
235  page_item item;
236  item.page = create_page_();
237 
238  // Add a white pixel as the first material in the atlas.
239  // This can be used for optimizing quad batching, to render
240  // quads with no texture.
241  color32 pixel{255, 255, 255, 255};
242  auto tex = renderer_.create_material(vector2ui(1u, 1u), &pixel);
243  item.no_texture_mat = item.page->add_material("", *tex);
244 
245  page_list_.push_back(std::move(item));
246 }
247 
248 } // namespace lxgui::gui
virtual std::shared_ptr< material > add_material_(const material &mat, const bounds2f &location)=0
Adds a new material to this page, at the provided location.
atlas_page(material::filter filt)
Constructor.
Definition: gui_atlas.cpp:12
std::shared_ptr< material > add_material(const std::string &file_name, const material &mat)
Creates a new material from a texture file.
Definition: gui_atlas.cpp:25
std::shared_ptr< font > fetch_font(const std::string &font_name) const
Find a font in this page (nullptr if not found).
Definition: gui_atlas.cpp:41
bool add_font(const std::string &font_name, std::shared_ptr< gui::font > fnt)
Creates a new font from a texture file.
Definition: gui_atlas.cpp:51
virtual float get_height_() const =0
Return the height of this page (in pixels).
std::shared_ptr< material > fetch_material(const std::string &file_name) const
Find a material in this page (nullptr if not found).
Definition: gui_atlas.cpp:14
virtual float get_width_() const =0
Return the width of this page (in pixels).
bool empty() const
Checks if this page is empty (contains no materials).
Definition: gui_atlas.cpp:72
std::size_t get_page_count() const
Return the number of pages in this atlas.
Definition: gui_atlas.cpp:230
std::shared_ptr< material > add_material(const std::string &file_name, const material &mat)
Add a new material to the atlas.
Definition: gui_atlas.cpp:172
virtual std::unique_ptr< atlas_page > create_page_()=0
Create a new page in this atlas.
bool add_font(const std::string &font_name, std::shared_ptr< gui::font > fnt)
Add a new font to the atlas.
Definition: gui_atlas.cpp:208
std::shared_ptr< material > fetch_material(const std::string &file_name) const
Find a material in this atlas (nullptr if not found).
Definition: gui_atlas.cpp:161
atlas(renderer &rdr, material::filter filt)
Constructor.
Definition: gui_atlas.cpp:159
renderer & renderer_
Definition: gui_atlas.hpp:193
std::shared_ptr< font > fetch_font(const std::string &font_name) const
Find a font in this atlas (nullptr if not found).
Definition: gui_atlas.cpp:198
A class that holds rendering data.
virtual bounds2f get_rect() const =0
Returns the pixel rect in pixels of the canvas containing this texture (if any).
Abstract type for implementation specific management.
std::shared_ptr< material > create_material(const std::string &file_name, material::filter filt=material::filter::none)
Creates a new material from a texture file.
vector2< float > vector2f
Holds 2D coordinates (as floats)
bounds2< float > bounds2f
Holds 2D bounds of a region (as floats).
vector2< std::size_t > vector2ui
Holds 2D coordinates (as unsigned integers)
std::ostream out
const std::string warning
Definition: gui_out.cpp:6
Holds a single color (byte RGBA, 32 bits)
Definition: gui_color.hpp:75