GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 419, 0 excluded
0.0%
Functions:
0 of 52, 0 excluded
0.0%
Branches:
0 of 740, 0 excluded
0.0%

apps/runner/src/eu/runner/main.cc
Line Branch Exec Source
1 #include <utility>
2
3 // todo(Gustav): look into glad support
4 #define SDL_FUNCTION_POINTER_IS_VOID_POINTER
5
6 #include "SDL.h"
7
8 // #include <string>
9
10 #include "eu/log/log.h"
11 #include "eu/base/memorychunk.h"
12
13 #include "eu/core/geom.builder.h"
14 #include "eu/core/geom.h"
15
16 #include "eu/io/file.h"
17 #include "eu/io/mesh.h"
18
19 #include "eu/render/canvas.h"
20 #include "eu/render/state.h"
21 #include "eu/render/font.h"
22 #include "eu/render/opengl_utils.h"
23 #include "eu/render/texture.io.h"
24 #include "eu/render/camera.h"
25 #include "eu/render/compiledmesh.h"
26 #include "eu/render/enable_high_performance_graphics.h"
27 #include "eu/render/postproc.h"
28 #include "eu/render/renderer.h"
29 #include "eu/render/world.h"
30
31 #include "eu/runner/script.h"
32 #include "eu/runner/input.h"
33 #include "eu/runner/entity.h"
34
35 #include "eu/kdl/kdl.h"
36
37
38 #if FF_HAS(EU_DEBUG_RUNNER)
39 #include "eu/imgui/ui.h"
40
41 #include "dear_imgui/imgui.h"
42 #include "dear_imgui/backends/imgui_impl_sdl3.h"
43 #include "dear_imgui/backends/imgui_impl_opengl3.h"
44
45 static void imgui_color(const char* const label, eu::Rgb* color)
46 {
47 ImGui::ColorEdit3(label, &color->r);
48 }
49 #endif
50
51
52 ENABLE_HIGH_PERFORMANCE_GRAPHICS
53
54 using namespace eu;
55 using namespace std::string_view_literals;
56
57 // todo(Gustav): move to io
58 eu::render::ShaderSource load_shader(const std::string& name)
59 {
60 return {
61 .vertex = eu::io::string_from_file(name + ".vert.glsl"),
62 .fragment = eu::io::string_from_file(name + ".frag.glsl")
63 };
64 }
65
66 eu::Size size_from_v2(const eu::v2& v)
67 {
68 return eu::Size{ .width = eu::int_from_float(v.x), .height = eu::int_from_float(v.y) };
69 }
70
71 std::shared_ptr<eu::render::Texture2d> load_texture(const std::string& path, eu::render::ColorData cd,
72 eu::render::TextureEdge texture_edge = eu::render::TextureEdge::repeat,
73 eu::render::Transparency transparency = eu::render::Transparency::exclude)
74 {
75 const auto bin = eu::io::bytes_from_file(path);
76 return std::make_shared<eu::render::Texture2d>(
77 load_image_from_embedded(SEND_DEBUG_LABEL_MANY(path) embedded_binary { reinterpret_cast<const unsigned int*>(bin.data()), static_cast<unsigned int>(bin.size()) }, texture_edge, eu::render::TextureRenderStyle::mipmap, transparency, cd)
78 );
79 }
80
81 std::shared_ptr<eu::render::Texture2d> create_texture(DEBUG_LABEL_ARG_MANY eu::core::SingleColor pixel_color, eu::render::ColorData cd)
82 {
83 return std::make_shared<eu::render::Texture2d>(
84 load_image_from_color(SEND_DEBUG_LABEL_MANY(debug_label) pixel_color, eu::render::TextureEdge::repeat, eu::render::TextureRenderStyle::pixel, eu::render::Transparency::exclude, cd)
85 );
86 }
87
88 struct Level
89 {
90 std::unordered_map<std::string, std::unique_ptr<eu::render::CompiledMesh>> meshes;
91 std::vector<std::unique_ptr<render::MeshInWorld>> instances;
92
93 void load(const std::string& path, render::World* world, std::shared_ptr<render::DefaultMaterial> material, const core::CompiledGeomVertexAttributes& layout)
94 {
95 // todo(Gustav): this is very hacky but just something so we can play a level
96 v3 offset = { 0.0f, 0.0f, 0.0f };
97 v3 size = { 1.0f, 1.0f, 1.0f };
98
99 const auto doc = kdl::parse(eu::io::string_from_file(path));
100 for (const auto& node: doc)
101 {
102 if (node.name() == "mesh")
103 {
104 const auto key = node.args()[0].as_string();
105 const auto file = node.args()[1].as_string();
106
107 const auto mesh = eu::io::mesh_from_file(file);
108 auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(file) mesh, layout);
109
110 meshes[key] = std::make_unique<eu::render::CompiledMesh>(compiled);
111 }
112 else if (node.name() == "offset")
113 {
114 const auto x = node.args()[0].as_number().as<float>();
115 const auto y = node.args()[1].as_number().as<float>();
116 const auto z = node.args()[2].as_number().as<float>();
117 offset = { x, y, z };
118 }
119 else if (node.name() == "cell_size")
120 {
121 const auto x = node.args()[0].as_number().as<float>();
122 const auto y = node.args()[1].as_number().as<float>();
123 const auto z = node.args()[2].as_number().as<float>();
124 size = {x, y, z};
125 }
126 else if (node.name() == "item")
127 {
128 const auto x = node.args()[0].as_number().as<float>();
129 const auto y = node.args()[1].as_number().as<float>();
130 const auto z = node.args()[2].as_number().as<float>();
131 const v3 pos = { x*size.x, y*size.y, z*size.z };
132 const auto item = node.properties().at("item").as_string();
133 const auto found = meshes.find(item);
134 if (found != meshes.end())
135 {
136 render::MeshInWorld car;
137 car.add_to_world(found->second.get(), world, material);
138 car.set_transform(eu::render::transform_from_rotation(offset + pos, Ypr{0_deg, 0_deg, 0_deg}));
139 }
140 else
141 {
142 LOG_WARN("Mesh '{}' not found for item in level file", item);
143 }
144 }
145 else
146 {
147 LOG_WARN("Unknown node in level file: '{}'", node.name());
148 }
149 }
150 }
151 };
152
153
154 struct Time
155 {
156 Uint64 now = SDL_GetPerformanceCounter();
157 Uint64 last = 0;
158
159
160 float calculate()
161 {
162 last = now;
163 now = SDL_GetPerformanceCounter();
164
165 const auto diff = static_cast<float>(now - last);
166 const auto pfc = static_cast<float>(SDL_GetPerformanceFrequency());
167 return diff / pfc;
168 }
169 };
170
171
172 struct MeshSpatialComponent : runner::SpatialComponent
173 {
174 render::MeshInWorld car;
175
176 eu::v3 position = { 0.0f, -1.8f, -10.0f };
177 eu::Ypr rotation = { .yaw = 30.0_deg, .pitch = 30.0_deg, .roll = 30.0_deg };
178
179 const char* display() override
180 {
181 return "MeshSpatialComponent";
182 }
183
184 void imgui() override
185 {
186 runner::SpatialComponent::imgui();
187 imgui::drag("Position", &position);
188 imgui::drag("Rotation", &rotation);
189 }
190
191 void update_transform()
192 {
193 set_transform(eu::render::transform_from_rotation(position, rotation));
194 car.set_transform(get_transform());
195 }
196
197 MeshSpatialComponent(render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material)
198 {
199 car.add_to_world(mesh, render_world, std::move(material));
200 update_transform();
201 }
202
203 EU_DEC_COMPONENT_TYPE();
204 };
205
206 EU_IMP_COMPONENT_TYPE(MeshSpatialComponent, runner::Component)
207
208 void boolean(const char* label, bool b)
209 {
210 ImGui::TextUnformatted(label);
211 ImGui::SameLine();
212 ImGui::TextUnformatted(b ? "TRUE" : "false");
213 }
214
215 struct InputSystem : runner::EntitySystem
216 {
217 runner::Input* input;
218 MeshSpatialComponent* mesh = nullptr;
219
220 const char* display() override
221 {
222 return "Input";
223 }
224
225 void imgui() override
226 {
227 boolean("has mesh", mesh != nullptr);
228 }
229
230 explicit InputSystem(runner::Input* in) : input(in)
231 {
232 }
233
234 runner::UpdateStageAndPrio get_stage() override
235 {
236 return
237 {
238 .stage = runner::UpdateStage::before_physics,
239 .prio = 100
240 };
241 }
242
243 void on_root_changed(runner::SpatialComponent*) override
244 {
245 }
246
247 void add_component(runner::Component* component) override
248 {
249 if (MeshSpatialComponent* mc = runner::component_cast<MeshSpatialComponent>(component); mc)
250 {
251 mesh = mc;
252 }
253 }
254
255 void update(float dt) override
256 {
257 if (mesh && input && input->get_current_frame().binds[0].current > 0.5f)
258 {
259 mesh->position += kk::in * dt;
260 }
261
262 if (mesh)
263 {
264 mesh->update_transform();
265 }
266 }
267 };
268
269 struct TargetComponent : runner::Component
270 {
271 m4 target = m4_identity;
272
273 const char* display() override
274 {
275 return "target";
276 }
277
278 void imgui() override
279 {
280 // todo(Gustav): display matrix
281 }
282
283 EU_DEC_COMPONENT_TYPE();
284 };
285 EU_IMP_COMPONENT_TYPE(TargetComponent, runner::Component)
286
287 struct CameraSpatialComponent : runner::SpatialComponent
288 {
289 const char* display() override
290 {
291 return "camera spatial";
292 }
293
294 void imgui() override
295 {
296 runner::SpatialComponent::imgui();
297 }
298 // todo(Gustav): add extra camera settings...
299 EU_DEC_COMPONENT_TYPE();
300 };
301 EU_IMP_COMPONENT_TYPE(CameraSpatialComponent, runner::SpatialComponent);
302
303 struct FollowCameraSystem : runner::EntitySystem
304 {
305 runner::UpdateStageAndPrio get_stage() override
306 {
307 return
308 {
309 .stage = runner::UpdateStage::after_physics,
310 .prio = 100
311 };
312 }
313
314 TargetComponent* target = nullptr;
315 CameraSpatialComponent* camera = nullptr;
316
317 const char* display() override
318 {
319 return "Follow camera";
320 }
321
322 v3 offset = kk::out * 3;
323 void imgui() override
324 {
325 boolean("Target", target != nullptr);
326 boolean("camera", camera != nullptr);
327 imgui::drag("offset", &offset);
328 imgui::gear("gear", &offset);
329 }
330
331 void on_root_changed(runner::SpatialComponent*) override
332 {
333 }
334
335 void add_component(runner::Component* component) override
336 {
337 if (auto* tar = runner::component_cast<TargetComponent>(component))
338 {
339 target = tar;
340 LOG_INFO("Follow camera found target");
341 }
342 else if (auto* cam = runner::component_cast<CameraSpatialComponent>(component))
343 {
344 camera = cam;
345 LOG_INFO("Follow camera found camera");
346 }
347 }
348 void update(float) override
349 {
350 if (!target) { return; }
351 if (!camera) { return; }
352
353 const auto focus = target->target.get_translation();
354 const auto pos_from_focus =
355 target->target.get_transformed_vec(kk::right) * offset.x +
356 target->target.get_transformed_vec(kk::up) * offset.y +
357 target->target.get_transformed_vec(kk::in) * offset.z ;
358 const auto pos = focus + pos_from_focus;
359 const auto rot = Q::look_at(pos, focus, kk::up);
360 if (!rot)
361 {
362 return;
363 }
364
365 const auto rot_mat = m4::from(*rot);
366 if (!rot_mat)
367 {
368 return;
369 }
370
371 const auto mat = *rot_mat * m4::from_translation(pos);
372
373 // const auto nt = target->target.get_translated(offset);
374 camera->set_transform(mat);
375 }
376 };
377
378 namespace tag
379 {
380 constexpr Hsh CameraSpatialSource = "camera_spatial_src_tag"sv;
381 constexpr Hsh CameraDestination = "camera_dst_tag"sv;
382 }
383
384 struct UpdateCameraTarget : runner::WorldSystem
385 {
386 runner::SpatialComponent* src_entity = nullptr;
387 TargetComponent* dst_entity = nullptr;
388
389 const char* display() override
390 {
391 return "Update camera target";
392 }
393
394 void imgui() override
395 {
396 boolean("src entity", src_entity != nullptr);
397 boolean("dst entity", dst_entity != nullptr);
398 }
399
400 runner::UpdateStageAndPrio get_stage() override
401 {
402 return {
403 .stage = runner::UpdateStage::after_physics,
404 .prio = 100
405 };
406 }
407
408 void on_root_changed(runner::Entity* entity, runner::SpatialComponent* component) override
409 {
410 if (entity->has_tag(tag::CameraSpatialSource))
411 {
412 LOG_INFO("Found spatial source");
413 src_entity = component;
414 }
415 }
416
417 void add_component(runner::Entity* entity, runner::Component* component) override
418 {
419 if (entity->has_tag(tag::CameraDestination))
420 {
421 if (auto* target = runner::component_cast<TargetComponent>(component))
422 {
423 LOG_INFO("Found camera destination on {}", entity->name);
424 dst_entity = target;
425 }
426 }
427 }
428
429 void update(float) override
430 {
431 if (!src_entity) { return; }
432 if (!dst_entity) { return; }
433
434 dst_entity->target = src_entity->get_transform();
435 }
436 };
437
438 struct CameraFetcherSystem : runner::WorldSystem
439 {
440 runner::UpdateStageAndPrio get_stage() override
441 {
442 return {
443 .stage = runner::UpdateStage::end_frame,
444 .prio = 100
445 };
446 }
447
448 CameraSpatialComponent* camera = nullptr;
449 m4 transform = m4_identity;
450
451 const char* display() override
452 {
453 return "Camera fetcher";
454 }
455
456 void imgui() override
457 {
458 boolean("camera", camera != nullptr);
459 //todo(Gustav): display transform
460 }
461
462 void add_component(runner::Entity*, runner::Component* component) override
463 {
464 if (auto cam = runner::component_cast<CameraSpatialComponent>(component))
465 {
466 LOG_INFO("Fetcher found camera");
467 camera = cam;
468 }
469 }
470
471 void on_root_changed(runner::Entity*, runner::SpatialComponent*) override
472 {
473 }
474
475 void update(float) override
476 {
477 if (!camera) return;
478 transform = camera->get_transform();
479 }
480
481 render::Camera fetch() const
482 {
483 render::Camera r;
484 r.position = transform.get_translation();
485 return r;
486 }
487 };
488
489 int main(int, char**)
490 {
491 int window_width = 1280;
492 int window_height = 720;
493 const char* glsl_version = "#version 130";
494
495 // todo(Gustav): set app metadata
496
497 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == false)
498 {
499 LOG_ERR("Error initializing SDL: {}", SDL_GetError());
500 return -1;
501 }
502
503 {
504 const auto str = [](int version)
505 {
506 const auto major = SDL_VERSIONNUM_MAJOR(version);
507 const auto minor = SDL_VERSIONNUM_MINOR(version);
508 const auto micro = SDL_VERSIONNUM_MICRO(version);
509 return fmt::format("{0}.{1}.{2}", major, minor, micro);
510 };
511 const int version_loaded = SDL_GetVersion();
512 LOG_INFO("SDL {0} initialized with {1}", str(SDL_VERSION), str(version_loaded));
513 }
514
515 #if defined(__APPLE__)
516 // GL 3.2 Core + GLSL 150
517 const char* glsl_version = "#version 150";
518 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
519 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
520 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
521 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
522 #else
523 // GL 3.0 + GLSL 130
524 // const char* glsl_version = "#version 130";
525 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
526 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
527 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
528 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); // was 0 in dear imgui example??
529 #endif
530
531 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
532 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
533
534 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
535 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
536
537 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
538 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
539 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
540 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
541
542 SDL_Window* window = SDL_CreateWindow("Runner",
543 window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE);
544 if (!window) {
545 LOG_ERR("Error creating window: {}", SDL_GetError());
546 return -1;
547 }
548
549 auto* glContext = SDL_GL_CreateContext(window);
550 SDL_GetWindowSize(window, &window_width, &window_height);
551 if (!glContext) {
552 LOG_ERR("Error creating gl context: {}", SDL_GetError());
553 return -1;
554 }
555
556 /* OpenGL setup */
557 const int glad_result = gladLoadGLLoader(SDL_GL_GetProcAddress);
558 if (glad_result == 0)
559 {
560 LOG_ERR("Failed to init glad, error: {0}", glad_result);
561 return -1;
562 }
563
564 {
565 if (GLAD_GL_VERSION_1_0) {
566 const std::string gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
567 const std::string gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
568 const std::string gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
569 LOG_INFO("OpenGL Vendor: {0}", gl_vendor);
570 LOG_INFO("OpenGL Renderer: {0}", gl_renderer);
571 LOG_INFO("OpenGL Version: {0}", gl_version);
572 }
573
574 if (GLAD_GL_VERSION_2_0) {
575 const std::string gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
576 LOG_INFO("Version GLSL: {0}", gl_shading_language_version);
577 }
578 }
579
580 if (!GLAD_GL_VERSION_4_3)
581 {
582 LOG_ERR("OpenGL 4.3 not supported ({0}.{1} detected), aborting...", GLVersion.major, GLVersion.minor);
583 return -1;
584 }
585
586 #if FF_HAS(EU_DEBUG_RUNNER)
587 IMGUI_CHECKVERSION();
588 ImGui::CreateContext();
589 ImGuiIO& io = ImGui::GetIO(); (void)io;
590 ImGui::StyleColorsDark();
591 if (false == ImGui_ImplSDL3_InitForOpenGL(window, glContext))
592 {
593 LOG_ERR("Failed to init ImGui SDL3 backend");
594 return -1;
595 }
596 if (false == ImGui_ImplOpenGL3_Init(glsl_version))
597 {
598 LOG_ERR("Failed to init ImGui OpenGL3 backend");
599 return -1;
600 }
601
602 bool show_demo_window = true;
603 #endif
604
605 eu::render::State states;
606 eu::render::Render2 render{ &states };
607
608 eu::render::Assets assets;
609
610 assets.black = create_texture(SEND_DEBUG_LABEL_MANY("black") eu::core::color_from_rgba(0, 0, 0, 255), eu::render::ColorData::dont_care);
611 assets.white = create_texture(SEND_DEBUG_LABEL_MANY("white") eu::core::color_from_rgba(255, 255, 255, 255), eu::render::ColorData::dont_care);
612
613 assets.default_shader_source = load_shader("default_shader");
614 assets.skybox_shader_source = load_shader("skybox");
615
616 assets.pp_vert_glsl = eu::io::string_from_file("pp_vert.glsl");
617 assets.pp_realize_frag_glsl = eu::io::string_from_file("pp_realize.frag.glsl");
618 assets.pp_extract_frag_glsl = eu::io::string_from_file("pp_extract.frag.glsl");
619 assets.pp_ping_pong_blur_frag_glsl = eu::io::string_from_file("pp_ping_pong_blur.frag.glsl");
620
621 eu::render::Renderer renderer{ &states, &assets, eu::render::RenderSettings{} };
622 if (renderer.is_loaded() == false)
623 {
624 return -1;
625 }
626
627 eu::runner::Input input;
628 if (false == input.load_from_file("input.kdl"))
629 {
630 return -1;
631 }
632
633 eu::runner::Script script;
634 if (false == script.run_file("main.lax"))
635 {
636 return -1;
637 }
638
639 eu::render::World render_world;
640 eu::render::EffectStack effects;
641
642 #if FF_HAS(EU_DEBUG_RUNNER)
643 imgui::ImguiShaderCache imgui_shader_cache;
644 #endif
645
646 // add demo world
647 {
648 constexpr auto PLANE_SIZE = 100.0f;
649 auto plane_geom = eu::render::compile_geom(
650 USE_DEBUG_LABEL_MANY("plane")
651 eu::core::geom::create_xz_plane(PLANE_SIZE, PLANE_SIZE, false).to_geom(),
652 renderer.default_geom_layout()
653 );
654
655 auto material = renderer.make_default_material();
656 material->diffuse = load_texture("sprites/smoke.png", eu::render::ColorData::dont_care);
657 material->specular = assets.white;
658
659 auto plane = make_mesh_instance(plane_geom, material);
660 render_world.meshes.emplace_back(plane);
661 plane->transform = eu::m4::from_translation({ 0.0f, -3.0f, 0.0f });
662
663 render_world.lights.point_lights.emplace_back();
664 }
665
666 // add demo mesh
667 eu::runner::World runner_world;
668 std::unordered_map<std::string, std::unique_ptr<render::CompiledMesh>> meshes;
669 std::unordered_map<std::string, std::shared_ptr<eu::render::Texture2d>> textures;
670
671 const auto load_mesh = [&](const std::string& f) -> render::CompiledMesh*
672 {
673 const auto found = meshes.find(f);
674 if (found != meshes.end())
675 {
676 return found->second.get();
677 }
678 const auto mesh = eu::io::mesh_from_file(f);
679 const auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(f) mesh, renderer.default_geom_layout());
680 auto data = std::make_unique<render::CompiledMesh>(compiled);
681 auto* ret = data.get();
682 meshes.insert_or_assign(f, std::move(data));
683 return ret;
684 };
685
686 const auto load_color_texture = [&](const std::string& f) -> std::shared_ptr<render::Texture2d>
687 {
688 const auto found = textures.find(f);
689 if (found != textures.end())
690 {
691 return found->second;
692 }
693 const auto loaded = load_texture(f, eu::render::ColorData::color_data);
694 textures.insert_or_assign(f, loaded);
695 return loaded;
696 };
697
698 runner_world.add_system(std::make_unique<UpdateCameraTarget>());
699
700 CameraFetcherSystem* camera_fetcher;
701 {
702 auto fetch = std::make_unique<CameraFetcherSystem>();
703 camera_fetcher = fetch.get();
704 runner_world.add_system(std::move(fetch));
705 }
706
707 {
708 auto* car = runner_world.add_entity("car", { tag::CameraSpatialSource });
709
710 // mesh component
711 // debug/axis.glb
712 // "models/vehicle-truck-purple.glb"
713 // "models/textures/colormap.png"
714 // render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material
715 auto* mesh = load_mesh("models/vehicle-truck-purple.glb");
716 auto material = renderer.make_default_material();
717 material->diffuse = load_color_texture("models/textures/colormap.png");
718 material->specular = assets.white;
719 auto comp = std::make_unique<MeshSpatialComponent>(mesh, &render_world, material);
720 car->set_root(comp.get());
721 car->add_component(std::move(comp));
722
723 // input component
724 car->add_system(std::make_unique<InputSystem>(&input));
725 }
726 {
727 auto* cam = runner_world.add_entity("camera", { tag::CameraDestination });
728
729 cam->add_component(std::make_unique<TargetComponent>());
730 auto spat = std::make_unique<CameraSpatialComponent>();
731 cam->set_root(spat.get());
732 cam->add_component(std::move(spat));
733 cam->add_system(std::make_unique<FollowCameraSystem>());
734 }
735 {
736 Level level;
737 auto material = renderer.make_default_material();
738 material->diffuse = load_color_texture("models/textures/colormap.png");
739 material->specular = assets.white;
740 level.load("level.kdl", &render_world, material, renderer.default_geom_layout());
741 }
742
743 LOG_INFO("Runner started");
744 bool running = true;
745 Time time;
746 while (running)
747 {
748 const auto dt = time.calculate();
749
750 runner_world.update(runner::UpdateStage::start_frame, dt);
751
752 SDL_Event e;
753 input.update();
754 while (SDL_PollEvent(&e) != 0)
755 {
756 #if FF_HAS(EU_DEBUG_RUNNER)
757 ImGui_ImplSDL3_ProcessEvent(&e);
758 #endif
759
760 switch (e.type)
761 {
762 case SDL_EVENT_KEY_DOWN:
763 case SDL_EVENT_KEY_UP:
764 input.on_key(e.key.key, e.type == SDL_EVENT_KEY_DOWN);
765 break;
766 case SDL_EVENT_WINDOW_RESIZED:
767 LOG_INFO("Resized");
768 SDL_GetWindowSize(window, &window_width, &window_height);
769 break;
770 case SDL_EVENT_QUIT: running = false; break;
771 default:
772 // ignore other events
773 break;
774 }
775 }
776
777 // todo(Gustav): lookup unity frame, when updates are called and update stages
778 runner_world.update(runner::UpdateStage::before_physics, dt);
779 runner_world.update(runner::UpdateStage::physics, dt);
780 runner_world.update(runner::UpdateStage::after_physics, dt);
781
782 #if FF_HAS(EU_DEBUG_RUNNER)
783 ImGui_ImplOpenGL3_NewFrame();
784 ImGui_ImplSDL3_NewFrame();
785 ImGui::NewFrame();
786
787 if (show_demo_window)
788 {
789 ImGui::ShowDemoWindow(&show_demo_window);
790 }
791
792 if (ImGui::Begin("Renderer"))
793 {
794 imgui_color("Clear color", &render_world.clear_color);
795 eu::imgui::simple_gamma_slider("Gamma/Brightness", &renderer.settings.gamma, -1.0f);
796 ImGui::Checkbox("Use bloom", &renderer.settings.use_bloom);
797 ImGui::DragFloat("Bloom cutoff", &renderer.settings.bloom_cutoff, 0.001f);
798 ImGui::SliderFloat("Softness", &renderer.settings.bloom_softness, 0.0f, 1.0f);
799 ImGui::DragInt("Bloom blur steps", &renderer.settings.bloom_blur_steps);
800 effects.gui(&imgui_shader_cache);
801 }
802 ImGui::End();
803
804 // todo(Gustav): add introspection ui
805 if (ImGui::Begin("Demo"))
806 {
807 runner_world.gui();
808 // imgui::drag("Position", &position);
809 // imgui::drag("Rotation", &rotation);
810 }
811 ImGui::End();
812 #endif
813
814 eu::render::RenderCommand cmd{ .states = &states, .render = &render, .size = {.width = window_width, .height = window_height} };
815
816 // clear
817 {
818 {
819 const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::extended,
820 .requested_width = 800, .requested_height = 600 };
821 cmd.clear(eu::colors::black, screen);
822 }
823 }
824
825 // render 3d world
826 {
827 const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
828 .requested_width = 800, .requested_height = 600 };
829 cmd.clear(eu::colors::blue_sky, screen);
830 auto layer = eu::render::with_layer3(cmd, screen);
831 // todo(Gustav): get transform from camera entity
832 effects.update(dt);
833 eu::render::Camera camera = camera_fetcher->fetch();
834 effects.render(eu::render::PostProcArg{
835 .world = &render_world,
836 .window_size = size_from_v2(layer.screen.get_size()),
837 .final_rect = layer.screen,
838 .camera = &camera,
839 .renderer = &renderer
840 });
841 }
842
843 // render hud
844 {
845 const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
846 .requested_width = 1280, .requested_height = 720 };
847
848 auto layer = eu::render::with_layer2(cmd, screen);
849 const float hud_size = 100.0f;
850 eu::render::Quad{ .tint = eu::colors::blue_sky }.draw(layer.batch, layer.viewport_aabb_in_worldspace.get_bottom(hud_size).get_right(hud_size).with_inset(eu::Lrtb{ 5.0f }));
851 }
852
853 #if FF_HAS(EU_DEBUG_RUNNER)
854 ImGui::Render();
855 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
856 #endif
857 SDL_GL_SwapWindow(window);
858
859 runner_world.update(runner::UpdateStage::end_frame, dt);
860 }
861
862 #if FF_HAS(EU_DEBUG_RUNNER)
863 ImGui_ImplOpenGL3_Shutdown();
864 ImGui_ImplSDL3_Shutdown();
865 ImGui::DestroyContext();
866 #endif
867
868 LOG_INFO("Shutting down");
869 SDL_GL_DestroyContext(glContext);
870 SDL_DestroyWindow(window);
871 SDL_Quit();
872
873 return 0;
874 }
875