This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/SimpleGraph/SimpleGraphView.cpp

938 lines
21 KiB
C++

/*
** 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 <http://www.gnu.org/licenses/>.
*/
// 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 ;
}