13 changed files with 1161 additions and 124 deletions
@ -0,0 +1,723 @@
@@ -0,0 +1,723 @@
|
||||
#include "TargetWindow.h" |
||||
|
||||
#define PT_PATH 0 |
||||
|
||||
TargetWindow::TargetWindow(int w, int h, GBCSolution* Gsln, GBCSolution* GMEM1, GBCSolution* GMEM2, GBCSolution* GMEM3) : Fl_Window(w,h,"Solution Visualization Tools"){ |
||||
|
||||
// Store a pointer to the calling solution.
|
||||
gsln=Gsln; |
||||
mem1=GMEM1; |
||||
mem2=GMEM2; |
||||
mem3=GMEM3; |
||||
|
||||
x_click=0; |
||||
y_click=0; |
||||
|
||||
begin(); |
||||
this->size_range(0,0,1600,1600); |
||||
|
||||
// Put a simple menu, for saving or closing the window.
|
||||
menu = new Fl_Menu_Bar(0,0,w,25,"MENU"); |
||||
|
||||
Fl_Menu_Item items[]= { |
||||
{"&File",0,0,0,FL_SUBMENU}, |
||||
{"Save as..",0,0,0,FL_SUBMENU | FL_MENU_INACTIVE}, |
||||
{"PNG (.png)",0,(Fl_Callback*)cb_nothing,this,0}, |
||||
{"JPEG (.jpg)",0,(Fl_Callback*)cb_nothing,this,0}, |
||||
{"Bitmap (.bmp)",0,(Fl_Callback*)cb_nothing,this,0}, |
||||
{"Adobe PDF (.pdf)",0,(Fl_Callback*)cb_nothing,this,0}, |
||||
{0}, |
||||
{"&Close",0,(Fl_Callback*)cb_Close,this,0}, |
||||
{0}, |
||||
{0} |
||||
}; |
||||
|
||||
menu->copy(items); |
||||
menu->down_box(FL_NO_BOX); |
||||
|
||||
// Put in a range slider for selecting the chart's range.
|
||||
sl_x = new Fl_Value_Slider(10,30,270,30,"Range (yds)"); |
||||
sl_x->align(FL_ALIGN_RIGHT); |
||||
sl_x->value(200); |
||||
sl_x->minimum(30); |
||||
sl_x->maximum(gsln->MaxRows()); |
||||
sl_x->type(FL_HOR_NICE_SLIDER); |
||||
sl_x->callback(cb_slUpdate,this); |
||||
sl_x->precision(0); |
||||
sl_x->step(10); |
||||
|
||||
btPerspective=new Fl_Check_Button(380,35,20,20,"Perspective"); |
||||
btPerspective->value(1); |
||||
btPerspective->callback(cb_Perspective,this); |
||||
btPerspective->tooltip("Apply perspective to bullet path"); |
||||
|
||||
Fl_Menu_Item guideitems[]={ |
||||
{"None",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"1,5 MIL",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"1-5 MIL",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"1-10 MIL",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"1-5,10 MIL",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"2,10 MOA",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"2,4,6,8,10 MOA",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"2-20 MOA",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"2-10,20 MOA",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"1-5 Degrees",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{"5,10,15 Degree",0,(Fl_Callback*)cb_Guides, this, 0}, |
||||
{0} |
||||
}; |
||||
cScopeGuides = new Fl_Choice(330,65,150,30,"Scope Guides"); |
||||
cScopeGuides->copy(guideitems); |
||||
cScopeGuides->value(1); |
||||
|
||||
btMem1 = new Fl_Check_Button(490,30,20,20,"Memory 1"); |
||||
btMem2 = new Fl_Check_Button(490,50,20,20,"Memory 2"); |
||||
btMem3 = new Fl_Check_Button(490,70,20,20,"Memory 3"); |
||||
btMem1->align(FL_ALIGN_RIGHT); |
||||
btMem2->align(FL_ALIGN_RIGHT); |
||||
btMem3->align(FL_ALIGN_RIGHT); |
||||
btMem1->labelcolor(FL_RED); |
||||
btMem2->labelcolor(FL_DARK_GREEN); |
||||
btMem3->labelcolor(FL_BLACK); |
||||
btMem1->callback(cb_Mem1,this); |
||||
btMem2->callback(cb_Mem2,this); |
||||
btMem3->callback(cb_Mem2,this); |
||||
|
||||
if (mem1==NULL) btMem1->deactivate(); |
||||
if (mem2==NULL) btMem2->deactivate(); |
||||
if (mem3==NULL) btMem3->deactivate(); |
||||
|
||||
if (mem1!=NULL) btMem1->tooltip(mem1->Name()); |
||||
if (mem2!=NULL) btMem2->tooltip(mem2->Name()); |
||||
if (mem3!=NULL) btMem3->tooltip(mem3->Name()); |
||||
|
||||
#define TARGET_COUNT 15 |
||||
Fl_Menu_Item plottypeitems[TARGET_COUNT+1]={ |
||||
{"1/2\" Rings",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"1\" Rings",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"2\" Rings",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
|
||||
{"ISSF 10m Air Pistol",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"ISSF 10m Air Rifle",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"ISSF 25m Rapid Fire Pistol",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"ISSF 25m Pistol",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"ISSF 50m Pistol",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
|
||||
{"ISSF 50m Rifle",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"ISSF 300m Rifle",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
|
||||
{"NRA 200 yard reduced for 100yd",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"NRA 200 yard",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"NRA 300 yard",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"NRA 600 yard",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{"NRA 800 yard+",0,(Fl_Callback*)cb_TargetType, this, 0}, |
||||
{0} |
||||
}; |
||||
cTargetType = new Fl_Choice(60,65,160,30,"Target"); |
||||
cTargetType->copy(plottypeitems); |
||||
cTargetType->value(1); |
||||
|
||||
end(); |
||||
show(); |
||||
|
||||
|
||||
} |
||||
|
||||
TargetWindow::~TargetWindow(void){ |
||||
|
||||
delete menu; |
||||
delete sl_x; |
||||
delete btMem1; |
||||
delete btMem2; |
||||
delete btMem3; |
||||
delete cScopeGuides; |
||||
delete cTargetType; |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
void TargetWindow::draw(void){ |
||||
|
||||
// Let the window draw itself first.
|
||||
Fl_Window::draw(); |
||||
|
||||
PlotPath(PT_PATH); |
||||
} |
||||
|
||||
int TargetWindow::handle(int evt){ |
||||
|
||||
if (evt==FL_PUSH && Fl::event_button1()){ |
||||
x_click=Fl::event_x(); |
||||
y_click=Fl::event_y(); |
||||
this->redraw(); |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
return Fl_Window::handle(evt); |
||||
|
||||
} |
||||
|
||||
|
||||
/*
|
||||
PlotMem() |
||||
|
||||
Plot the requested data from a given GBCSolution (the current solution or a memory) |
||||
|
||||
mode = 0: Path, 1: Energy, 2: Velocity, 3: Wind Drift |
||||
|
||||
*/ |
||||
|
||||
void TargetWindow::PlotMem(GBCSolution* mem, int mode){ |
||||
|
||||
double x1,x2; |
||||
double y1,y2; |
||||
int px1, px2, py1,py2; |
||||
int range; |
||||
|
||||
double t_range=sl_x->value(); |
||||
int perspective=btPerspective->value()==1; |
||||
int start = perspective ? 20 : 0; |
||||
|
||||
x1=mem->GetWindage(start); |
||||
y1=mem->GetPath(start); |
||||
|
||||
// apply perspective
|
||||
if (perspective) { |
||||
y1*=t_range / (double)(start+1); |
||||
x1*=t_range / (double)(start+1); |
||||
} |
||||
|
||||
int m=this->gsln->MaxRows(); |
||||
|
||||
for (int n=start+1;n<=t_range && n<m ;n++){ |
||||
x2=mem->GetWindage(n); |
||||
y2=mem->GetPath(n); |
||||
|
||||
// apply perspective
|
||||
if (perspective && n<t_range) { |
||||
y2*=t_range / (double)(n+1); |
||||
x2*=t_range / (double)(n+1); |
||||
} |
||||
|
||||
// Translate the x,y values into scaled pixels.
|
||||
px1=(int)(x0+x_scale*(double)x1); |
||||
px2=(int)(x0+x_scale*(double)x2); |
||||
|
||||
py1=(int)((y0)-(double)y_scale*(double)y1); |
||||
py2=(int)((y0)-(double)y_scale*(double)y2); |
||||
|
||||
if (n>50) { |
||||
if (py2>=ymax) break; |
||||
if (px2>=xmax) break; |
||||
} |
||||
|
||||
// Plot the points.
|
||||
if (px1>xmin && px1<xmax && py1<ymax && py1>ymin) |
||||
fl_line(px1,py1,px2,py2); |
||||
|
||||
x1=x2; |
||||
y1=y2; |
||||
} |
||||
|
||||
// draw a "bullet hole" at the impact point
|
||||
//fl_color(FL_YELLOW);
|
||||
double r=0.300/2; // should use half caliber here
|
||||
double r2; |
||||
fl_pie(px2 - (r*x_scale), py2-(r*y_scale), r*2*x_scale, r*2*y_scale, 0, 360); |
||||
|
||||
// and a 1-MOA ring (predicted impact variance)
|
||||
// plus 0.1 MOA for every 100 yds over 100 yds (rough spin drift allowance)
|
||||
r=(1.0 * t_range / 100.0)/2; |
||||
r2=r; |
||||
if (t_range > 100) r2 += (t_range - 100)/1000; |
||||
|
||||
fl_color(fl_gray_ramp(FL_NUM_GRAY * 2 / 4)); |
||||
fl_arc(px2 - (r2*x_scale), py2-(r*y_scale), r2*2*x_scale, r*2*y_scale, 0, 360); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
SetupPlot() |
||||
|
||||
Calculate some common parameters for the plots |
||||
This code was common to all plots (Path, Energy, Velocity) and was consolidated here |
||||
|
||||
Requires x_range and y_range to be set before calling |
||||
*/ |
||||
|
||||
int get_ticks(int range) { |
||||
if (range < 100) return 10; |
||||
if (range < 200) return 20; |
||||
if (range < 400) return 50; |
||||
if (range < 800) return 100; |
||||
if (range < 1600) return 200; |
||||
if (range < 3200) return 500; |
||||
if (range < 6400) return 1000; |
||||
if (range < 12800) return 2000; |
||||
if (range < 25600) return 4000; |
||||
if (range < 51200) return 8000; |
||||
if (range < 102400) return 16000; |
||||
if (range < 204800) return 32000; |
||||
return 64000; |
||||
} |
||||
|
||||
void TargetWindow::SetupPlot(void){ |
||||
|
||||
int w = this->w(); |
||||
int h = this->h(); |
||||
|
||||
xmin = 5; |
||||
xmax = w-5; |
||||
ymin = 100; |
||||
ymax = h-5; |
||||
|
||||
x_scale = (double)(xmax-xmin) / (double)x_range; |
||||
y_scale = (double)(ymax-ymin) / (double)y_range; |
||||
|
||||
x_ticks=get_ticks(x_range); |
||||
y_ticks=get_ticks(y_range); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
DrawPlotBackground() |
||||
|
||||
Draw the graph background, axis, scale etc |
||||
Code was common to all plots and has been consolidated here |
||||
|
||||
Requires SetupPlot to have been called and y0 set first |
||||
*/ |
||||
|
||||
void TargetWindow::DrawPlotBackground(int mode, const char *ylabel){ |
||||
|
||||
// Draw the x-axis.
|
||||
fl_color(FL_BLACK); |
||||
fl_line_style(FL_SOLID,2); |
||||
fl_line(xmin,y0,xmax,y0); |
||||
|
||||
// Draw the y-axis
|
||||
fl_line(x0,ymin,x0,ymax); |
||||
|
||||
double px; |
||||
fl_color(FL_BLACK); |
||||
fl_line_style(FL_SOLID,1); |
||||
char txt[64]; |
||||
int r=0; |
||||
double txtw=0; |
||||
|
||||
int tick_top=ymax; |
||||
if (mode != PT_PATH) tick_top=y0; |
||||
|
||||
// Draw X gridlines every x_ticks yds and label them.
|
||||
if (xmin<0) { |
||||
for (px=x0;px>=xmin;px-=((double)x_ticks*x_scale)){ |
||||
//fl_line((int)px,y_bottom,(int)px,y_top);
|
||||
fl_line_style(FL_DOT,1); |
||||
fl_line((int)px,(int)ymin,(int)px,tick_top); |
||||
sprintf(txt,"%d",r*x_ticks); |
||||
if (r%2==0 && r>0) { |
||||
txtw=fl_width(txt); |
||||
fl_line_style(FL_SOLID,1); |
||||
fl_line((int)px,y0,(int)px,y0+8); |
||||
fl_draw(txt,(int)((int)px-(txtw/2)),y0+10+fl_height()); |
||||
} |
||||
r++; |
||||
} |
||||
} |
||||
|
||||
// Draw X gridlines every x_ticks yds and label them.
|
||||
for (px=x0;px<=xmax;px+=((double)x_ticks*x_scale)){ |
||||
//fl_line((int)px,y_bottom,(int)px,y_top);
|
||||
fl_line_style(FL_DOT,1); |
||||
fl_line((int)px,(int)ymin,(int)px,tick_top); |
||||
sprintf(txt,"%d",r*x_ticks); |
||||
if (r%2==0 && r>0) { |
||||
txtw=fl_width(txt); |
||||
fl_line_style(FL_SOLID,1); |
||||
fl_line((int)px,y0,(int)px,y0+8); |
||||
fl_draw(txt,(int)((int)px-(txtw/2)),y0+10+fl_height()); |
||||
} |
||||
r++; |
||||
} |
||||
|
||||
// Draw Y hashmarks every 1 y_tick going up.
|
||||
double py; |
||||
r=0; |
||||
for (py=y0;py>=ymin;py-=((double)y_ticks*y_scale)){ |
||||
if (r>0){ |
||||
fl_line(xmin,(int)py,xmax,(int)py); |
||||
sprintf(txt,"+%d%s",r*y_ticks, ylabel); |
||||
fl_draw(txt,xmin+10,(int)(py+fl_height()/2)); |
||||
} |
||||
r++; |
||||
} |
||||
|
||||
// Draw Y hashmarks every 1 y_tick going down
|
||||
if (mode==PT_PATH) { |
||||
r=0; |
||||
for (py=y0;py<=ymax;py+=((double)y_ticks*y_scale)){ |
||||
if (r>0){ |
||||
fl_line(xmin,(int)py,xmax,(int)py); |
||||
sprintf(txt,"-%d%s",r*y_ticks, ylabel); |
||||
if (py+fl_height()/2<ymax) fl_draw(txt,xmin+10,(int)(py+fl_height()/2)); |
||||
} |
||||
r++; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
void TargetWindow::DrawPlotData(int mode){ |
||||
|
||||
fl_color(FL_BLUE); |
||||
fl_line_style(FL_SOLID,1); |
||||
PlotMem(this->gsln, mode); |
||||
|
||||
if (btMem1->value()==1){ |
||||
fl_color(FL_RED); |
||||
fl_line_style(FL_SOLID,1); |
||||
PlotMem(mem1, mode); |
||||
} |
||||
if (btMem2->value()==1){ |
||||
fl_color(FL_GREEN); |
||||
fl_line_style(FL_SOLID,1); |
||||
PlotMem(mem2, mode); |
||||
} |
||||
if (btMem3->value()==1){ |
||||
fl_color(FL_BLACK); |
||||
fl_line_style(FL_SOLID,1); |
||||
PlotMem(mem3, mode); |
||||
} |
||||
|
||||
if (x_click>xmin && x_click<xmax && y_click>ymin && y_click<ymax){ |
||||
// If the user clicks, show the coordinates they clicked on.
|
||||
double click_label_x= ((x_click)-x0)/x_scale; |
||||
double click_label_y= (y0-y_click)/y_scale; |
||||
char lbl_point[20]; |
||||
sprintf(lbl_point,"(%.2f,%.2f)",click_label_x, click_label_y); |
||||
fl_color(FL_RED); |
||||
fl_draw(lbl_point,x_click+5, y_click-5); |
||||
fl_rectf(x_click-2, y_click-2,4,4); |
||||
} |
||||
|
||||
// Draw the frame around it last to make it all pretty.
|
||||
fl_frame("aaaa",5,100,w()-10,h()-105); |
||||
|
||||
} |
||||
|
||||
|
||||
void TargetWindow::PlotPath(int ptype){ |
||||
|
||||
// We need to find the max and min y-values to determine our y scale and y-ticks
|
||||
double _miny=0; |
||||
double _maxy=0; |
||||
double _minx=0; |
||||
double _maxx=0; |
||||
double minsize=20; |
||||
|
||||
switch (cTargetType->value()) { |
||||
case 0: minsize=6; |
||||
break; |
||||
case 1: minsize=12; |
||||
break; |
||||
case 2: minsize=12; |
||||
break; |
||||
case 3: minsize=5; |
||||
break; |
||||
case 4: minsize=1.5; |
||||
break; |
||||
case 5: |
||||
case 6: |
||||
case 7: minsize=10; |
||||
break; |
||||
case 8: minsize=5; |
||||
break; |
||||
} |
||||
|
||||
|
||||
for (int e=0;e<sl_x->value();e++){ |
||||
double v=gsln->GetPath(e); |
||||
if (v > _maxy) _maxy=v; |
||||
if (v < _miny) _miny=v; |
||||
v=gsln->GetWindage(e); |
||||
if (v > _maxx) _maxx=v; |
||||
if (v < _minx) _minx=v; |
||||
} |
||||
|
||||
if (_minx > -minsize) _minx=-minsize; |
||||
else _minx *= 1.05; |
||||
if (_maxx < minsize) _maxx=minsize; |
||||
else _maxx *= 1.05; |
||||
x_range= _maxx - _minx; |
||||
|
||||
_miny*=1.1; |
||||
if (_miny > -minsize) _miny=-minsize; |
||||
else _miny*=1.05; |
||||
if (_maxy < minsize) _maxy=minsize; |
||||
else _maxy*=1.05; |
||||
|
||||
if (_maxy < fabs(_miny)/3) _maxy = fabs(_miny)/3; |
||||
else if (fabs(_miny) < (_maxy/3)) _miny = -_maxy/3; |
||||
|
||||
y_range = _maxy - _miny; |
||||
|
||||
// keep a fixed aspect ratio, and keep the target roughly central
|
||||
if (y_range < x_range) y_range=x_range; |
||||
if (x_range < y_range) x_range=y_range; |
||||
|
||||
t_range = (int)sl_x->value(); |
||||
|
||||
SetupPlot(); |
||||
y0 = ymax + (_miny * y_scale); |
||||
x0 = xmin - (_minx * x_scale); |
||||
|
||||
// Now do our custom drawing.
|
||||
fl_draw_box(FL_FLAT_BOX,xmin,ymin,w()-10,h()-ymin-5,FL_WHITE); |
||||
|
||||
fl_clip(5,100,w()-10,h()-105); |
||||
fl_line_style(FL_SOLID,1); |
||||
|
||||
// Draw the score circles at 1 inch intervals
|
||||
double r, aimring, *rings; |
||||
int i, score, aimring_drawn=0; |
||||
char txt[10]; |
||||
#define mm(x) (x/25.4) |
||||
double aimrings[TARGET_COUNT]= |
||||
{ 3, 6, 12, // 1/2, 1, 2"
|
||||
mm(59.5), mm(30.5), mm(500), mm(200), mm(200), // ISSF Pistol
|
||||
mm(112.4), mm(600), // ISSF Rifle
|
||||
6.35, 13, 19, 36, 44 // NRA
|
||||
}; |
||||
double rings_data[TARGET_COUNT][13] = { |
||||
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0 }, // 1/2" rings
|
||||
{ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 0 }, // 1" rings
|
||||
{ 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 0 }, // 2" rings
|
||||
|
||||
// ISSF 10m Air Pistol - 11.5mm 10 ring, increasing by 16mm
|
||||
{ mm(6.5), mm(11.5), mm(27.5), mm(43.5), mm(59.5), mm(75.5), mm(91.5), mm(107.5), mm(123.5), mm(139.5), mm(155), 0 }, |
||||
// ISSF 10m Air Rifle
|
||||
{ -0.01, mm(0.5), mm(5.5), mm(10.5), mm(15.5), mm(20.5), mm(25.5), mm(30.5), mm(35.5), mm(40.5), mm(45.5), 0 }, |
||||
// ISSF 25m Rapid Pistol - 100mm 10 ring, increasing by 100mm
|
||||
{ mm(50), mm(100), mm(180), mm(260), mm(340), mm(420), mm(500), 0 }, |
||||
// ISSF 25m Precision Pistol - 50mm 10 ring, increasing by 50mm
|
||||
{ mm(25), mm(50), mm(100), mm(150), mm(200), mm(250), mm(300), mm(350), mm(400), mm(450), mm(500), 0 }, |
||||
// ISSF 50m Precision Pistol - 50mm 10 ring, increasing by 50mm
|
||||
{ mm(25), mm(50), mm(100), mm(150), mm(200), mm(250), mm(300), mm(350), mm(400), mm(450), mm(500), 0 }, |
||||
|
||||
// ISSF 50m Rifle
|
||||
{ mm(5.2), mm(10.4), mm(26.4), mm(42.4), mm(58.4), mm(74.4), mm(90.4), mm(106.4), mm(122.4), mm(138.4), mm(154.4), 0 }, |
||||
// ISSF 300m Rifle
|
||||
{ mm(50), mm(100), mm(200), mm(300), mm(400), mm(500), mm(600), mm(700), mm(800), mm(900), mm(1000), 0 }, |
||||
|
||||