/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ // SimpleGraphView.cpp : implementation of the CSimpleGraphView class // #include "stdafx.h" #include "SimpleGraph.h" #include "SimpleGraphDoc.h" #include "SimpleGraphView.h" #include "vector3.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CSimpleGraphView IMPLEMENT_DYNCREATE(CSimpleGraphView, CView) BEGIN_MESSAGE_MAP(CSimpleGraphView, CView) //{{AFX_MSG_MAP(CSimpleGraphView) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_COMMAND(IDM_DELETE, OnDelete) ON_UPDATE_COMMAND_UI(IDM_DELETE, OnUpdateDelete) ON_COMMAND(IDM_ZOOM_EXTENTS, OnZoomExtents) ON_WM_RBUTTONDOWN() ON_WM_RBUTTONUP() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSimpleGraphView construction/destruction CSimpleGraphView::CSimpleGraphView() : m_Min (0, 0), m_Max (100, 100), m_DraggingPt (-1), m_SelPt (-1), m_IsZooming (false) { } CSimpleGraphView::~CSimpleGraphView() { } BOOL CSimpleGraphView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs m_Brush.CreateSolidBrush (RGB (0, 255, 0)); m_SelBrush.CreateSolidBrush (RGB (255, 0, 0)); return CView::PreCreateWindow(cs); } ///////////////////////////////////////////////////////////////////////////// // // OnDraw // ///////////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnDraw (CDC* dc) { HFONT old_font = (HFONT)::SelectObject (*dc, m_Font); Render_Axis (dc); Render_Graph (dc); Render_Points (dc); ::SelectObject (*dc, old_font); return ; } ///////////////////////////////////////////////////////////////////////////// // CSimpleGraphView diagnostics #ifdef _DEBUG void CSimpleGraphView::AssertValid() const { CView::AssertValid(); } void CSimpleGraphView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CSimpleGraphDoc* CSimpleGraphView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSimpleGraphDoc))); return (CSimpleGraphDoc*)m_pDocument; } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSimpleGraphView message handlers ///////////////////////////////////////////////////////////////////////////// // // Repaint_Graph // ///////////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Repaint_Graph (void) { CRect rect; Get_Graph_Rect (rect); InvalidateRect (rect, TRUE); UpdateWindow (); return ; } /////////////////////////////////////////////////////////////////////// // // Render_Axis // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Render_Axis (CDC *dc) { CRect view_rect; GetClientRect (&view_rect); CRect graph_rect; Get_Graph_Rect (graph_rect); dc->MoveTo (view_rect.left + 70, view_rect.top + 25); dc->LineTo (view_rect.left + 70, view_rect.bottom - 50); dc->MoveTo (view_rect.left + 70, view_rect.bottom - 45); dc->LineTo (view_rect.right - 25, view_rect.bottom - 45); int tick_width = 35; int tick_height = 35; int ticks_x = (graph_rect.Width () / tick_width); int ticks_y = (graph_rect.Height () / tick_height); tick_width = (graph_rect.Width () / ticks_x); tick_height = (graph_rect.Height () / ticks_y); float x_val = m_Min.X; float y_val = m_Max.Y; float x_inc = (m_Max.X - m_Min.X) / ticks_x; float y_inc = (m_Min.Y - m_Max.Y) / ticks_y; // // Draw the x-axis tick marks // int location = graph_rect.left; for (int tick = 0; tick <= ticks_x; tick ++) { dc->MoveTo (location, view_rect.bottom - 50); dc->LineTo (location, view_rect.bottom - 40); CRect label_rect; label_rect.left = location - 15; label_rect.right = location + 15; label_rect.top = view_rect.bottom - 38; label_rect.bottom = view_rect.bottom - 20; CString label; label.Format ("%.2f", x_val); dc->DrawText (label, label_rect, DT_CENTER); x_val += x_inc; location += tick_width; } // // Draw the y-axis tick marks // location = graph_rect.top; for (tick = 0; tick <= ticks_y; tick ++) { dc->MoveTo (view_rect.left + 65, location); dc->LineTo (view_rect.left + 75, location); CRect label_rect; label_rect.left = view_rect.left + 38; label_rect.right = view_rect.left + 63; label_rect.top = location - 15; label_rect.bottom = location + 15; CString label; label.Format ("%.2f", y_val); dc->DrawText (label, label_rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER); y_val += y_inc; location += tick_height; } return ; } /////////////////////////////////////////////////////////////////////// // // Render_Graph // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Render_Graph (CDC *dc) { Curve1DClass &spline = GetDocument ()->Get_Spline (); if (spline.Key_Count () < 2) { return ; } CRect graph_area; Get_Graph_Rect (graph_area); float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; float start_time = 0; float end_time = 0; float foo = 0; spline.Get_Key (0, &foo, &start_time); spline.Get_Key (spline.Key_Count () - 1, &foo, &end_time); start_time = max (m_Min.X, start_time); end_time = min (m_Max.X, end_time); float start_value = 0; float end_value = 0; CPoint start_pt; CPoint end_pt; spline.Evaluate (start_time, &start_value); spline.Evaluate (end_time, &end_value); Value_To_Point (Vector2 (start_time, start_value), &start_pt); Value_To_Point (Vector2 (end_time, end_value), &end_pt); int count = ((end_pt.x - start_pt.x) / 4); float time_inc = (end_time - start_time) / (float)count; // // Draw the curve // dc->MoveTo (start_pt.x, start_pt.y); for (float time = start_time; time <= end_time; time += time_inc) { float value; spline.Evaluate (time, &value); float percent_x = (time - m_Min.X) / delta_x; float percent_y = (value - m_Min.Y) / delta_y; int client_x = graph_area.left + int(graph_area.Width () * percent_x); int client_y = graph_area.top + int(graph_area.Height () * (1-percent_y)); if (percent_x <= 0) { dc->MoveTo (client_x, client_y); } if ( percent_x >= 0 && percent_x <= 1.0F && percent_y >= 0 && percent_y <= 1.0F) { dc->LineTo (client_x, client_y); } } dc->LineTo (end_pt.x, end_pt.y); return ; } /////////////////////////////////////////////////////////////////////// // // Render_Points // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Render_Points (CDC *dc) { Curve1DClass &spline = GetDocument ()->Get_Spline (); if (spline.Key_Count () < 1) { return ; } // // Draw the points // CBrush *old_brush = dc->SelectObject (&m_Brush); int count = spline.Key_Count (); for (int index = 0; index < count; index ++) { float time = 0; float value; spline.Get_Key (index, &value, &time); CPoint graph_pt; Value_To_Point (Vector2 (time,value), &graph_pt); if (m_SelPt == index) { dc->SelectObject (&m_SelBrush); dc->Ellipse (graph_pt.x - 4, graph_pt.y - 4, graph_pt.x + 4, graph_pt.y + 4); dc->SelectObject (&m_Brush); } else { dc->Ellipse (graph_pt.x - 4, graph_pt.y - 4, graph_pt.x + 4, graph_pt.y + 4); } if (m_DraggingPt == index) { CRect label_rect; label_rect.left = graph_pt.x - 30; label_rect.right = graph_pt.x + 30; label_rect.top = graph_pt.y - 30; label_rect.bottom = graph_pt.y - 6; CString label; label.Format ("%.2f, %.2f", time, value); dc->DrawText (label, label_rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER); } } dc->SelectObject (old_brush); return ; } /////////////////////////////////////////////////////////////////////// // // Get_Visible_Points // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Get_Visible_Points (int *left_pt, int *right_pt) { Curve1DClass &spline = GetDocument ()->Get_Spline (); (*left_pt) = -1; (*right_pt) = -1; CRect graph_area; Get_Graph_Rect (graph_area); float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; int count = spline.Key_Count (); for (int index = 0; index < count; index ++) { float time = 0; float value; spline.Get_Key (index, &value, &time); float percent_x = (time - m_Min.X) / delta_x; float percent_y = 1.0F - ((value - m_Min.Y) / delta_y); // // Is this point in the current view? // if ( percent_x >= 0 && percent_x <= 1.0F && percent_y >= 0 && percent_y <= 1.0F) { if ((*left_pt) == -1) { (*left_pt) = index; } (*right_pt) = index; } } return ; } /////////////////////////////////////////////////////////////////////// // // Hit_Test // /////////////////////////////////////////////////////////////////////// CSimpleGraphView::HITTYPE CSimpleGraphView::Hit_Test (const CPoint &point, int *hit_pt) { Curve1DClass &spline = GetDocument ()->Get_Spline (); HITTYPE retval = HIT_UNKNOWN; CRect graph_area; Get_Graph_Rect (graph_area); //CPoint graph_pt; //graph_pt.x = point.x - graph_area.left; //graph_pt.y = point.y - graph_area.top; if ( (point.x >= 0 && point.x < graph_area.left) || (point.y >= 0 && point.y < graph_area.top)) { retval = HIT_AXIS; } else if (point.x >= graph_area.left && point.y >= graph_area.top) { retval = HIT_GRAPH_AREA; float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; int count = spline.Key_Count (); for (int index = 0; index < count; index ++) { float time = 0; float value; spline.Get_Key (index, &value, &time); float percent_x = (time - m_Min.X) / delta_x; float percent_y = 1.0F - ((value - m_Min.Y) / delta_y); int graph_x = graph_area.left + int(graph_area.Width () * percent_x); int graph_y = graph_area.top + int(graph_area.Height () * percent_y); // // Are we within 'fudge' of the graph point? // if ( ::abs (graph_x - point.x) < 8 && ::abs (graph_y - point.y) < 8) { (*hit_pt) = index; retval = HIT_POINT; break; } } } return retval; } /////////////////////////////////////////////////////////////////////// // // OnLButtonDown // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnLButtonDown (UINT nFlags, CPoint point) { if ((nFlags & MK_RBUTTON) == 0 && m_DraggingPt == -1) { int point_index = 0; HITTYPE hit_type = Hit_Test (point, &point_index); if (hit_type == HIT_GRAPH_AREA) { m_DraggingPt = Add_New_Point (point); } else if (hit_type == HIT_POINT) { m_DraggingPt = point_index; } if (m_DraggingPt >= 0) { m_SelPt = m_DraggingPt; Repaint_Graph (); SetCapture (); } } CView::OnLButtonDown (nFlags, point); return; } /////////////////////////////////////////////////////////////////////// // // OnLButtonDown // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnLButtonUp (UINT nFlags, CPoint point) { if (m_DraggingPt >= 0) { ::ReleaseCapture (); m_DraggingPt = -1; Repaint_Graph (); } CView::OnLButtonUp (nFlags, point); return ; } /////////////////////////////////////////////////////////////////////// // // OnMouseMove // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnMouseMove (UINT nFlags, CPoint point) { if (nFlags & MK_LBUTTON && nFlags & MK_RBUTTON) { CPoint delta = m_ZoomPt - point; float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; CRect graph_area; Get_Graph_Rect (graph_area); float units_x = (delta.x * delta_x) / (float)graph_area.Width (); float units_y = -(delta.y * delta_y) / (float)graph_area.Height (); m_Min.X += units_x; m_Min.Y += units_y; m_Max.X += units_x; m_Max.Y += units_y; InvalidateRect (NULL, TRUE); UpdateWindow (); } else if (m_IsZooming) { CPoint delta = m_ZoomPt - point; float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; float factor = ((float)delta.y) / 100.0F; /*if (delta.y < 0) { factor = 100.0F / ((float)delta.y); }*/ m_Min.X = m_Min.X - (factor * delta_x); m_Min.Y = m_Min.Y - (factor * delta_y); m_Max.X = m_Max.X + (factor * delta_x); m_Max.Y = m_Max.Y + (factor * delta_y); InvalidateRect (NULL, TRUE); UpdateWindow (); } else if (m_DraggingPt >= 0) { // // Determine the new value based on the the screen location // of the mouse cursor... // Vector2 new_value; Point_To_Value (point, &new_value); new_value.X = max (new_value.X, m_Min.X); new_value.Y = max (new_value.Y, m_Min.Y); new_value.X = min (new_value.X, m_Max.X); new_value.Y = min (new_value.Y, m_Max.Y); // // Make sure the new point is sorted correctly // m_DraggingPt = Move_Value (m_DraggingPt, new_value); m_SelPt = m_DraggingPt; } m_ZoomPt = point; CView::OnMouseMove (nFlags, point); return ; } /////////////////////////////////////////////////////////////////////// // // Add_New_Point // /////////////////////////////////////////////////////////////////////// int CSimpleGraphView::Add_New_Point (CPoint &point) { Curve1DClass &spline = GetDocument ()->Get_Spline (); Vector2 new_value; Point_To_Value (point, &new_value); new_value.X = max (new_value.X, m_Min.X); new_value.Y = max (new_value.Y, m_Min.Y); new_value.X = min (new_value.X, m_Max.X); new_value.Y = min (new_value.Y, m_Max.Y); float time = new_value.X; float value = new_value.Y; int index = spline.Add_Key (value,time); return Move_Value (index, new_value); } /////////////////////////////////////////////////////////////////////// // // Move_Value // /////////////////////////////////////////////////////////////////////// int CSimpleGraphView::Move_Value (int old_index, const Vector2 &new_value) { Curve1DClass &spline = GetDocument ()->Get_Spline (); int new_index = -1; int count = spline.Key_Count (); Vector2 *point_list = new Vector2[count]; int curr_index = 0; for (int list_index = 0; list_index < count; list_index ++) { // // Skip the old value // if (list_index != old_index) { // // Get this key's value // float time = 0; float value; spline.Get_Key (list_index, &value, &time); Vector2 curr_point; curr_point.X = time; curr_point.Y = value; // // Should the moving-value be inserted before the current value? // if (new_index == -1 && (new_value.X < curr_point.X)) { new_index = curr_index++; point_list[new_index].Set (new_value.X, new_value.Y); } // // Add the current value to the list // point_list[curr_index++].Set (curr_point.X, curr_point.Y); } } if (old_index != -1 && new_index == -1) { new_index = curr_index; point_list[new_index].Set (new_value.X, new_value.Y); } // // Remove all the keys from the spline // for (int index = 0; index < count; index ++) { spline.Remove_Key (0); } // // Add the new 'sorted' keys back into the spline // float time; float key; float last_time = -9999999999.0F; for (index = 0; index < count; index ++) { time = point_list[index].X; key = point_list[index].Y; if (time == last_time) { spline.Add_Key(key,time + WWMATH_EPSILON); } else { spline.Add_Key(key,time); } last_time = time; } Repaint_Graph (); delete [] point_list; return new_index; } /////////////////////////////////////////////////////////////////////// // // Point_To_Value // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Point_To_Value (const CPoint &point, Vector2 *value) { CRect graph_area; Get_Graph_Rect (graph_area); CPoint graph_pt; graph_pt.x = point.x - graph_area.left; graph_pt.y = point.y - graph_area.top; float percent_x = (float)graph_pt.x / (float)graph_area.Width (); float percent_y = (float)graph_pt.y / (float)graph_area.Height (); value->X = m_Min.X + ((m_Max.X - m_Min.X) * percent_x); value->Y = m_Min.Y + ((m_Max.Y - m_Min.Y) * (1.0F - percent_y)); return ; } /////////////////////////////////////////////////////////////////////// // // Value_To_Point // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Value_To_Point (const Vector2 &value, CPoint *point) { CRect graph_area; Get_Graph_Rect (graph_area); float percent_x = (value.X - m_Min.X) / (m_Max.X - m_Min.X); float percent_y = (value.Y - m_Min.Y) / (m_Max.Y - m_Min.Y); point->x = graph_area.left + int(graph_area.Width () * percent_x); point->y = graph_area.top + int(graph_area.Height () * (1.0F - percent_y)); return ; } /////////////////////////////////////////////////////////////////////// // // Get_Graph_Rect // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Get_Graph_Rect (CRect &rect) { CRect view_rect; GetClientRect (&view_rect); rect.left = 75; rect.right = view_rect.Width () - 25; rect.top = 25; rect.bottom = view_rect.Height () - 50; return ; } /////////////////////////////////////////////////////////////////////// // // OnInitialUpdate // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnInitialUpdate (void) { HDC screen_dc = ::GetDC (NULL); m_Font = CreateFont (-::MulDiv (7, GetDeviceCaps(screen_dc, LOGPIXELSY), 72), 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Small Fonts"); ::ReleaseDC (NULL, screen_dc); CView::OnInitialUpdate(); return ; } /////////////////////////////////////////////////////////////////////// // // Delete_Point // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::Delete_Point (int index) { Curve1DClass &spline = GetDocument ()->Get_Spline (); if (index >= 0 && index < spline.Key_Count ()) { spline.Remove_Key (index); if (index == m_SelPt) { m_SelPt = -1; } if (index == m_DraggingPt) { m_DraggingPt = -1; } Repaint_Graph (); } return ; } /////////////////////////////////////////////////////////////////////// // // OnDelete // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnDelete (void) { if (m_SelPt != -1) { Delete_Point (m_SelPt); } return ; } /////////////////////////////////////////////////////////////////////// // // OnUpdateDelete // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnUpdateDelete (CCmdUI *pCmdUI) { pCmdUI->Enable (m_SelPt != -1); return ; } /////////////////////////////////////////////////////////////////////// // // OnZoomExtents // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnZoomExtents (void) { Curve1DClass &spline = GetDocument ()->Get_Spline (); int count = spline.Key_Count (); if (count > 0) { m_Min.Set (99999.0F, 99999.0F); m_Max.Set (-99999.0F, -99999.0F); float start_time = spline.Get_Start_Time (); float end_time = spline.Get_End_Time (); float time_inc = (end_time - start_time) / 400.0F; float time = start_time; for (int index = 0; index < 400; index ++) { float value; spline.Evaluate (time, &value); m_Min.X = min (m_Min.X, time); m_Min.Y = min (m_Min.Y, value); m_Max.X = max (m_Max.X, time); m_Max.Y = max (m_Max.Y, value); time += time_inc; } float delta_x = m_Max.X - m_Min.X; float delta_y = m_Max.Y - m_Min.Y; m_Min.X -= (delta_x / 100.0F); m_Min.Y -= (delta_y / 100.0F); m_Max.X += (delta_x / 100.0F); m_Max.Y += (delta_y / 100.0F); InvalidateRect (NULL, TRUE); UpdateWindow (); } return ; } /////////////////////////////////////////////////////////////////////// // // OnRButtonDown // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnRButtonDown (UINT nFlags, CPoint point) { if (m_IsZooming == false) { m_ZoomPt = point; m_IsZooming = true; SetCapture (); } CView::OnRButtonDown (nFlags, point); return ; } /////////////////////////////////////////////////////////////////////// // // OnRButtonUp // /////////////////////////////////////////////////////////////////////// void CSimpleGraphView::OnRButtonUp (UINT nFlags, CPoint point) { if (m_IsZooming) { m_IsZooming = false; ::ReleaseCapture (); } CView::OnRButtonUp(nFlags, point); return ; }