Loading...
Searching...
No Matches
Texture.cpp
Go to the documentation of this file.
1#include "hellfire/graphics/texture/Texture.h"
2
3#include <GL/glew.h>
4
5#define STB_IMAGE_IMPLEMENTATION
6#include <fstream>
7#include <iostream>
8#include <stb/stb_image.h>
9
10#include "hellfire/graphics/material/Material.h"
11
12namespace hellfire {
13 // Static cache for TextureCache
15
17 TextureSettings settings;
18
19 settings.max_size = 1024;
20 switch (type) {
22 settings.flip_vertically = true;
23 break;
27 settings.min_filter = TextureFilter::LINEAR; // Single channel, less filtering
28 break;
31 settings.generate_mipmaps = true; // Full quality for color textures
32 break;
33 default:
34 break;
35 }
36
37 return settings;
38 }
39
40 Texture::Texture(const std::string &path, TextureType type)
41 : Texture(path, type, TextureSettings::for_type(type)) {
42 }
43
44 Texture::Texture(const std::string &path, TextureType type, const TextureSettings &settings)
45 : path_(path), type_(type), settings_(settings) {
47 }
48
49 Texture::Texture(Texture &&other) noexcept
50 : width(other.width), height(other.height), nr_channels(other.nr_channels),
51 type_(other.type_), path_(std::move(other.path_)),
53 other.texture_id_ = 0; // Transfer ownership
54 }
55
56 Texture &Texture::operator=(Texture &&other) noexcept {
57 if (this != &other) {
58 // Clean up current texture
59 if (texture_id_ != 0) {
60 glDeleteTextures(1, &texture_id_);
61 }
62
63 // Transfer ownership
64 width = other.width;
65 height = other.height;
67 type_ = other.type_;
68 path_ = std::move(other.path_);
70 settings_ = other.settings_;
71
72 other.texture_id_ = 0;
73 }
74 return *this;
75 }
76
78 if (texture_id_ != 0) {
79 glDeleteTextures(1, &texture_id_);
80 }
81 }
82
84 texture_id_ = 0;
85 width = height = nr_channels = 0;
86 is_valid_ = false;
87
88 // Check if file exists
89 if (const std::ifstream file(path_); !file.good()) {
90 std::cerr << "Texture file does not exist: " << path_ << std::endl;
91 return;
92 }
93
94 // Set STBI settings
95 stbi_set_flip_vertically_on_load(settings_.flip_vertically);
96 int desired_channels = 0;
98 desired_channels = 3;
101 desired_channels = 1;
102 }
103
104 unsigned char *data = stbi_load(path_.c_str(), &width, &height, &nr_channels, desired_channels);
105
106 if (desired_channels > 0) {
107 nr_channels = desired_channels;
108 }
109
110 if (!data) {
111 const char *error = stbi_failure_reason();
112 std::cerr << "STBI failed to load: " << path_
113 << " - " << (error ? error : "Unknown error") << std::endl;
114 return;
115 }
116
117 // Validate loaded parts
118 if (width <= 0 || height <= 0 || nr_channels <= 0) {
119 std::cerr << "Invalid texture data: " << width << "x" << height
120 << " channels=" << nr_channels << std::endl;
121 stbi_image_free(data);
122 return;
123 }
124
125 // Generate OpenGL texture
126 glGenTextures(1, &texture_id_);
127 if (texture_id_ == 0) {
128 std::cerr << "Failed to generate OpenGL texture" << std::endl;
129 stbi_image_free(data);
130 return;
131 }
132
133 glBindTexture(GL_TEXTURE_2D, texture_id_);
134 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
135
136 // Determine formats
137 GLenum format, internal_format;
138 switch (nr_channels) {
139 case 1:
140 format = GL_RED;
141 internal_format = GL_R8;
142 break;
143 case 3:
144 format = GL_RGB;
145 internal_format = GL_RGB8;
146 break;
147 case 4:
148 format = GL_RGBA;
149 internal_format = GL_RGBA8;
150 break;
151 default:
152 std::cerr << "Unsupported channel count: " << nr_channels << std::endl;
153 stbi_image_free(data);
154 glDeleteTextures(1, &texture_id_);
155 texture_id_ = 0;
156 return;
157 }
158
159 // Handle special texture types
162 if (nr_channels >= 3) {
163 format = GL_RED; // Read only red channel from source
164 internal_format = GL_R8; // Store as single channel
165 }
166 }
167
168 // Upload to GPU
169 glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
170
171 GLenum gl_error = glGetError();
172 if (gl_error != GL_NO_ERROR) {
173 std::cerr << "OpenGL error uploading texture: " << gl_error << std::endl;
174 stbi_image_free(data);
175 glDeleteTextures(1, &texture_id_);
176 texture_id_ = 0;
177 return;
178 }
179
180 // Generate mipmaps and set parameters
182 glGenerateMipmap(GL_TEXTURE_2D);
183 }
184
185 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, get_gl_wrap_mode(settings_.wrap_s));
186 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, get_gl_wrap_mode(settings_.wrap_t));
187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_gl_filter_mode(settings_.min_filter));
188 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_gl_filter_mode(settings_.mag_filter));
189
190 // Clean up and mark as valid
191 stbi_image_free(data);
192 is_valid_ = true;
193
194 // std::cout << "Successfully loaded texture: " << path_
195 // << " (" << width << "x" << height << ", " << nr_channels << " channels)" << std::endl;
196 }
197
198 bool Texture::is_valid() const {
199 return texture_id_ != 0 && is_valid_ && width > 0 && height > 0;
200 }
201
202 void Texture::bind(unsigned int slot) const {
203 glActiveTexture(GL_TEXTURE0 + slot);
204 glBindTexture(GL_TEXTURE_2D, texture_id_);
205 }
206
207 void Texture::unbind() const {
208 glBindTexture(GL_TEXTURE_2D, 0);
209 }
210
211 std::string Texture::type_to_string(TextureType type) {
212 switch (type) {
213 case TextureType::DIFFUSE: return "Diffuse";
214 case TextureType::SPECULAR: return "Specular";
215 case TextureType::NORMAL: return "Normal";
216 case TextureType::AMBIENT_OCCLUSION: return "AO";
217 case TextureType::ROUGHNESS: return "Roughness";
218 case TextureType::METALNESS: return "Metalness";
219 case TextureType::EMISSIVE: return "Emissive";
220 case TextureType::HEIGHT: return "Height";
221 case TextureType::OPACITY: return "Opacity";
222 default: return "Unknown";
223 }
224 }
225
227 switch (type) {
228 case TextureType::DIFFUSE: return "uDiffuseTexture";
229 case TextureType::SPECULAR: return "uSpecularTexture";
230 case TextureType::NORMAL: return "uNormalTexture";
231 case TextureType::AMBIENT_OCCLUSION: return "uAOTexture";
232 case TextureType::ROUGHNESS: return "uRoughnessTexture";
233 case TextureType::METALNESS: return "uMetalnessTexture";
234 case TextureType::EMISSIVE: return "uEmissiveTexture";
235 case TextureType::HEIGHT: return "uHeightTexture";
236 case TextureType::OPACITY: return "uOpacityTexture";
237 default: return "uTexture";
238 }
239 }
240
241 GLint Texture::get_gl_wrap_mode(const TextureWrap wrap) const {
242 switch (wrap) {
243 case TextureWrap::REPEAT: return GL_REPEAT;
244 case TextureWrap::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
245 case TextureWrap::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER;
246 case TextureWrap::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT;
247 default: return GL_REPEAT;
248 }
249 }
250
252 switch (filter) {
253 case TextureFilter::NEAREST: return GL_NEAREST;
254 case TextureFilter::LINEAR: return GL_LINEAR;
255 case TextureFilter::LINEAR_MIPMAP_LINEAR: return GL_LINEAR_MIPMAP_LINEAR;
256 case TextureFilter::NEAREST_MIPMAP_NEAREST: return GL_NEAREST_MIPMAP_NEAREST;
257 default: return GL_LINEAR;
258 }
259 }
260
261 std::shared_ptr<Texture> TextureCache::load(const std::string &path, TextureType type,
262 const TextureSettings &settings) {
263 std::string cache_key = path + "_" + std::to_string(static_cast<int>(type));
264 const auto it = cache_.find(cache_key);
265 if (it != cache_.end()) {
266 auto shared_texture = it->second.lock();
267 if (shared_texture) {
268 return shared_texture;
269 }
270 cache_.erase(it);
271 }
272
273 // Create new texture
274 auto texture = std::make_shared<Texture>(path, type);
275 if (texture->is_valid()) {
276 cache_[cache_key] = texture;
277 return texture;
278 } else {
279 std::cerr << "Failed to create valid texture from: " << path << std::endl;
280 return nullptr;
281 }
282 }
283
285 cache_.clear();
286 }
287
289 for (auto it = cache_.begin(); it != cache_.end();) {
290 if (it->second.expired()) {
291 it = cache_.erase(it);
292 } else {
293 ++it;
294 }
295 }
296 return cache_.size();
297 }
298
299 MaterialTextureSet &MaterialTextureSet::diffuse(const std::string &path) {
300 textures_[TextureType::DIFFUSE] = TextureCache::load(path, TextureType::DIFFUSE);
301 return *this;
302 }
303
304 MaterialTextureSet &MaterialTextureSet::normal(const std::string &path) {
305 textures_[TextureType::NORMAL] = TextureCache::load(path, TextureType::NORMAL);
306 return *this;
307 }
308
309 MaterialTextureSet &MaterialTextureSet::specular(const std::string &path) {
310 textures_[TextureType::SPECULAR] = TextureCache::load(path, TextureType::SPECULAR);
311 return *this;
312 }
313
314 MaterialTextureSet &MaterialTextureSet::roughness(const std::string &path) {
315 textures_[TextureType::ROUGHNESS] = TextureCache::load(path, TextureType::ROUGHNESS);
316 return *this;
317 }
318
319 MaterialTextureSet &MaterialTextureSet::metalness(const std::string &path) {
320 textures_[TextureType::METALNESS] = TextureCache::load(path, TextureType::METALNESS);
321 return *this;
322 }
323
324 MaterialTextureSet &MaterialTextureSet::texture(TextureType type, const std::string &path) {
325 textures_[type] = TextureCache::load(path, type);
326 return *this;
327 }
328
329 MaterialTextureSet &MaterialTextureSet::ao(const std::string &path) {
330 textures_[TextureType::AMBIENT_OCCLUSION] = TextureCache::load(path, TextureType::AMBIENT_OCCLUSION);
331 return *this;
332 }
333
334 MaterialTextureSet &MaterialTextureSet::emissive(const std::string &path) {
335 textures_[TextureType::EMISSIVE] = TextureCache::load(path, TextureType::EMISSIVE);
336 return *this;
337 }
338
339 std::shared_ptr<Texture> MaterialTextureSet::get(TextureType type) const {
340 auto it = textures_.find(type);
341 return (it != textures_.end()) ? it->second : nullptr;
342 }
343
345 auto it = textures_.find(type);
346 return (it != textures_.end()) && (it->second != nullptr) && (it->second->is_valid());
347 }
348
350 int texture_unit = 0;
351 for (const auto &texture: textures_ | std::views::values) {
352 if (texture && texture->is_valid()) {
353 texture->bind(texture_unit);
354 texture_unit++;
355 }
356 }
357 }
358
360 for (const auto &[type, texture]: textures_) {
361 if (texture && texture->is_valid()) {
362 std::string uniform_name = Texture::get_uniform_name(type);
363 material.set_property(uniform_name, texture.get(), uniform_name);
364 }
365 }
366 }
367
368 bool file_exists(const std::string &filename) {
369 return std::filesystem::exists(filename);
370 }
371
372 MaterialTextureSet MaterialTextureSet::from_directory(const std::string &base_path,
373 const std::string &material_name) {
374 MaterialTextureSet texture_set;
375
376 // Common naming conventions for different texture types
377 std::vector<std::pair<TextureType, std::vector<std::string> > > naming_patterns = {
378 {
379 TextureType::DIFFUSE, {
380 material_name + "_diffuse.jpg", material_name + "_diffuse.png",
381 material_name + "_albedo.jpg", material_name + "_albedo.png",
382 material_name + "_color.jpg", material_name + "_color.png",
383 material_name + "_basecolor.jpg", material_name + "_basecolor.png"
384 }
385 },
386 {
387 TextureType::NORMAL, {
388 material_name + "_normal.jpg", material_name + "_normal.png",
389 material_name + "_nrm.jpg", material_name + "_nrm.png",
390 material_name + "_norm.jpg", material_name + "_norm.png",
391 material_name + "_normalmap.jpg", material_name + "_normalmap.png"
392 }
393 },
394 {
395 TextureType::SPECULAR, {
396 material_name + "_specular.jpg", material_name + "_specular.png",
397 material_name + "_spec.jpg", material_name + "_spec.png",
398 material_name + "_gloss.jpg", material_name + "_gloss.png"
399 }
400 },
401 {
402 TextureType::ROUGHNESS, {
403 material_name + "_roughness.jpg", material_name + "_roughness.png",
404 material_name + "_rough.jpg", material_name + "_rough.png"
405 }
406 },
407 {
408 TextureType::METALNESS, {
409 material_name + "_metalness.jpg", material_name + "_metalness.png",
410 material_name + "_metal.jpg", material_name + "_metal.png",
411 material_name + "_metallic.jpg", material_name + "_metallic.png"
412 }
413 },
414 {
415 TextureType::AMBIENT_OCCLUSION, {
416 material_name + "_ao.jpg", material_name + "_ao.png",
417 material_name + "_occlusion.jpg", material_name + "_occlusion.png",
418 material_name + "_ambient.jpg", material_name + "_ambient.png"
419 }
420 },
421 {
422 TextureType::EMISSIVE, {
423 material_name + "_emissive.jpg", material_name + "_emissive.png",
424 material_name + "_emission.jpg", material_name + "_emission.png",
425 material_name + "_glow.jpg", material_name + "_glow.png"
426 }
427 },
428 {
429 TextureType::HEIGHT, {
430 material_name + "_height.jpg", material_name + "_height.png",
431 material_name + "_displacement.jpg", material_name + "_displacement.png",
432 material_name + "_disp.jpg", material_name + "_disp.png"
433 }
434 },
435 {
436 TextureType::OPACITY, {
437 material_name + "_opacity.jpg", material_name + "_opacity.png",
438 material_name + "_alpha.jpg", material_name + "_alpha.png",
439 material_name + "_mask.jpg", material_name + "_mask.png"
440 }
441 }
442 };
443
444 // Try to find textures based on naming patterns
445 for (const auto &[type, patterns]: naming_patterns) {
446 for (const auto &pattern: patterns) {
447 std::string full_path = base_path;
448
449 // Ensure proper path separator
450 if (!base_path.empty() && base_path.back() != '/' && base_path.back() != '\\') {
451 full_path += "/";
452 }
453 full_path += pattern;
454
455 if (file_exists(full_path)) {
456 // Found one, move to next type
457 texture_set.texture(type, full_path);
458 break;
459 }
460 }
461 }
462
463 return texture_set;
464 }
465}
void apply_to_material(Material &material) const
Definition Texture.cpp:359
bool has(TextureType type) const
Definition Texture.cpp:344
std::shared_ptr< Texture > get(TextureType type) const
Definition Texture.cpp:339
static std::unordered_map< std::string, std::weak_ptr< Texture > > cache_
Definition Texture.h:159
static void clear_cache()
Definition Texture.cpp:284
static size_t get_cache_size()
Definition Texture.cpp:288
void load_texture_data()
Definition Texture.cpp:83
static std::string type_to_string(TextureType type)
Definition Texture.cpp:211
bool is_valid() const
Definition Texture.cpp:198
Texture(Texture &&other) noexcept
Definition Texture.cpp:49
TextureSettings settings_
Definition Texture.h:137
std::string path_
Definition Texture.h:135
static std::string get_uniform_name(TextureType type)
Definition Texture.cpp:226
GLint get_gl_filter_mode(TextureFilter filter) const
Definition Texture.cpp:251
void unbind() const
Definition Texture.cpp:207
void bind(unsigned int slot=0) const
Definition Texture.cpp:202
TextureType type_
Definition Texture.h:134
Texture & operator=(Texture &&other) noexcept
Definition Texture.cpp:56
uint32_t texture_id_
Definition Texture.h:136
TextureWrap
Definition Texture.h:32
bool file_exists(const std::string &filename)
Definition Texture.cpp:368
TextureType
Definition Texture.h:13
TextureFilter
Definition Texture.h:25
TextureFilter min_filter
Definition Texture.h:40
static TextureSettings for_type(TextureType type)
Definition Texture.cpp:16