#include #include #include "RammingChart.hpp" #include "GUI.hpp" wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); void Chart::draw() { wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win dc.SetBrush(GetBackgroundColour()); dc.SetPen(GetBackgroundColour()); dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows dc.SetPen(*wxBLACK_PEN); dc.SetBrush(*wxWHITE_BRUSH); dc.DrawRectangle(m_rect); if (visible_area.m_width < 0.499) { dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2)); return; } if (!m_line_to_draw.empty()) { for (unsigned int i=0;i2) { m_buttons.erase(m_buttons.begin()+button_index); recalculate_line(); } } void Chart::mouse_clicked(wxMouseEvent& event) { wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); if ( button_index != -1) { m_dragged = &m_buttons[button_index]; m_previous_mouse = point; } } void Chart::mouse_moved(wxMouseEvent& event) { if (!event.Dragging() || !m_dragged) return; wxPoint pos = event.GetPosition(); wxRect rect = m_rect; rect.Deflate(side/2.); if (!(rect.Contains(pos))) { // the mouse left chart area mouse_left_window(event); return; } int delta_x = pos.x - m_previous_mouse.x; int delta_y = pos.y - m_previous_mouse.y; m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); m_previous_mouse = pos; recalculate_line(); } void Chart::mouse_double_clicked(wxMouseEvent& event) { if (!manual_points_manipulation) return; wxPoint point = event.GetPosition(); if (!m_rect.Contains(point)) // the click is outside the chart return; m_buttons.push_back(screen_to_math(point)); std::sort(m_buttons.begin(),m_buttons.end()); recalculate_line(); return; } void Chart::recalculate_line() { std::vector points; for (auto& but : m_buttons) { points.push_back(wxPoint(math_to_screen(but.get_pos()))); if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back(); if (points.size()>1 && points.back().x > m_rect.GetRight()) { points.pop_back(); break; } } std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; }); m_line_to_draw.clear(); m_total_volume = 0.f; // Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point // false - second ---- || ------- const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points std::vector diag(N+1); std::vector mu(N+1); std::vector lambda(N+1); std::vector h(N+1); std::vector rhs(N+1); // let's fill in inner equations for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x; std::fill(diag.begin(),diag.end(),2.f); for (int i=1;i<=N-1;++i) { mu[i] = h[i]/(h[i]+h[i+1]); lambda[i] = 1.f - mu[i]; rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) - float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) ); } // now fill in the first and last equations, according to boundary conditions: if (boundary_first_derivative) { const float endpoints_derivative = 0; lambda[0] = 1; mu[N] = 1; rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative); rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x)); } else { lambda[0] = 0; mu[N] = 0; rhs[0] = 0; rhs[N] = 0; } // the trilinear system is ready to be solved: for (int i=1;i<=N;++i) { float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation diag[i]-= multiple * lambda[i-1]; rhs[i] -= multiple * rhs[i-1]; } // now the back substitution (vector mu contains invalid values from now on): rhs[N] = rhs[N]/diag[N]; for (int i=N-1;i>=0;--i) rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i]; unsigned int i=1; float y=0.f; for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) { if (splines) { if (i x) y = points[0].y; else if (points[N].x < x) y = points[N].y; else y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) + (points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] + (points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i]; m_line_to_draw.push_back(y); } else { float x_math = screen_to_math(wxPoint(x,0)).m_x; if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math) ++i; m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y); } m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); } wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED)); Refresh(); } std::vector Chart::get_ramming_speed(float sampling) const { std::vector speeds_out; const int number_of_samples = std::round( visible_area.m_width / sampling); if (number_of_samples>0) { const int dx = (m_line_to_draw.size()-1) / number_of_samples; for (int j=0;j> Chart::get_buttons() const { std::vector> buttons_out; for (const auto& button : m_buttons) buttons_out.push_back(std::make_pair(button.get_pos().m_x,button.get_pos().m_y)); return buttons_out; } BEGIN_EVENT_TABLE(Chart, wxWindow) EVT_MOTION(Chart::mouse_moved) EVT_LEFT_DOWN(Chart::mouse_clicked) EVT_LEFT_UP(Chart::mouse_released) EVT_LEFT_DCLICK(Chart::mouse_double_clicked) EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked) EVT_LEAVE_WINDOW(Chart::mouse_left_window) EVT_PAINT(Chart::paint_event) END_EVENT_TABLE()