[dasher: 15/28] Game mode: Draw brachistochrone if target offscreen



commit f672eb9df54718008d903017c0dd062a714d0f59
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Tue Aug 3 16:46:19 2010 +0100

    Game mode: Draw brachistochrone if target offscreen
    
    Adding CDasherView::DasherSpaceArc. Also (unused, old) DrawHelperArrow

 Src/DasherCore/DasherView.h         |    2 +
 Src/DasherCore/DasherViewSquare.cpp |   32 ++++++++++--
 Src/DasherCore/DasherViewSquare.h   |   12 ++++-
 Src/DasherCore/GameModule.cpp       |   89 ++++++++++++++++++++++++++++++++--
 Src/DasherCore/GameModule.h         |    8 +++-
 5 files changed, 129 insertions(+), 14 deletions(-)
---
diff --git a/Src/DasherCore/DasherView.h b/Src/DasherCore/DasherView.h
index dbcd8a2..66b9a1b 100644
--- a/Src/DasherCore/DasherView.h
+++ b/Src/DasherCore/DasherView.h
@@ -132,6 +132,8 @@ public:
   ///Draw a straight line in Dasher-space - which may be curved on the screen...
   void DasherSpaceLine(myint x1, myint y1, myint x2, myint y2, int iWidth, int iColour);
   
+  virtual void DasherSpaceArc(myint cy, myint r, myint x1, myint y1, myint x2, myint y2, int iColour, int iLineWidth)=0;
+
   ///
   /// Draw a polyline specified in Dasher co-ordinates
   ///
diff --git a/Src/DasherCore/DasherViewSquare.cpp b/Src/DasherCore/DasherViewSquare.cpp
index 89e4092..e777ca2 100644
--- a/Src/DasherCore/DasherViewSquare.cpp
+++ b/Src/DasherCore/DasherViewSquare.cpp
@@ -361,7 +361,7 @@ void CDasherViewSquare::Circle(myint Range, myint y1, myint y2, int fCol, int oC
   } else {
     Dasher2Screen(x2=0,y2,p.x,p.y);
   }
-  CircleTo(cy,r,y1,x1,y2,x2,p,pts);
+  CircleTo(cy,r,y1,x1,y2,x2,p,pts, 2.0);
   if (iDasherMaxY == y2) {
     Dasher2Screen(0, iDasherMaxX, p.x, p.y);
     pts.push_back(p);
@@ -372,21 +372,41 @@ void CDasherViewSquare::Circle(myint Range, myint y1, myint y2, int fCol, int oC
   delete[] p_array;
 }
 
-void CDasherViewSquare::CircleTo(myint cy, myint r, myint y1, myint x1, myint y3, myint x3, CDasherScreen::point dest, vector<CDasherScreen::point> &pts) {
+void CDasherViewSquare::CircleTo(myint cy, myint r, myint y1, myint x1, myint y3, myint x3, CDasherScreen::point dest, vector<CDasherScreen::point> &pts, double dXMul) {
   myint y2((y1+y3)/2);
-  myint x2(sqrt(double(sq(r)-sq(cy-y2)))*2);
+  myint x2(sqrt(double(sq(r)-sq(cy-y2)))*dXMul);
   CDasherScreen::point mid; //where midpoint of circle/arc should be
   Dasher2Screen(x2, y2, mid.x, mid.y); //(except "midpoint" measured along y axis)
   int lmx=(pts.back().x + dest.x)/2, lmy = (pts.back().y + dest.y)/2; //midpoint of straight line
-  if (dest.y-pts.back().y<2 || abs(mid.x-lmx) + abs(mid.y-lmy)<2) {
+  if (abs(dest.y-pts.back().y)<2 || abs(mid.x-lmx) + abs(mid.y-lmy)<2) {
     //okay, use straight line
     pts.push_back(dest);
   } else {
-    CircleTo(cy, r, y1, x1, y2, x2, mid, pts);
-    CircleTo(cy, r, y2, x2, y3, x3, dest, pts);
+    CircleTo(cy, r, y1, x1, y2, x2, mid, pts, dXMul);
+    CircleTo(cy, r, y2, x2, y3, x3, dest, pts, dXMul);
   }
 }
 #undef sq
+
+void CDasherViewSquare::DasherSpaceArc(myint cy, myint r, myint x1, myint y1, myint x2, myint y2, int iColour, int iLineWidth) {
+  CDasherScreen::point p;
+  //start point
+  Dasher2Screen(x1, y1, p.x, p.y);
+  vector<CDasherScreen::point> pts;
+  pts.push_back(p);
+  //if circle goes behind crosshair, force division into at least two sections, with point of max-x as boundary
+  if (r>CDasherModel::ORIGIN_X) {
+    Dasher2Screen(r, cy, p.x, p.y);
+    CircleTo(cy, r, y1, x1, cy, r, p, pts, 1.0);
+    x1=r; y1=cy;
+  }
+  Dasher2Screen(x2, y2, p.x, p.y);
+  CircleTo(cy, r, y1, x1, y2, x2, p, pts, 1.0);
+  CDasherScreen::point *p_array = new CDasherScreen::point[pts.size()];
+  for (unsigned int i=0; i<pts.size(); i++) p_array[i] = pts[i];
+  Screen()->Polyline(p_array, pts.size(), iLineWidth, iColour);
+}
+
 void CDasherViewSquare::Quadric(myint Range, myint lowY, myint highY, int fillColor, int outlineColour, int lineWidth) {
   static const double RR2=1.0/sqrt(2.0);
   const int midY=(lowY+highY)/2;
diff --git a/Src/DasherCore/DasherViewSquare.h b/Src/DasherCore/DasherViewSquare.h
index 1b1ed39..2384083 100644
--- a/Src/DasherCore/DasherViewSquare.h
+++ b/Src/DasherCore/DasherViewSquare.h
@@ -94,13 +94,23 @@ public:
 
   /// @}
 
+  void DasherSpaceArc(myint cy, myint r, myint x1, myint y1, myint x2, myint y2, int colour, int iLineWidth);
+  
 private:
   ///draw a possibly-truncated triangle given dasher-space coords & accounting for non-linearity
   /// @param x = max dasher-x extent
   /// @param y1, y2 = dasher-y extent along y-axis
   /// @param midy1,midy2 = extent along line of max x (midy1==midy2 => triangle, midy1<midy2 => truncated tri)
   void TruncateTri(myint x, myint y1, myint y2, myint midy1, myint midy2, int fillColor, int outlineColor, int lineWidth);
-  void CircleTo(myint cy, myint r, myint y1, myint x1, myint y3, myint x3, CDasherScreen::point dest, vector<CDasherScreen::point> &pts);
+
+  /// compute screen coords for a circle, centered on y-axis, between two points
+  /// cy, r - dasher coords of center (on y-axis), radius
+  /// x1,y1 - one end-point of arc (dasher coords)
+  /// x2,y2 - other end-point (dasher-coords)
+  /// dest - point (x2,y2) in screen coords
+  /// pts - vector into which to store points; on entry, last element should already be screen-coords of (x1,y1)
+  /// dXMul - multiply x coords (in dasher space) by this (i.e. aspect ratio), for ovals
+  void CircleTo(myint cy, myint r, myint y1, myint x1, myint y3, myint x3, CDasherScreen::point dest, vector<CDasherScreen::point> &pts, double dXMul);
   void Circle(myint Range, myint lowY, myint highY, int fCol, int oCol, int lWidth);
   void Quadric(myint Range, myint lowY, myint highY, int fillColor, int outlineColour, int lineWidth);
   ///draw isoceles triangle, with baseline from y1-y2 along y axis (x=0), and other point at (x,(y1+y2)/2)
diff --git a/Src/DasherCore/GameModule.cpp b/Src/DasherCore/GameModule.cpp
index 7c8071a..b502f71 100644
--- a/Src/DasherCore/GameModule.cpp
+++ b/Src/DasherCore/GameModule.cpp
@@ -105,31 +105,36 @@ void CGameModule::DecorateView(unsigned long lTime, CDasherView *pView, CDasherM
   if (m_dSentenceStartNats == numeric_limits<double>::max())
     m_dSentenceStartNats = pModel->GetNats();
 
-  m_vTargetY.push_back((m_y1+m_y2)/2);
+  m_vTargetY.push_back(m_iTargetY = (m_y1+m_y2)/2);
   
   //draw a line along the y axis
   myint x[2], y[2];
   x[0] = x[1] = -100;
   
+  bool bDrawHelper=true;
+  const int lineWidth(GetLongParameter(LP_LINE_WIDTH));
+  
   if (m_y1 > CDasherModel::MAX_Y) {
     //off the top! draw an arrow pointing up...
     y[1] = CDasherModel::MAX_Y;
     y[0] = y[1] - 400;
-    pView->DasherPolyarrow(x, y, 2, 2, m_iCrosshairColor, 0.1);
   } else if (m_y2 < 0) {
     //off the bottom! draw arrow pointing down...
     y[1] = 0;
     y[0] = 400;
-    pView->DasherPolyarrow(x, y, 2, 2, m_iCrosshairColor, 0.1);
   } else {
     //draw line parallel to that region of y-axis
     y[0] = m_y1; y[1] = m_y2;
-    pView->DasherPolyline(x, y, 2, 2, m_iCrosshairColor);
+    pView->DasherPolyline(x, y, 2, lineWidth, m_iCrosshairColor);
     //and a horizontal arrow pointing to the midpoint
     x[0] = -400;
-    y[0] = y[1] = (m_y1+m_y2)/2;
-    pView->DasherPolyarrow(x, y, 2, 2, m_iCrosshairColor, 0.1);
+    y[0] = y[1] = m_iTargetY;
+    bDrawHelper=false;
   }
+  pView->DasherPolyarrow(x, y, 2, 3*lineWidth, m_iCrosshairColor, 0.2);
+  
+  if (bDrawHelper) DrawBrachistochrone(pView);
+  
   //reset location accumulators ready for next frame
   m_y1 = std::numeric_limits<myint>::min();
   m_y2 = std::numeric_limits<myint>::max();
@@ -154,6 +159,78 @@ void CGameModule::DecorateView(unsigned long lTime, CDasherView *pView, CDasherM
   DrawText(pView);
 }
 
+void CGameModule::DrawBrachistochrone(CDasherView *pView) {
+  // Plot a brachistochrone - the optimal path from the crosshair to the target
+  // this is a circle, passing through both crosshair and target, centered on the y-axis
+  const myint CenterY = ComputeBrachCenter();
+  pView->DasherSpaceArc(CenterY, abs(CenterY - m_iTargetY), CDasherModel::ORIGIN_X, CDasherModel::ORIGIN_Y, 0, m_iTargetY, m_iCrosshairColor, 2*(int)GetLongParameter(LP_LINE_WIDTH));
+}
+
+void CGameModule::DrawHelperArrow(Dasher::CDasherView* pView)
+{
+  // This plots a helpful pointer to the best direction to take to get to the target.
+  // Probably too much floating point maths here, sort later.
+  // Start of line is the crosshair location
+  const int gameColour = 135; //Neon green. (!)
+  const int noOfPoints = 10; // The curve will be made up of 9 straight segments...
+  const myint m_iCrossX(CDasherModel::ORIGIN_X),m_iCrossY(CDasherModel::ORIGIN_Y);
+  
+  struct {
+    myint iTargetY;
+    myint iCenterY;
+  } m_Target;
+  m_Target.iTargetY = m_iTargetY;
+  m_Target.iCenterY = ComputeBrachCenter();
+  myint iX[noOfPoints];
+  myint iY[noOfPoints];
+  myint iLength;
+  
+  // Arrow starts at the cross hairs
+  iX[0] = m_iCrossX;
+  iY[0] = m_iCrossY;
+  
+  myint a = m_iCrossX/5;
+  myint defaultlength = m_iCrossX - a ; 
+  
+  // ... then decide the length of the arrow...
+  myint r = m_Target.iTargetY-m_Target.iCenterY; // radius of our circle (+ or -)
+  
+  if(m_Target.iTargetY < a && m_Target.iCenterY < m_iCrossY-defaultlength/2)
+  {
+    myint x = (myint) sqrt((double)(r*r-pow((double)(m_Target.iCenterY-a),2)));
+    iLength = (myint) sqrt((double)(pow((double)(x-m_iCrossX),2)+pow((double)(a-m_iCrossY),2)));
+  }
+  else if(m_Target.iTargetY > 2*m_iCrossY-a && m_Target.iCenterY > m_iCrossY+defaultlength/2)
+  {
+    myint x = (myint) sqrt((double)(r*r-pow((double)(m_Target.iCenterY+a-2*m_iCrossY),2)));
+    iLength = (myint) sqrt((double)(pow((double)(x-m_iCrossX),2)+pow((double)(a-m_iCrossY),2)));
+  }
+  else
+    iLength = defaultlength;
+  
+  //...then calculate the points required...
+  double angle = ((double)iLength/(double)r)/(double)noOfPoints;
+  
+  for(int n = 1; n < noOfPoints; ++n)
+  {
+    iX[n] = (myint) (cos(angle)*(iX[n-1]) - sin(angle)*(iY[n-1]-m_Target.iCenterY));
+    iY[n] = (myint) (m_Target.iCenterY + sin(angle)*(iX[n-1]) + cos(angle)*(iY[n-1]-m_Target.iCenterY));
+  }
+  //...then plot it.
+  pView->DasherPolyarrow(iX, iY, noOfPoints, GetLongParameter(LP_LINE_WIDTH)*4, gameColour, 1.414);
+  
+}
+
+myint CGameModule::ComputeBrachCenter() {
+  const myint iCrossX(CDasherModel::ORIGIN_X), iCrossY(CDasherModel::ORIGIN_Y);
+  // This formula computes the Dasher Y Coordinate of the center of the circle on which
+  // the dasher brachistochrone lies : iCenterY
+  
+  // It comes from the pythagorean relation: iCrossX^2 + (iCenterY - iCrossY)^2 = r^2
+  // where r is the radius of the circle, r = abs(iTargetY-iCenterY)
+  return 0.5*(double(iCrossX*iCrossX)/double(iCrossY-m_iTargetY)+iCrossY+m_iTargetY);
+}
+
 bool CGameModule::GenerateChunk() {
   m_iLastSym = -1;
   m_vTargetSymbols.clear();
diff --git a/Src/DasherCore/GameModule.h b/Src/DasherCore/GameModule.h
index ecdcac0..8549cc3 100644
--- a/Src/DasherCore/GameModule.h
+++ b/Src/DasherCore/GameModule.h
@@ -67,8 +67,12 @@ protected:
   virtual void ChunkGenerated() {}
   
   /// Called when a node has been populated. Look for Game children.
-  virtual void HandleEvent(CDasherNode *pNode); 
+  virtual void HandleEvent(CDasherNode *pNode);
   
+  void DrawBrachistochrone(Dasher::CDasherView* pView);
+  void DrawHelperArrow(Dasher::CDasherView* pView);
+  myint ComputeBrachCenter();
+    
   /// Called when a node has been output/deleted. Update string (to be/) written.
   virtual void HandleEvent(const CEditEvent *);
   
@@ -133,6 +137,8 @@ private:
   ///Time and nats at which this sentence started
   unsigned long m_ulSentenceStartTime;
   double m_dSentenceStartNats;
+
+  myint m_iTargetY;
   
 /* ---------------------------------------------------------------------
  * Constants



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]