Unravel Engine C++ Reference
Loading...
Searching...
No Matches
mcp_material_utils.cpp
Go to the documentation of this file.
2
8#include <graphics/texture.h>
9#include <logging/logging.h>
10#include <math/color.h>
11#include <uuid/uuid.h>
12
13namespace unravel::mcp
14{
15namespace
16{
17
18auto cull_type_to_string(cull_type value) -> const char*
19{
20 switch(value)
21 {
22 case cull_type::none:
23 return "none";
25 return "clockwise";
27 return "counter_clockwise";
28 }
29 return "counter_clockwise";
30}
31
32auto cull_type_from_string(std::string_view value, cull_type& out) -> bool
33{
34 if(value == "none")
35 {
37 return true;
38 }
39 if(value == "clockwise")
40 {
42 return true;
43 }
44 if(value == "counter_clockwise" || value == "counter-clockwise" || value == "ccw")
45 {
47 return true;
48 }
49 return false;
50}
51
52auto alpha_mode_to_string(alpha_mode value) -> const char*
53{
54 switch(value)
55 {
57 return "opaque";
59 return "mask";
61 return "blend";
62 }
63 return "opaque";
64}
65
66auto alpha_mode_from_string(std::string_view value, alpha_mode& out) -> bool
67{
68 if(value == "opaque")
69 {
71 return true;
72 }
73 if(value == "mask")
74 {
76 return true;
77 }
78 if(value == "blend")
79 {
81 return true;
82 }
83 return false;
84}
85
86auto color_to_json(const math::color& c) -> std::string
87{
88 const math::vec4 v = c;
89 return fmt::format("[{:.6g},{:.6g},{:.6g},{:.6g}]", v.x, v.y, v.z, v.w);
90}
91
92auto vec2_to_json(const math::vec2& v) -> std::string
93{
94 return fmt::format("[{:.6g},{:.6g}]", v.x, v.y);
95}
96
97auto texture_key_json(const asset_handle<gfx::texture>& tex) -> std::string
98{
99 if(!tex)
100 {
101 return "null";
102 }
103 return make_json_string(tex.id());
104}
105
106auto read_color(const simdjson::dom::element& el, math::color& out) -> bool
107{
108 simdjson::dom::array arr;
109 if(el.get(arr))
110 {
111 return false;
112 }
113 std::vector<double> values;
114 for(auto item : arr)
115 {
116 double v = 0.0;
117 if(item.get(v))
118 {
119 return false;
120 }
121 values.push_back(v);
122 }
123 if(values.size() == 3)
124 {
125 out = math::color(static_cast<float>(values[0]),
126 static_cast<float>(values[1]),
127 static_cast<float>(values[2]),
128 1.0f);
129 return true;
130 }
131 if(values.size() == 4)
132 {
133 out = math::color(static_cast<float>(values[0]),
134 static_cast<float>(values[1]),
135 static_cast<float>(values[2]),
136 static_cast<float>(values[3]));
137 return true;
138 }
139 return false;
140}
141
142auto read_vec2(const simdjson::dom::element& el, math::vec2& out) -> bool
143{
144 simdjson::dom::array arr;
145 if(el.get(arr))
146 {
147 return false;
148 }
149 std::vector<double> values;
150 for(auto item : arr)
151 {
152 double v = 0.0;
153 if(item.get(v))
154 {
155 return false;
156 }
157 values.push_back(v);
158 }
159 if(values.size() != 2)
160 {
161 return false;
162 }
163 out = math::vec2{static_cast<float>(values[0]), static_cast<float>(values[1])};
164 return true;
165}
166
167auto resolve_texture(rtti::context& ctx, const simdjson::dom::element& el, asset_handle<gfx::texture>& out, std::string& error)
168 -> bool
169{
170 if(el.is_null())
171 {
172 out = {};
173 return true;
174 }
175
176 std::string_view key_view;
177 if(el.get(key_view))
178 {
179 error = "Texture map value must be a string asset key or null";
180 return false;
181 }
182 if(key_view.empty())
183 {
184 out = {};
185 return true;
186 }
187
188 auto& am = ctx.get_cached<asset_manager>();
189 out = am.get_asset<gfx::texture>(std::string(key_view));
190 if(!out)
191 {
192 error = "Texture not found: " + std::string(key_view);
193 return false;
194 }
195 return true;
196}
197
198} // namespace
199
201{
202 return R"([
203{"name":"cull_type","type":"string","enum":["none","clockwise","counter_clockwise"]},
204{"name":"base_color","type":"array","items":"number","minItems":3,"maxItems":4},
205{"name":"subsurface_color","type":"array","items":"number","minItems":3,"maxItems":4},
206{"name":"emissive_color","type":"array","items":"number","minItems":3,"maxItems":4},
207{"name":"emissive_intensity","type":"number"},
208{"name":"roughness","type":"number"},
209{"name":"metalness","type":"number"},
210{"name":"bumpiness","type":"number"},
211{"name":"alpha_mode","type":"string","enum":["opaque","mask","blend"]},
212{"name":"alpha_cutoff","type":"number"},
213{"name":"tiling","type":"array","items":"number","minItems":2,"maxItems":2},
214{"name":"dither_threshold","type":"array","items":"number","minItems":2,"maxItems":2},
215{"name":"color_map","type":"string","description":"Texture asset key or null"},
216{"name":"normal_map","type":"string"},
217{"name":"roughness_map","type":"string"},
218{"name":"metalness_map","type":"string"},
219{"name":"emissive_map","type":"string"},
220{"name":"ao_map","type":"string"}
221])";
222}
223
224auto normalize_material_key(const std::string& path_or_key) -> std::string
225{
226 auto key = path_or_key;
227 if(key.empty())
228 {
229 return key;
230 }
231 // Allow absolute filesystem paths by converting to protocol when possible.
232 fs::error_code ec;
233 const fs::path as_path(key);
234 if(as_path.is_absolute() && fs::exists(as_path, ec))
235 {
236 key = fs::convert_to_protocol(as_path).generic_string();
237 }
238 if(key.size() < 4 || key.substr(key.size() - 4) != ".mat")
239 {
240 key += ".mat";
241 }
242 return key;
243}
244
245auto resolve_material_asset(rtti::context& ctx, const std::string& key, const std::string& uid, std::string& error)
247{
248 auto& am = ctx.get_cached<asset_manager>();
249 if(!key.empty())
250 {
251 auto handle = am.get_asset<material>(key);
252 if(!handle)
253 {
254 error = "Material not found: " + key;
255 return {};
256 }
257 return handle;
258 }
259 if(!uid.empty())
260 {
261 auto uuid = hpp::uuid::from_string(uid);
262 if(!uuid)
263 {
264 error = "Invalid material uid";
265 return {};
266 }
267 auto handle = am.get_asset<material>(*uuid);
268 if(!handle)
269 {
270 error = "Material not found for uid: " + uid;
271 return {};
272 }
273 return handle;
274 }
275 error = "Provide key or uid";
276 return {};
277}
278
279auto as_pbr_material(const material::sptr& mat, std::string& error) -> std::shared_ptr<pbr_material>
280{
281 if(!mat)
282 {
283 error = "Null material";
284 return {};
285 }
286 auto pbr = std::dynamic_pointer_cast<pbr_material>(mat);
287 if(!pbr)
288 {
289 error = "Material is not a pbr_material";
290 return {};
291 }
292 return pbr;
293}
294
295auto material_to_json(const material& mat) -> std::string
296{
297 std::string json = "{";
298 json += fmt::format(R"("cull_type":{},)", make_json_string(std::string(cull_type_to_string(mat.get_cull_type()))));
299
300 auto pbr = dynamic_cast<const pbr_material*>(&mat);
301 if(!pbr)
302 {
303 json += R"("type":"material"})";
304 return json;
305 }
306
307 json += R"("type":"pbr_material",)";
308 json += fmt::format(R"("base_color":{},)", color_to_json(pbr->get_base_color()));
309 json += fmt::format(R"("subsurface_color":{},)", color_to_json(pbr->get_subsurface_color()));
310 json += fmt::format(R"("emissive_color":{},)", color_to_json(pbr->get_emissive_color()));
311 json += fmt::format(R"("emissive_intensity":{:.6g},)", pbr->get_emissive_intensity());
312 json += fmt::format(R"("roughness":{:.6g},)", pbr->get_roughness());
313 json += fmt::format(R"("metalness":{:.6g},)", pbr->get_metalness());
314 json += fmt::format(R"("bumpiness":{:.6g},)", pbr->get_bumpiness());
315 json += fmt::format(R"("alpha_mode":{},)", make_json_string(std::string(alpha_mode_to_string(pbr->get_alpha_mode()))));
316 json += fmt::format(R"("alpha_cutoff":{:.6g},)", pbr->get_alpha_cutoff());
317 json += fmt::format(R"("tiling":{},)", vec2_to_json(pbr->get_tiling()));
318 json += fmt::format(R"("dither_threshold":{},)", vec2_to_json(pbr->get_dither_threshold()));
319 json += fmt::format(R"("color_map":{},)", texture_key_json(pbr->get_color_map()));
320 json += fmt::format(R"("normal_map":{},)", texture_key_json(pbr->get_normal_map()));
321 json += fmt::format(R"("roughness_map":{},)", texture_key_json(pbr->get_roughness_map()));
322 json += fmt::format(R"("metalness_map":{},)", texture_key_json(pbr->get_metalness_map()));
323 json += fmt::format(R"("emissive_map":{},)", texture_key_json(pbr->get_emissive_map()));
324 json += fmt::format(R"("ao_map":{})", texture_key_json(pbr->get_ao_map()));
325 json += "}";
326 return json;
327}
328
329auto apply_material_properties(rtti::context& ctx, material::sptr mat, const simdjson::dom::object& properties)
331{
333 std::string cast_error;
334 auto pbr = as_pbr_material(mat, cast_error);
335 if(!pbr)
336 {
337 result.ok = false;
338 result.errors.push_back(cast_error);
339 return result;
340 }
341
342 for(auto field : properties)
343 {
344 const std::string key(field.key);
345 const auto& value = field.value;
346 std::string error;
347
348 auto mark_applied = [&]()
349 {
350 result.applied.push_back(key);
351 };
352 auto mark_error = [&](const std::string& msg)
353 {
354 result.ok = false;
355 result.errors.push_back(key + ": " + msg);
356 };
357
358 if(key == "cull_type")
359 {
360 std::string_view s;
361 cull_type cull{};
362 if(value.get(s) || !cull_type_from_string(s, cull))
363 {
364 mark_error("expected none|clockwise|counter_clockwise");
365 continue;
366 }
367 pbr->set_cull_type(cull);
368 mark_applied();
369 continue;
370 }
371 if(key == "base_color")
372 {
373 math::color c;
374 if(!read_color(value, c))
375 {
376 mark_error("expected [r,g,b] or [r,g,b,a]");
377 continue;
378 }
379 pbr->set_base_color(c);
380 mark_applied();
381 continue;
382 }
383 if(key == "subsurface_color")
384 {
385 math::color c;
386 if(!read_color(value, c))
387 {
388 mark_error("expected [r,g,b] or [r,g,b,a]");
389 continue;
390 }
391 pbr->set_subsurface_color(c);
392 mark_applied();
393 continue;
394 }
395 if(key == "emissive_color")
396 {
397 math::color c;
398 if(!read_color(value, c))
399 {
400 mark_error("expected [r,g,b] or [r,g,b,a]");
401 continue;
402 }
403 pbr->set_emissive_color(c);
404 mark_applied();
405 continue;
406 }
407 if(key == "emissive_intensity")
408 {
409 double v = 0.0;
410 if(value.get(v))
411 {
412 mark_error("expected number");
413 continue;
414 }
415 pbr->set_emissive_intensity(static_cast<float>(v));
416 mark_applied();
417 continue;
418 }
419 if(key == "roughness")
420 {
421 double v = 0.0;
422 if(value.get(v))
423 {
424 mark_error("expected number");
425 continue;
426 }
427 pbr->set_roughness(static_cast<float>(v));
428 mark_applied();
429 continue;
430 }
431 if(key == "metalness")
432 {
433 double v = 0.0;
434 if(value.get(v))
435 {
436 mark_error("expected number");
437 continue;
438 }
439 pbr->set_metalness(static_cast<float>(v));
440 mark_applied();
441 continue;
442 }
443 if(key == "bumpiness")
444 {
445 double v = 0.0;
446 if(value.get(v))
447 {
448 mark_error("expected number");
449 continue;
450 }
451 pbr->set_bumpiness(static_cast<float>(v));
452 mark_applied();
453 continue;
454 }
455 if(key == "alpha_mode")
456 {
457 std::string_view s;
458 alpha_mode mode{};
459 if(value.get(s) || !alpha_mode_from_string(s, mode))
460 {
461 mark_error("expected opaque|mask|blend");
462 continue;
463 }
464 pbr->set_alpha_mode(mode);
465 mark_applied();
466 continue;
467 }
468 if(key == "alpha_cutoff")
469 {
470 double v = 0.0;
471 if(value.get(v))
472 {
473 mark_error("expected number");
474 continue;
475 }
476 pbr->set_alpha_cutoff(static_cast<float>(v));
477 mark_applied();
478 continue;
479 }
480 if(key == "tiling")
481 {
482 math::vec2 v{};
483 if(!read_vec2(value, v))
484 {
485 mark_error("expected [x,y]");
486 continue;
487 }
488 pbr->set_tiling(v);
489 mark_applied();
490 continue;
491 }
492 if(key == "dither_threshold")
493 {
494 math::vec2 v{};
495 if(!read_vec2(value, v))
496 {
497 mark_error("expected [x,y]");
498 continue;
499 }
500 pbr->set_dither_threshold(v);
501 mark_applied();
502 continue;
503 }
504
505 auto apply_map = [&](auto setter)
506 {
508 if(!resolve_texture(ctx, value, tex, error))
509 {
510 mark_error(error);
511 return;
512 }
513 (pbr.get()->*setter)(tex);
514 mark_applied();
515 };
516
517 if(key == "color_map")
518 {
519 apply_map(&pbr_material::set_color_map);
520 continue;
521 }
522 if(key == "normal_map")
523 {
525 continue;
526 }
527 if(key == "roughness_map")
528 {
530 continue;
531 }
532 if(key == "metalness_map")
533 {
535 continue;
536 }
537 if(key == "emissive_map")
538 {
540 continue;
541 }
542 if(key == "ao_map")
543 {
544 apply_map(&pbr_material::set_ao_map);
545 continue;
546 }
547
548 result.unknown.push_back(key);
549 }
550
551 return result;
552}
553
555{
556 if(!handle)
557 {
558 error = "Invalid material handle";
559 return false;
560 }
561
563 {
564 error = "Failed to save material: " + handle.id();
565 return false;
566 }
567
568 if(ctx.has<thumbnail_manager>())
569 {
570 ctx.get_cached<thumbnail_manager>().regenerate_thumbnail(handle.uid());
571 }
572 return true;
573}
574
575} // namespace unravel::mcp
Manages assets, including loading, unloading, and storage.
Base class for materials used in rendering.
Definition material.h:44
std::shared_ptr< material > sptr
Definition material.h:48
Class for physically-based rendering (PBR) materials.
Definition material.h:112
void set_roughness_map(const asset_handle< gfx::texture > &val)
Sets the roughness map of the material.
Definition material.h:390
void set_color_map(const asset_handle< gfx::texture > &val)
Sets the color map of the material.
Definition material.h:354
void set_ao_map(const asset_handle< gfx::texture > &val)
Sets the ambient occlusion map of the material.
Definition material.h:426
void set_emissive_map(const asset_handle< gfx::texture > &val)
Sets the emissive map of the material.
Definition material.h:444
void set_normal_map(const asset_handle< gfx::texture > &val)
Sets the normal map of the material.
Definition material.h:372
void set_metalness_map(const asset_handle< gfx::texture > &val)
Sets the metalness map of the material.
Definition material.h:408
std::string error
Definition mcp_async.cpp:33
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
auto atomic_save_to_file(const fs::path &key, const asset_handle< T > &obj) -> bool
auto list_material_property_schema_json() -> std::string
auto resolve_material_asset(rtti::context &ctx, const std::string &key, const std::string &uid, std::string &error) -> asset_handle< material >
auto material_to_json(const material &mat) -> std::string
auto make_json_string(const std::string &value) -> std::string
auto as_pbr_material(const material::sptr &mat, std::string &error) -> std::shared_ptr< pbr_material >
auto apply_material_properties(rtti::context &ctx, material::sptr mat, const simdjson::dom::object &properties) -> material_apply_result
auto save_material_asset(rtti::context &ctx, const asset_handle< material > &handle, std::string &error) -> bool
auto normalize_material_key(const std::string &path_or_key) -> std::string
cull_type
Enum representing the type of culling to be used.
Definition material.h:25
@ none
No culling.
@ counter_clockwise
Cull counter-clockwise faces.
@ clockwise
Cull clockwise faces.
alpha_mode
glTF-aligned surface alpha behavior (default: opaque).
Definition material.h:33
@ opaque
No alpha clip; casts solid shadows.
@ blend
Transparent (lit pass); does not cast shadows.
@ mask
Hard alpha cutoff; casts cutout shadows.
Thread-safe handle to an asset.
std::vector< std::string > unknown
std::vector< std::string > errors
std::vector< std::string > applied
gfx::uniform_handle handle
Definition uniform.cpp:9