From 82b749158d7c45a16905e45aa7ba7097613aa4f8 Mon Sep 17 00:00:00 2001
From: Nick Gasson <nick@nickg.me.uk>
Date: Fri, 12 Aug 2011 18:57:43 +0100
Subject: [PATCH] Work in progress on track graph

---
 include/ITrackGraph.hpp |  27 ++++-
 src/Editor.cpp          | 252 +++++++++++++++++++++++++++++++++++++---
 2 files changed, 265 insertions(+), 14 deletions(-)

diff --git a/include/ITrackGraph.hpp b/include/ITrackGraph.hpp
index 6e595d7..d0ed449 100644
--- a/include/ITrackGraph.hpp
+++ b/include/ITrackGraph.hpp
@@ -20,11 +20,36 @@
 
 #include "IMap.hpp"
 
+namespace graph {
+
+   struct Arc;
+
+   enum NodeType {
+      NODE_ROOT,
+      NODE_STATION,
+      NODE_POINTS
+   };
+   
+   struct Node {
+      unsigned id;
+      NodeType type;
+      vector<Arc> arcs;
+      ITrackSegmentPtr track;
+   };
+
+   struct Arc {
+      unsigned start, end;
+      float length;
+   };
+}
+
 struct ITrackGraph {
    virtual ~ITrackGraph() {}
 
-   // Dump the track graph in DOT format for rendering with Graphviz
    virtual void write_dot_file(const string& file) const = 0;
+   
+   virtual const graph::Node& root() const = 0;
+   virtual const graph::Node& node(unsigned n) const = 0;   
 };
 
 typedef shared_ptr<ITrackGraph> ITrackGraphPtr;
diff --git a/src/Editor.cpp b/src/Editor.cpp
index 27748ec..d1dd0f6 100644
--- a/src/Editor.cpp
+++ b/src/Editor.cpp
@@ -66,16 +66,25 @@ private:
    void build_gui();
    void draw_dragged_track();
    bool draw_track_tile(Point<int> where, track::Direction axis);
-   void draw_dragged_straight(const track::Direction& an_axis, int a_length);
-   void draw_dragged_curve(int x_length, int y_length);
+   void draw_dragged_straight(const track::Direction& axis, int length);
+   void draw_diagonal_straight(const track::Direction& axis, int length);
+   void draw_initial_track();
+   void draw_unconstrained_track(const track::Direction& start_dir);
+   void draw_constrained_track(const track::Direction& start_dir,
+                               const track::Direction& end_dir);
+   void draw_curve(const track::Direction& entry_dir,
+                   const track::Direction& exit_dir);
+   void draw_s_bend(const track::Direction& dir);
    bool can_connect(const Point<int>& a_first_point,
-      const Point<int>& a_second_point) const;
+                    const Point<int>& a_second_point) const;
    bool can_place_track(ITrackSegmentPtr track);
    bool guess_track_dir(const Point<int>& p, track::Direction& d) const;
    void drag_box_bounds(int& x_min, int& x_max, int &y_min, int& y_max) const;
+   void drag_box_size(int& xlen, int& ylen) const;
    void delete_objects();
    void plant_trees();
    void save();
+   bool is_diagonal(const track::Direction& dir) const;
       
    IMapPtr map;
    
@@ -172,6 +181,15 @@ void Editor::drag_box_bounds(int& x_min, int& x_max, int &y_min, int& y_max) con
    y_max = max(drag_begin.y, drag_end.y); 
 }
 
+void Editor::drag_box_size(int& xlen, int& ylen) const
+{
+   int xmin, xmax, ymin, ymax;
+   drag_box_bounds(xmin, xmax, ymin, ymax);
+
+   xlen = abs(xmax - xmin) + 1;
+   ylen = abs(ymax - ymin) + 1;
+}
+
 // Render the next frame
 void Editor::display(IGraphicsPtr a_context) const
 {
@@ -327,15 +345,31 @@ bool Editor::draw_track_tile(Point<int> where, track::Direction axis)
 
 // Special case where the user drags a rectangle of width 1
 // This just draws straight track along the rectangle
-void Editor::draw_dragged_straight(const track::Direction& an_axis, int a_length)
+void Editor::draw_dragged_straight(const track::Direction& axis, int length)
+{
+   Point<int> where = drag_begin;
+
+   for (int i = 0; i < length; i++) {
+      draw_track_tile(where, axis);
+
+      where.x += axis.x;
+      where.y += axis.z;
+   }
+}
+
+// The user draws a square and one of the corners meets a diagnonal
+// track segment
+void Editor::draw_diagonal_straight(const track::Direction& axis, int length)
 {
    Point<int> where = drag_begin;
 
-   for (int i = 0; i < a_length; i++) {
-      draw_track_tile(where, an_axis);
+   for (int i = 0; i < length; i++) {
+      VectorI delta = make_vector(axis.x, axis.z, 0);
+      ITrackSegmentPtr track = make_spline_track(delta, axis, axis);
+      map->set_track_at(where, track);
 
-      where.x += an_axis.x;
-      where.y += an_axis.z;
+      where.x += axis.x;
+      where.y += axis.z;
    }
 }
 
@@ -357,17 +391,191 @@ bool Editor::can_place_track(ITrackSegmentPtr track)
    return true;
 }
 
+bool Editor::is_diagonal(const track::Direction& dir) const
+{
+   return !(dir == axis::X || dir == axis::Y
+            || dir == -axis::X || dir == -axis::Y);
+}
+
+// The direction of neither endpoint is known
+void Editor::draw_initial_track()
+{
+   debug() << __func__ << ": drag_begin=" << drag_begin
+           << " drag_end=" << drag_end;
+
+   int xmin, xmax, ymin, ymax, xlen, ylen;
+   drag_box_bounds(xmin, xmax, ymin, ymax);
+   drag_box_size(xlen, ylen);
+   
+   if (xlen == 1)
+      draw_dragged_straight(drag_begin.y > drag_end.y ? -axis::Y : axis::Y,
+                            ylen);
+   else if (ylen == 1)
+      draw_dragged_straight(drag_begin.x > drag_end.x ? -axis::X : axis::X,
+                            xlen);
+   else if (is_shift_down && xlen == ylen)
+      warn() << "draw_diagonal_straight";
+   else
+      warn() << "cannot infer track";
+}
+
+void Editor::draw_curve(const track::Direction& entry_dir,
+                        const track::Direction& exit_dir)
+{
+   int xmin, xmax, ymin, ymax, xlen, ylen;
+   drag_box_bounds(xmin, xmax, ymin, ymax);
+   drag_box_size(xlen, ylen);
+
+   VectorI delta = make_vector(drag_end.x - drag_begin.x,
+                               drag_end.y - drag_begin.y,
+                               0);
+   ITrackSegmentPtr curve = make_spline_track(delta, entry_dir, exit_dir);
+   map->set_track_at(drag_begin, curve);
+}
+
+void Editor::draw_s_bend(const track::Direction& dir)
+{
+   draw_curve(dir, dir);
+}
+
+// The direction of the start is known but not the end
+void Editor::draw_unconstrained_track(const track::Direction& start_dir)
+{   
+   debug() << __func__ << ": drag_begin=" << drag_begin
+           << " drag_end=" << drag_end << " start_dir=" << start_dir;
+
+   int xmin, xmax, ymin, ymax, xlen, ylen;
+   drag_box_bounds(xmin, xmax, ymin, ymax);
+   drag_box_size(xlen, ylen);
+
+   bool start_is_ortho = (start_dir == axis::X || start_dir == axis::Y
+                          || start_dir == -axis::X || start_dir == -axis::Y);
+   bool could_be_curve = (xlen >= 3 && ylen >= 3);
+   bool could_be_90_curve = (start_is_ortho && could_be_curve
+                             && !is_shift_down);
+   bool could_be_45_curve = (could_be_curve && xlen != ylen
+                             && (!start_is_ortho || is_shift_down));
+   bool could_be_s_bend = (start_is_ortho && xlen != ylen && !is_shift_down);
+   bool could_be_straight = ((start_is_ortho && (xlen == 1 || ylen == 1))
+                             || (!start_is_ortho && xlen == ylen
+                                 && !is_shift_down));
+
+   if (could_be_straight) {
+      if (start_is_ortho)
+         draw_dragged_straight(start_dir, xlen == 1 ? ylen : xlen);
+      else
+         draw_diagonal_straight(start_dir, xlen);
+   }
+   else if (could_be_90_curve) {
+
+      track::Direction exit_dir;
+      if (start_dir == axis::X || start_dir == -axis::X) {
+         if (drag_end.y < drag_begin.y)
+            exit_dir = -axis::Y;
+         else
+            exit_dir = axis::Y;
+      }
+      else if (start_dir == axis::Y || start_dir == -axis::Y) {
+         if (drag_end.x < drag_begin.x)
+            exit_dir = -axis::X;
+         else
+            exit_dir = axis::X;
+      }
+      else
+         assert(false);
+
+      // Draw straight track until we have a square
+      while (xlen > ylen) {
+         if (start_dir == axis::X || start_dir == -axis::X) {
+            draw_track_tile(drag_begin, axis::X);
+            drag_begin += make_point(start_dir.x, 0);
+         }
+         else if (start_dir == axis::Y || start_dir == -axis::Y) {
+            draw_track_tile(drag_end, axis::X);
+            drag_end -= make_point(exit_dir.x, 0);
+         }
+         xlen--;
+      }
+      while (ylen > xlen) {
+         if (start_dir == axis::Y || start_dir == -axis::Y) {
+            draw_track_tile(drag_begin, axis::Y);
+            drag_begin += make_point(0, start_dir.z);
+         }
+         else if (start_dir == axis::X || start_dir == -axis::X) {
+            draw_track_tile(drag_end, axis::Y);
+            drag_end -= make_point(0, exit_dir.z);
+         }
+         ylen--;
+      }
+      
+      draw_curve(start_dir, exit_dir);
+   }
+   else if (could_be_45_curve) {
+      
+      track::Direction exit_dir;
+      if (start_dir == axis::X || start_dir == -axis::X) {
+         if (drag_end.y < drag_begin.y)
+            exit_dir = make_vector(start_dir.x, 0, -1);
+         else
+            exit_dir = make_vector(start_dir.x, 0, 1);
+      }
+      else if (start_dir == axis::Y || start_dir == -axis::Y) {
+         if (drag_end.x < drag_begin.x)
+            exit_dir = make_vector(-1, 0, start_dir.z);
+         else
+            exit_dir = make_vector(1, 0, start_dir.z);
+      }
+      else
+         assert(false);
+
+      draw_curve(start_dir, exit_dir);
+   }
+   else if (could_be_s_bend) {
+      draw_s_bend(start_dir);
+   }
+   else
+      warn() << "cannot infer track";
+      
+}
+
+// The direction of both endpoints is known
+void Editor::draw_constrained_track(const track::Direction& start_dir,
+                                    const track::Direction& end_dir)
+{
+   debug() << __func__ << ": drag_begin=" << drag_begin
+           << " drag_end=" << drag_end << " start_dir=" << start_dir
+           << " end_dir=" << end_dir;
+
+   int xlen, ylen;
+   drag_box_size(xlen, ylen);
+
+   bool start_is_ortho = (start_dir == axis::X || start_dir == axis::Y
+                          || start_dir == -axis::X || start_dir == -axis::Y);
+
+   bool is_straight = ((start_dir == end_dir || start_dir == -end_dir)
+                       && ((start_is_ortho && (xlen == 1 || ylen == 1))
+                           || (!start_is_ortho && xlen == ylen)));
+
+   if (is_straight) {
+      if (xlen == 1 || ylen == 1)
+         draw_dragged_straight(start_dir, max(xlen, ylen));
+      else
+         draw_diagonal_straight(start_dir, xlen);
+   }
+   else {
+      warn() << "TODO";
+   }
+}
+
 // Called when the user has finished dragging a rectangle for track
 // Connect the beginning and end up in the simplest way possible
 void Editor::draw_dragged_track()
 {
    track::Direction straight;  // Orientation for straight track section
 
-   int xmin, xmax, ymin, ymax;
+   int xmin, xmax, ymin, ymax, xlen, ylen;
    drag_box_bounds(xmin, xmax, ymin, ymax);
-   
-   int xlen = abs(xmax - xmin) + 1;
-   int ylen = abs(ymax - ymin) + 1;
+   drag_box_size(xlen, ylen);
 
    // Try to merge the start and end directly
    const track::Direction merge_axis =
@@ -393,6 +601,23 @@ void Editor::draw_dragged_track()
    bool start_was_guess = !guess_track_dir(drag_begin, start_dir);
    bool end_was_guess = !guess_track_dir(drag_end, end_dir);
 
+   if (start_was_guess) {
+      if (end_was_guess)
+         draw_initial_track();
+      else {
+         swap(drag_begin, drag_end);
+         draw_unconstrained_track(end_dir);
+      }         
+   }
+   else {
+      if (end_was_guess)
+         draw_unconstrained_track(start_dir);
+      else
+         draw_constrained_track(start_dir, end_dir);
+   }
+
+   
+#if 0
    end_dir = -end_dir;
 
    // If we have to guess both orientations use a heuristic to decide
@@ -447,7 +672,7 @@ void Editor::draw_dragged_track()
       }
    }
 
-#if 0
+#if 1
    debug();
    debug() << "start_was_guess=" << start_was_guess
            << " end_was_guess=" << end_was_guess;
@@ -483,6 +708,7 @@ void Editor::draw_dragged_track()
       if (can_place_track(track))
          map->set_track_at(where, track);
    }
+#endif
 }
 
 // Delete all objects in the area selected by the user
-- 
2.39.5