#include "camera.hpp" #include "debugdraw.hpp" #include "editor.hpp" #include "model.hpp" #include "physics.hpp" #include "scene.hpp" #include "ui.hpp" #include "world.hpp" extern "C" { #include "memory.h" #include "plat.h" } #include #include struct Editor_PD : Physics_Debughook { void shrinkwrap( const Int_Collider* col, int axis, const v2f* points, int count ) override; void projection( const Int_Collider* col, const v3f& n, int axis, const v2f* points, int count ) override; }; struct Editor_DD : Device_Debug_Hooks { }; static struct { Editor_Settings settings; Editor_PD physics_debughook; Editor_DD device_debughook; UI* ui; UI::Toolbar* toolbar; UI::Button* mat_btn, * ent_btn, * phy_btn, * dev_btn; UI::Modal* mat_win, * ent_win, * phy_win, * dev_win; UI::Label* mat_name, * spl; UI::Slider* metalness_input; UI::Slider* roughness_input; UI::Slider* ao_input; UI::Label* ent_handle, * ent_index, * ent_version, * ent_mask; World* world; Entity_Id selected; Model_Instance* selected_inst; int selected_mesh; int hovered_axis; int dragging_gizmo; int debug_sat_axis; v3f drag_axis, drag_off; v3f gf, gu, gr; std::vector shrinkwrap_points; std::vector projection_points; std::vector psos; v3f projection_start, projection_end; } editor; static int dev_win_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::destroy) { editor.dev_btn->enable(); editor.dev_win = 0; } return 0; } static void create_pso_view(UI::Element* p, const Pipeline* pso) { auto ui = editor.ui; auto disp_val = [&](UI::Element* parent, const char* name, const char* fmt, auto... vals) { char buf[64]; sprintf(buf, fmt, vals...); ui->create_element(parent, name); ui->create_element(parent, buf); }; auto disp_bool = [&](UI::Element* parent, const char* name, bool v) { disp_val(parent, name, v? "Yes": "No"); }; auto disp_enum = [&](UI::Element* parent, const char* name, int v, auto... s) { const char* arr[] = { s... }; disp_val(parent, name, "%s", arr[v]); }; disp_val(p, "PSO Hash", "%llx", pso->pipeline_hash); disp_val(p, "Descriptor Resource Hash", "%llx", pso->descriptor_resource_hash); disp_val( p, "Viewport", "%d %d %d %d", pso->viewport[0], pso->viewport[1], pso->viewport[2], pso->viewport[3] ); disp_val( p, "Scissor", "%d %d %d %d", pso->scissor[0], pso->scissor[1], pso->scissor[2], pso->scissor[3] ); disp_bool(p, "Depth Test", pso->depth_test); disp_bool(p, "Depth Write", pso->depth_write); disp_bool(p, "Blend Enable", pso->blend_enable); disp_enum(p, "Depth Mode", (int)pso->depth_mode, "Less", "Less Equal", "Equal", "Greater", "Greater Equal", "Always", "Never" ); disp_enum(p, "Geometry Type", (int)pso->geo, "Triangles", "Lines", "Points" ); auto disp_blend = [&](const char* n, int v) { disp_enum(p, n, v, "Zero", "One", "Src Colour", "Inv_src_colour", "Dst_colour", "Inv_dst_colour", "Src_alpha", "Inv_src_alpha", "Dst_alpha", "Inv_dst_alpha" ); }; auto disp_blend_mode = [&](const char* n, int v) { disp_enum(p, n, v, "Add", "Subtract", "Reverse Subtract", "Min", "Max" ); }; disp_blend("Colour Blend Source", (int)pso->blend_src); disp_blend("Colour Blend Destination", (int)pso->blend_dst); disp_blend_mode("Colour Blend Mode", (int)pso->blend_mode); disp_blend("Alpha Blend Source", (int)pso->blend_src_alpha); disp_blend("Alpha Blend Destination", (int)pso->blend_dst_alpha); disp_blend_mode("Alpha Blend Mode", (int)pso->blend_mode_alpha); disp_val(p, "Vertex Format ID", "%d", (int)pso->vertex_format.index); disp_val(p, "Shader ID", "%d", (int)pso->shader.index); auto dt = ui->create_element(p, "Descriptors"); for (int i = 0; i < pso->descriptor_count; i++) { int rows[] = { 300, 300, 0 }; const Descriptor& d = pso->descriptors[i]; char buf[32]; sprintf(buf, "%d", d.slot); auto tree = ui->create_element(dt, buf); auto table = ui->create_element(tree, rows); disp_enum(table, "Type", (int)d.type, "Texture", "Constant Buffer" ); switch (d.type) { case Descriptor::Type::texture: { Texture_Descriptor& tex = *(Texture_Descriptor*)d.payload; disp_val(table, "Texture ID", "%d", (int)tex.texture.index); disp_val(table, "Sampler ID", "%d", (int)tex.sampler.index); } break; case Descriptor::Type::constant_buffer: { Constant_Buffer_Descriptor& cb = *(Constant_Buffer_Descriptor*)d.payload; disp_val(table, "Offset", "%d", cb.offset); disp_val(table, "Size", "%d", cb.size); disp_val(table, "Buffer ID", "%d", (int)cb.buffer.index); } break; } tree->collapse(); } } static int dev_btn_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::click) { editor.dev_btn->disable(); editor.dev_win = editor.ui->create_element( editor.ui->root, "Device Debugger" ); editor.dev_win->handler = dev_win_handler; auto ui = editor.ui; auto& psov = editor.psos; auto& hook = editor.device_debughook; auto content = ui->create_element( editor.dev_win->contents, "Pipeline State Objects" ); psov.clear(); psov.resize(hook.query_psos(0)); hook.query_psos(&psov[0]); for (auto& pso : psov) { UI::Tree* tree; UI::Table* table; int rows[] = { 300, 300, 0 }; char buf[32]; sprintf(buf, "0x%lx", pso.pipeline_hash); tree = ui->create_element(content, buf); table = ui->create_element(tree, rows); create_pso_view(table, &pso); tree->collapse(); } } return 0; } static int mat_win_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::destroy) { editor.mat_btn->enable(); editor.mat_win = 0; } return 0; } static void setup_mat_edit() { if (editor.selected_inst) { Mesh* meshes = editor.selected_inst->m->get_meshes(); Mesh& msh = meshes[editor.selected_mesh]; Material* mat = msh.material; editor.metalness_input->enable(); editor.metalness_input->input->set_val(mat->metalness); editor.roughness_input->enable(); editor.roughness_input->input->set_val(mat->roughness); editor.ao_input->enable(); editor.ao_input->input->set_val(mat->ao); editor.mat_name->set_text(mat->name); } else { editor.metalness_input->disable(); editor.roughness_input->disable(); editor.ao_input->disable(); editor.mat_name->set_text(""); } } void setup_ent_debug() { if (editor.selected) { int ind = entity_index(editor.selected); editor.ent_handle->set_hex(editor.selected); editor.ent_version->set_int(entity_version(editor.selected)); editor.ent_index->set_int(ind); editor.ent_mask->set_hex(editor.world->masks[ind]); } else { editor.ent_handle->set_text(""); editor.ent_version->set_text(""); editor.ent_index->set_text(""); editor.ent_mask->set_text(""); } } static int mat_btn_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::click) { editor.mat_win = editor.ui->create_element( editor.ui->root, "Material Editor" ); int rows[] = { 200, 250, 0 }; editor.mat_win->handler = mat_win_handler; editor.mat_btn->disable(); editor.mat_name = editor.ui->create_element( editor.mat_win->contents, "" ); auto container = editor.ui->create_element( editor.mat_win->contents, rows ); #define bind_mat_float(el, name, point) \ editor.ui->create_element( \ container, \ name \ ); \ el = editor.ui->create_element( \ container, \ 0.0f, \ 1.0f \ ); \ el->input->handler = []( \ UI::Element* e, \ const UI::Message& m \ ) { \ if ( \ m.type == UI::Message::Type::input_changed && \ editor.selected_inst \ ) { \ UI::Float_Input* in = (UI::Float_Input*)e; \ Mesh* meshes = editor.selected_inst->m->get_meshes(); \ Mesh& mesh = meshes[editor.selected_mesh]; \ Material* mat = mesh.material; \ mat->point = in->val; \ } \ return 0; \ }; bind_mat_float( editor.metalness_input, "Metalness", metalness ); bind_mat_float( editor.roughness_input, "Roughness", roughness ); bind_mat_float( editor.ao_input, "Ambient Occlusion", ao ); #undef bind_mat_float setup_mat_edit(); } return 0; } static int ent_win_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::destroy) { editor.ent_btn->enable(); editor.ent_win = 0; } return 0; } static int ent_btn_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::click) { auto ui = editor.ui; editor.ent_win = ui->create_element( editor.ui->root, "Entity Debugger" ); editor.ent_win->handler = ent_win_handler; editor.ent_btn->disable(); int rows[] = { 100, 50, 0 }; auto tab = ui->create_element( editor.ent_win->contents, rows ); ui->create_element(tab, "Handle"); editor.ent_handle = ui->create_element(tab, ""); ui->create_element(tab, "Index"); editor.ent_index = ui->create_element(tab, ""); ui->create_element(tab, "Version"); editor.ent_version = ui->create_element(tab, ""); ui->create_element(tab, "Mask"); editor.ent_mask = ui->create_element(tab, ""); setup_ent_debug(); } return 0; } static int phy_win_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::destroy) { editor.phy_btn->enable(); editor.phy_win = 0; } return 0; } static int phy_btn_handler(UI::Element* e, const UI::Message& m) { (void)e; if (m.type == UI::Message::Type::click) { auto ui = editor.ui; editor.phy_win = ui->create_element( editor.ui->root, "Physics Debugger" ); auto cont = editor.phy_win->contents; editor.phy_win->handler = phy_win_handler; editor.phy_btn->disable(); auto cdebug = ui->create_element(cont, "Debug Draw"); cdebug->handler = [](UI::Element* e, const UI::Message& m) { if (m.type == UI::Message::Type::checkbox_changed) { UI::Checkbox* ch = (UI::Checkbox*)e; editor.settings.debug_physics = ch->val; } return 0; }; cdebug->set_val(editor.settings.debug_physics); auto cpause = ui->create_element(cont, "Pause"); cpause->set_val(editor.settings.pause_physics); cpause->handler = [](UI::Element* e, const UI::Message& m) { if (m.type == UI::Message::Type::checkbox_changed) { UI::Checkbox* ch = (UI::Checkbox*)e; editor.settings.pause_physics = ch->val; } return 0; }; auto csat = ui->create_element(cont, "Debug SAT"); csat->set_val(editor.settings.debug_sat); csat->handler = [](UI::Element* e, const UI::Message& m) { if (m.type == UI::Message::Type::checkbox_changed) { UI::Checkbox* ch = (UI::Checkbox*)e; editor.settings.debug_sat = ch->val; } return 0; }; int rows[] = { 150, 50, 0 }; auto tab = ui->create_element(cont, rows); ui->create_element(tab, "Debug Axis"); auto iaxis = ui->create_element(tab); iaxis->set_val(editor.debug_sat_axis); iaxis->handler = [](UI::Element* e, const UI::Message& m) { if (m.type == UI::Message::Type::input_finalised) { UI::Int_Input* i = (UI::Int_Input*)e; editor.debug_sat_axis = i->val; } return 0; }; ui->create_element(tab, "Shrinkwrap"); editor.spl = ui->create_element(tab, "0"); } return 0; } void init_editor(Device* d, UI* ui, World* world) { editor.device_debughook.dev = d; d->hooks = &editor.device_debughook; editor.ui = ui; editor.toolbar = ui->create_element(ui->root); editor.mat_btn = ui->create_element( editor.toolbar, "Edit Material" ); editor.mat_btn->handler = mat_btn_handler; editor.ent_btn = ui->create_element( editor.toolbar, "Entity Debugger" ); editor.ent_btn->handler = ent_btn_handler; editor.phy_btn = ui->create_element( editor.toolbar, "Physics Debugger" ); editor.phy_btn->handler = phy_btn_handler; editor.dev_btn = ui->create_element( editor.toolbar, "Device Debugger" ); editor.dev_btn->handler = dev_btn_handler; editor.mat_win = 0; editor.ent_win = 0; editor.phy_win = 0; editor.dev_win = 0; editor.debug_sat_axis = 0; editor.world = world; editor.dragging_gizmo = 0; zero(&editor.settings, sizeof editor.settings); } void deinit_editor() { } void editor_on_select(Entity_Id e, int m) { if (!editor.ui->hovered && !editor.dragging_gizmo) { editor.selected = e; if (e && editor.world->has(e)) { auto& cm = editor.world->get(e); editor.selected_inst = cm.i; editor.selected_mesh = m; } else { editor.selected_inst = 0; editor.selected_mesh = -1; } if (editor.mat_win) { setup_mat_edit(); } if (editor.ent_win) { setup_ent_debug(); } } } void editor_draw(Line_Renderer& lr) { if (editor.selected_inst) { lr.add_box( editor.selected_inst->bounds[editor.selected_mesh] ); } if (editor.selected) { Entity_Id e = editor.selected; World& w = *editor.world; if (w.has(e)) { Transform& t = w.get(e); m4f& m = t.mat; v3f p = v3f(m.m[3][0], m.m[3][1], m.m[3][2]); v3f& f = editor.gf; v3f& u = editor.gu; v3f& r = editor.gr; if (editor.hovered_axis == 1) lr.colour(v3f(1.0f, 1.0f, 1.0f)); else lr.colour(v3f(1.0f, 0.0f, 0.0f)); lr.add_arrow(p, p + f); if (editor.hovered_axis == 2) lr.colour(v3f(1.0f, 1.0f, 1.0f)); else lr.colour(v3f(0.0f, 1.0f, 0.0f)); lr.add_arrow(p, p + u); if (editor.hovered_axis == 3) lr.colour(v3f(1.0f, 1.0f, 1.0f)); else lr.colour(v3f(0.0f, 0.0f, 1.0f)); lr.add_arrow(p, p + r); } if (w.has(e)) { if (editor.settings.debug_sat) { lr.colour(v3f(1.0f, 1.0f, 1.0f)); int i, c = editor.shrinkwrap_points.size(); for (i = 0; i < c; i++) { v2f point = editor.shrinkwrap_points[i]; v2f prev = editor.shrinkwrap_points[(i + 1) % c]; lr.add_line(v3f(prev, 0.0f), v3f(point, 0.0f)); } lr.colour(v3f(1.0f, 0.0f, 0.0f)); c = editor.projection_points.size(); for (i = 0; i < c; i++) { v2f point = editor.projection_points[i]; lr.add_point(v3f(point, 0.0f)); } lr.colour(v3f(1.0f, 0.5f, 1.0f)); lr.add_arrow(editor.projection_start, editor.projection_end); } } } } v3f gizmo_pos(const v3f& p, const App& app, const Camera& cam) { v2f uv = (2.0f * v2f(app.mx, app.my) - v2f(app.w, app.h)) / (float)app.h; v4f e = cam.get_proj().inverse() * v4f(uv.x, uv.y, -1.0f, 1.0f); e.z = -1.0f; e.w = 0.0f; v4f d4 = cam.get_view().inverse() * e; v3f d = v3f::normalised(v3f(d4.x, d4.y, d4.z)); v3f n = v3f::perp(editor.drag_axis); float denom = v3f::dot(n, d); v3f o = cam.position + cam.near * cam.forward; float t = v3f::dot(p - o, n) / denom; return (o + t * d) * editor.drag_axis; } void editor_update(const App& app, const Camera& cam) { Entity_Id e = editor.selected; World& w = *editor.world; if (!e || !w.has(e)) return; Transform& t = w.get(e); m4f& m = t.mat; v3f p = v3f(m.m[3][0], m.m[3][1], m.m[3][2]); auto vp = cam.get_proj() * cam.get_view(); if (editor.dragging_gizmo) { auto gp = gizmo_pos(p, app, cam) + editor.drag_off; m.m[3][0] += (gp.x - m.m[3][0]) * editor.drag_axis.x; m.m[3][1] += (gp.y - m.m[3][1]) * editor.drag_axis.y; m.m[3][2] += (gp.z - m.m[3][2]) * editor.drag_axis.z; if (w.has(e)) { Rigidbody& rb = w.get(e); rb.set_pos(v3f(m.m[3][0], m.m[3][1], m.m[3][2])); } if (app.mjr(mbtn_left)) editor.dragging_gizmo = 0; return; } editor.hovered_axis = 0; editor.gf = v3f::normalised((m * v4f(0.0f, 0.0f, 1.0f, 0.0f)).xyz()); editor.gu = v3f::normalised((m * v4f(0.0f, 1.0f, 0.0f, 0.0f)).xyz()); editor.gr = v3f::normalised((m * v4f(1.0f, 0.0f, 0.0f, 0.0f)).xyz()); auto do_axis = [&](const v3f& axis) { v4f sss4 = vp * v4f(p, 1.0f); v4f sse4 = vp * v4f(p + axis, 1.0f); if (sss4.w < 0.0f || sse4.w < 0.0f) return false; float mx = app.mx; float my = app.my; v3f sss = sss4.xyz() / sss4.w; v3f sse = sse4.xyz() / sse4.w; v3f s = v3f(app.w, app.h, 1.0f); sss = (sss * 0.5f + 0.5f) * s; sse = (sse * 0.5f + 0.5f) * s; float d = fabsf((sse.y - sss.y) * mx - (sse.x - sss.x) * my + sse.x * sss.y - sse.y * sss.x); d /= v2f::mag(sse.xy() - sss.xy()); if (d < 8.0f) { v2f mini( INFINITY, INFINITY); v2f maxi(-INFINITY, -INFINITY); if (sss.x < mini.x) mini.x = sss.x; if (sss.y < mini.y) mini.y = sss.y; if (sse.x < mini.x) mini.x = sse.x; if (sse.y < mini.y) mini.y = sse.y; if (sss.x > maxi.x) maxi.x = sss.x; if (sss.y > maxi.y) maxi.y = sss.y; if (sse.x > maxi.x) maxi.x = sse.x; if (sse.y > maxi.y) maxi.y = sse.y; if (mx > mini.x && my > mini.y && mx < maxi.x && my < maxi.y) { if (app.mjp(mbtn_left)) { editor.drag_axis = axis; editor.drag_off = p - gizmo_pos(p, app, cam); editor.dragging_gizmo = 1; } return true; } } return false; }; if (do_axis(editor.gf)) { editor.hovered_axis = 1; return; } if (do_axis(editor.gu)) { editor.hovered_axis = 2; return; } if (do_axis(editor.gr)) { editor.hovered_axis = 3; return; } } Editor_Settings& editor_settings() { return editor.settings; } Physics_Debughook* editor_physics_debughook() { return &editor.physics_debughook; } void Editor_PD::shrinkwrap( const Int_Collider* col, int axis, const v2f* points, int count ) { World& world = *editor.world; Entity_Id s = editor.selected; Rigidbody* rb; if (!editor.settings.debug_sat) return; if (!s) return; if (!world.has(s)) return; rb = &world.get(s); if (axis == editor.debug_sat_axis && col->rb == rb) { int i; auto& v = editor.shrinkwrap_points; v.clear(); editor.spl->set_int(count); for (i = 0; i < count; i++) v.push_back(points[i]); } } void Editor_PD::projection( const Int_Collider* col, const v3f& n, int axis, const v2f* points, int count ) { World& world = *editor.world; Entity_Id s = editor.selected; Rigidbody* rb; if (!editor.settings.debug_sat) return; if (!s) return; if (!world.has(s)) return; rb = &world.get(s); if (axis == editor.debug_sat_axis && col->rb == rb) { int i; float w = 1.0f / (float)count; auto& v = editor.projection_points; v.clear(); editor.projection_start = v3f(0.0f); for (i = 0; i < count; i++) { const v2f& point = points[i]; v.push_back(point); editor.projection_start += v3f(point, 0.0f) * w; } editor.projection_end = editor.projection_start + n; } }