//------------------------------------------------------------------------------ // imgui-glfw // Demonstrates basic integration with Dear Imgui (without custom // texture or custom font support). //------------------------------------------------------------------------------ #include "imgui.h" #define SOKOL_IMPL #define SOKOL_GLCORE33 #include "sokol_gfx.h" #include "sokol_time.h" #define SOKOL_GL_IMPL #include "util/sokol_gl.h" #define GLFW_INCLUDE_NONE #include #include "Camera.h" #include "SkinnedMesh.h" #include "GLFW/glfw3.h" const int Width = 1024; const int Height = 768; const int MaxVertices = (1 << 16); const int MaxIndices = MaxVertices * 3; uint64_t last_time = 0; bool show_imgui_demo_window = false; bool show_another_window = false; sg_pass_action pass_action; sg_pipeline pip; sg_bindings bind; typedef struct { ImVec2 disp_size; } vs_params_t; static void draw_imgui(ImDrawData*); #define HANDMADE_MATH_IMPLEMENTATION #define HANDMADE_MATH_NO_SSE #include "HandmadeMath.h" #include "camera.h" // ozz-animation headers #include // fmodf #include // std::unique_ptr, std::make_unique #include "AnimationController.h" #include "ozz/animation/runtime/animation.h" #include "ozz/animation/runtime/local_to_model_job.h" #include "ozz/animation/runtime/sampling_job.h" #include "ozz/animation/runtime/skeleton.h" #include "ozz/base/containers/vector.h" #include "ozz/base/io/archive.h" #include "ozz/base/io/stream.h" #include "ozz/base/log.h" #include "ozz/base/maths/soa_transform.h" #include "ozz/base/maths/vec_float.h" // wrapper struct for managed ozz-animation C++ objects, mUST BE DEleted // before shutdown, otherwise ozz-animation will report a memory leak typedef struct { ozz::animation::Skeleton skeleton; ozz::animation::Animation animation; ozz::animation::SamplingCache cache; ozz::vector local_matrices; ozz::vector model_matrices; } ozz_t; static struct { std::unique_ptr ozz; sg_pass_action pass_action; Camera camera; struct { bool skeleton; bool animation; bool failed; } loaded; struct { double frame; double absolute; uint64_t laptime; float factor; float anim_ratio; bool anim_ratio_ui_override; bool paused; } time; } state; typedef struct { int32_t mousedX; int32_t mousedY; int32_t mouseX; int32_t mouseY; uint8_t mouseButton; int32_t mouseScroll; char key; } GuiInputState; GuiInputState gGuiInputState = {0, 0, 0, 0, 0, 0, 0}; enum class ControlMode { ControlModeNone, ControlModeFPS }; ControlMode gControlMode = ControlMode::ControlModeNone; // io buffers for skeleton and animation data files, we know the max file size upfront static uint8_t skel_data_buffer[4 * 1024]; static uint8_t anim_data_buffer[32 * 1024]; static void draw_grid(void); static void draw_ui(void); // static void skeleton_data_loaded(const sfetch_response_t* response); // static void animation_data_loaded(const sfetch_response_t* response); static void frame(void); void handle_mouse(GLFWwindow* w, GuiInputState* io_input_state) { if (!glfwGetWindowAttrib(w, GLFW_FOCUSED)) { return; } double mouse_x, mouse_y; glfwGetCursorPos(w, &mouse_x, &mouse_y); if (io_input_state->mouseButton) { io_input_state->mousedX = int32_t(mouse_x) - io_input_state->mouseX; io_input_state->mousedY = int32_t(mouse_y) - io_input_state->mouseY; } else { io_input_state->mousedX = 0; io_input_state->mousedY = 0; } io_input_state->mouseX = int32_t(mouse_x); io_input_state->mouseY = int32_t(mouse_y); io_input_state->mouseButton = glfwGetMouseButton(w, 0) + (glfwGetMouseButton(w, 1) << 1) + (glfwGetMouseButton(w, 2) << 2); } int main() { // window and GL context via GLFW and flextGL glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); glfwWindowHint(GLFW_SAMPLES, 16); GLFWwindow* w = glfwCreateWindow(Width, Height, "AnimTestbed", 0, 0); glfwMakeContextCurrent(w); glfwSwapInterval(1); // GLFW to ImGui input forwarding glfwSetMouseButtonCallback( w, [](GLFWwindow*, int btn, int action, int /*mods*/) { if ((btn >= 0) && (btn < 3)) { ImGui::GetIO().MouseDown[btn] = (action == GLFW_PRESS); } }); glfwSetCursorPosCallback(w, [](GLFWwindow*, double pos_x, double pos_y) { ImGui::GetIO().MousePos.x = float(pos_x); ImGui::GetIO().MousePos.y = float(pos_y); }); glfwSetScrollCallback(w, [](GLFWwindow*, double /*pos_x*/, double pos_y) { ImGui::GetIO().MouseWheel = float(pos_y); }); glfwSetKeyCallback( w, [](GLFWwindow*, int key, int /*scancode*/, int action, int mods) { ImGuiIO& io = ImGui::GetIO(); if ((key >= 0) && (key < 512)) { io.KeysDown[key] = (action == GLFW_PRESS) || (action == GLFW_REPEAT); } io.KeyCtrl = (0 != (mods & GLFW_MOD_CONTROL)); io.KeyAlt = (0 != (mods & GLFW_MOD_ALT)); io.KeyShift = (0 != (mods & GLFW_MOD_SHIFT)); }); glfwSetCharCallback(w, [](GLFWwindow*, unsigned int codepoint) { ImGui::GetIO().AddInputCharacter((ImWchar)codepoint); }); // setup sokol_gfx and sokol_time stm_setup(); sg_desc desc = {}; sg_setup(&desc); assert(sg_isvalid()); // setup sokol-gl sgl_desc_t sgldesc = {}; sgldesc.sample_count = 0; sgl_setup(&sgldesc); printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator()); SkinnedMesh skinned_mesh; skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz"); skinned_mesh.LoadAnimation("../media/Idle-loop.ozz"); skinned_mesh.LoadAnimation("../media/Walking-loop.ozz"); skinned_mesh.LoadAnimation("../media/RunningSlow-loop.ozz"); skinned_mesh.LoadAnimation("../media/RunningFast-loop.ozz"); skinned_mesh.SetCurrentAnimation(0); AnimationController animation_controller (&skinned_mesh); // state.ozz = std::make_unique(); state.time.factor = 1.0f; Camera_Init(&state.camera); // setup Dear Imgui ImGui::CreateContext(); ImGui::StyleColorsDark(); ImGuiIO& io = ImGui::GetIO(); io.IniFilename = nullptr; io.Fonts->AddFontDefault(); io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; // dynamic vertex- and index-buffers for imgui-generated geometry sg_buffer_desc vbuf_desc = {}; vbuf_desc.usage = SG_USAGE_STREAM; vbuf_desc.size = MaxVertices * sizeof(ImDrawVert); bind.vertex_buffers[0] = sg_make_buffer(&vbuf_desc); sg_buffer_desc ibuf_desc = {}; ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; ibuf_desc.usage = SG_USAGE_STREAM; ibuf_desc.size = MaxIndices * sizeof(ImDrawIdx); bind.index_buffer = sg_make_buffer(&ibuf_desc); // font texture for imgui's default font unsigned char* font_pixels; int font_width, font_height; io.Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height); sg_image_desc img_desc = {}; img_desc.width = font_width; img_desc.height = font_height; img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; img_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE; img_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE; img_desc.data.subimage[0][0] = sg_range{font_pixels, size_t(font_width * font_height * 4)}; bind.fs_images[0] = sg_make_image(&img_desc); // shader object for imgui rendering sg_shader_desc shd_desc = {}; auto& ub = shd_desc.vs.uniform_blocks[0]; ub.size = sizeof(vs_params_t); ub.uniforms[0].name = "disp_size"; ub.uniforms[0].type = SG_UNIFORMTYPE_FLOAT2; shd_desc.vs.source = "#version 330\n" "uniform vec2 disp_size;\n" "layout(location=0) in vec2 position;\n" "layout(location=1) in vec2 texcoord0;\n" "layout(location=2) in vec4 color0;\n" "out vec2 uv;\n" "out vec4 color;\n" "void main() {\n" " gl_Position = vec4(((position/disp_size)-0.5)*vec2(2.0,-2.0), 0.5, " "1.0);\n" " uv = texcoord0;\n" " color = color0;\n" "}\n"; shd_desc.fs.images[0].name = "tex"; shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; shd_desc.fs.source = "#version 330\n" "uniform sampler2D tex;\n" "in vec2 uv;\n" "in vec4 color;\n" "out vec4 frag_color;\n" "void main() {\n" " frag_color = texture(tex, uv) * color;\n" "}\n"; sg_shader shd = sg_make_shader(&shd_desc); // pipeline object for imgui rendering sg_pipeline_desc pip_desc = {}; pip_desc.layout.buffers[0].stride = sizeof(ImDrawVert); auto& attrs = pip_desc.layout.attrs; attrs[0].format = SG_VERTEXFORMAT_FLOAT2; attrs[1].format = SG_VERTEXFORMAT_FLOAT2; attrs[2].format = SG_VERTEXFORMAT_UBYTE4N; pip_desc.shader = shd; pip_desc.index_type = SG_INDEXTYPE_UINT16; pip_desc.colors[0].blend.enabled = true; pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; pip_desc.colors[0].write_mask = SG_COLORMASK_RGB; pip = sg_make_pipeline(&pip_desc); // initial clear color pass_action.colors[0].action = SG_ACTION_CLEAR; pass_action.colors[0].value = {0.1f, 0.1f, 0.1f, 1.0f}; // draw loop while (!glfwWindowShouldClose(w)) { state.time.frame = stm_sec( stm_round_to_common_refresh_rate(stm_laptime(&state.time.laptime))); state.time.absolute += state.time.frame * state.time.factor; int cur_width, cur_height; glfwGetFramebufferSize(w, &cur_width, &cur_height); // this is standard ImGui demo code ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(float(cur_width), float(cur_height)); io.DeltaTime = (float)stm_sec(stm_laptime(&last_time)); ImGui::NewFrame(); ImGui::Begin("Camera"); ImGui::SliderFloat3("pos", state.camera.pos, -100.f, 100.f); ImGui::SliderFloat("near", &state.camera.near, 0.001f, 10.f); ImGui::SliderFloat("far", &state.camera.far, 1.0f, 10000.f); ImGui::SliderFloat("heading", &state.camera.heading, -180.0f, 180.f); ImGui::SliderFloat("pitch", &state.camera.pitch, -179.0f, 179.f); ImGui::End(); // handle input handle_mouse (w, &gGuiInputState); if (glfwGetMouseButton(w, GLFW_MOUSE_BUTTON_RIGHT)) { if (gControlMode == ControlMode::ControlModeNone) { gControlMode = ControlMode::ControlModeFPS; Camera_CalcFromMatrix(&state.camera, &state.camera.mtxView[0]); glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } } else { gControlMode = ControlMode::ControlModeNone; glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); Camera_Update( &state.camera, cur_width, cur_height, state.time.frame, 0, 0, nullptr); } if (gControlMode == ControlMode::ControlModeFPS) { float camera_accel[3] = {0.f, 0.f, 0.f}; float accel_scale = 100.0; if (glfwGetKey(w, GLFW_KEY_LEFT_SHIFT)) { accel_scale *= 3.; } else if (glfwGetKey(w, GLFW_KEY_LEFT_CONTROL)) { accel_scale /= 3.; } if (glfwGetKey(w, GLFW_KEY_W)) { camera_accel[0] -= accel_scale; } if (glfwGetKey(w, GLFW_KEY_S)) { camera_accel[0] += accel_scale; } if (glfwGetKey(w, GLFW_KEY_C)) { camera_accel[1] -= accel_scale; } if (glfwGetKey(w, GLFW_KEY_SPACE)) { camera_accel[1] += accel_scale; } if (glfwGetKey(w, GLFW_KEY_A)) { camera_accel[2] -= accel_scale; } if (glfwGetKey(w, GLFW_KEY_D)) { camera_accel[2] += accel_scale; } Camera_Update( &state.camera, cur_width, cur_height, state.time.frame, gGuiInputState.mousedX, gGuiInputState.mousedY, camera_accel); } if (ImGui::BeginMainMenuBar()) { ImGui::Text("AnimTestbed"); ImGui::Checkbox("ImGui Demo", &show_imgui_demo_window); ImGui::EndMainMenuBar(); } draw_grid(); skinned_mesh.DrawDebugUi(); animation_controller.DrawDebugUi(); animation_controller.Update(state.time.frame); animation_controller.Evaluate(); sgl_defaults(); sgl_matrix_mode_projection(); sgl_load_matrix((const float*)&state.camera.mtxProj); sgl_matrix_mode_modelview(); sgl_load_matrix((const float*)&state.camera.mtxView); skinned_mesh.DrawSkeleton(); // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow() if (show_imgui_demo_window) { ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver); ImGui::ShowDemoWindow(); } // the sokol_gfx draw pass sg_begin_default_pass(&pass_action, cur_width, cur_height); sgl_draw(); ImGui::Render(); draw_imgui(ImGui::GetDrawData()); sg_end_pass(); sg_commit(); glfwSwapBuffers(w); glfwPollEvents(); } // state.ozz = nullptr; /* cleanup */ ImGui::DestroyContext(); sgl_shutdown(); sg_shutdown(); glfwTerminate(); return 0; } bool LoadSkeleton(const char* _filename, ozz::animation::Skeleton* _skeleton) { assert(_filename && _skeleton); ozz::log::Out() << "Loading skeleton archive " << _filename << "." << std::endl; ozz::io::File file(_filename, "rb"); if (!file.opened()) { ozz::log::Err() << "Failed to open skeleton file " << _filename << "." << std::endl; return false; } ozz::io::IArchive archive(&file); if (!archive.TestTag()) { ozz::log::Err() << "Failed to load skeleton instance from file " << _filename << "." << std::endl; return false; } // Once the tag is validated, reading cannot fail. archive >> *_skeleton; return true; } bool LoadAnimation( const char* _filename, ozz::animation::Animation* _animation) { assert(_filename && _animation); ozz::log::Out() << "Loading animation archive: " << _filename << "." << std::endl; ozz::io::File file(_filename, "rb"); if (!file.opened()) { ozz::log::Err() << "Failed to open animation file " << _filename << "." << std::endl; return false; } ozz::io::IArchive archive(&file); if (!archive.TestTag()) { ozz::log::Err() << "Failed to load animation instance from file " << _filename << "." << std::endl; return false; } // Once the tag is validated, reading cannot fail. archive >> *_animation; return true; } static void load_skeleton(void) { // const char* skeleton_file = "../media/skeleton.ozz"; const char* skeleton_file = "../media/MixamoYBot-skeleton.ozz"; if (!LoadSkeleton(skeleton_file, &state.ozz->skeleton)) { std::cerr << "Could not load skeleton " << skeleton_file << std::endl; return; } const int num_soa_joints = state.ozz->skeleton.num_soa_joints(); const int num_joints = state.ozz->skeleton.num_joints(); state.ozz->local_matrices.resize(num_soa_joints); state.ozz->model_matrices.resize(num_joints); state.ozz->cache.Resize(num_joints); state.loaded.skeleton = true; std::cout << "Successfully loaded " << skeleton_file << " (soa: " << num_soa_joints << ", joints: " << num_joints << ")" << std::endl; } static void load_animation(void) { // const char* anim_file = "../media/animation1.ozz"; const char* anim_file = "../media/RunningSlow-loop.ozz"; if (!LoadAnimation(anim_file, &state.ozz->animation)) { std::cerr << "Could not load animation " << anim_file << std::endl; return; } state.loaded.animation = true; std::cout << "Successfully loade " << anim_file << std::endl; } static void draw_vec(const ozz::math::SimdFloat4& vec) { sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec)); } static void draw_line( const ozz::math::SimdFloat4& v0, const ozz::math::SimdFloat4& v1) { draw_vec(v0); draw_vec(v1); } static void draw_grid(void) { sgl_defaults(); sgl_matrix_mode_projection(); sgl_load_matrix((const float*)&state.camera.mtxProj); sgl_matrix_mode_modelview(); sgl_load_matrix((const float*)&state.camera.mtxView); const int grid_size = 10; sgl_begin_lines(); sgl_c3f(0.4f, 0.4f, 0.4f); for (int i = -grid_size; i <= grid_size; i++) { if (i == 0) { continue; } ozz::math::SimdFloat4 p0 = ozz::math::simd_float4::Load(i * 1.0f, 0.f, -grid_size * 1.0f, 1.f); ozz::math::SimdFloat4 p1 = ozz::math::simd_float4::Load(i * 1.0f, 0.f, grid_size * 1.0f, 1.f); draw_line(p0, p1); p0 = ozz::math::simd_float4::Load(-grid_size * 1.0f, 0.f, i * 1.0f, 1.f); p1 = ozz::math::simd_float4::Load(grid_size * 1.0f, 0.f, i * 1.0f, 1.f); draw_line(p0, p1); } sgl_c3f (0.7f, 0.4f, 0.2f); ozz::math::SimdFloat4 p0 = ozz::math::simd_float4::Load(0, 0.f, -grid_size * 1.0f, 1.f); ozz::math::SimdFloat4 p1 = ozz::math::simd_float4::Load(0, 0.f, grid_size * 1.0f, 1.f); draw_line(p0, p1); sgl_c3f (0.2f, 0.4f, 0.7f); p0 = ozz::math::simd_float4::Load(-grid_size * 1.0f, 0.f, 0.f, 1.f); p1 = ozz::math::simd_float4::Load(grid_size * 1.0f, 0.f, 0.f, 1.f); draw_line(p0, p1); sgl_end(); } // draw ImGui draw lists via sokol-gfx void draw_imgui(ImDrawData* draw_data) { assert(draw_data); if (draw_data->CmdListsCount == 0) { return; } // render the command list sg_apply_pipeline(pip); vs_params_t vs_params; vs_params.disp_size.x = ImGui::GetIO().DisplaySize.x; vs_params.disp_size.y = ImGui::GetIO().DisplaySize.y; sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE(vs_params)); for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++) { const ImDrawList* cl = draw_data->CmdLists[cl_index]; // append vertices and indices to buffers, record start offsets in resource binding struct const uint32_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert); const uint32_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx); const uint32_t vb_offset = sg_append_buffer( bind.vertex_buffers[0], {&cl->VtxBuffer.front(), vtx_size}); const uint32_t ib_offset = sg_append_buffer(bind.index_buffer, {&cl->IdxBuffer.front(), idx_size}); /* don't render anything if the buffer is in overflow state (this is also checked internally in sokol_gfx, draw calls that attempt from overflowed buffers will be silently dropped) */ if (sg_query_buffer_overflow(bind.vertex_buffers[0]) || sg_query_buffer_overflow(bind.index_buffer)) { continue; } bind.vertex_buffer_offsets[0] = vb_offset; bind.index_buffer_offset = ib_offset; sg_apply_bindings(&bind); int base_element = 0; for (const ImDrawCmd& pcmd : cl->CmdBuffer) { if (pcmd.UserCallback) { pcmd.UserCallback(cl, &pcmd); } else { const int scissor_x = (int)(pcmd.ClipRect.x); const int scissor_y = (int)(pcmd.ClipRect.y); const int scissor_w = (int)(pcmd.ClipRect.z - pcmd.ClipRect.x); const int scissor_h = (int)(pcmd.ClipRect.w - pcmd.ClipRect.y); sg_apply_scissor_rect(scissor_x, scissor_y, scissor_w, scissor_h, true); sg_draw(base_element, pcmd.ElemCount, 1); } base_element += pcmd.ElemCount; } } }