+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2006-2010 Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
#include <cmath>
+#include "block.h"
+#include "catalogue.h"
+#include "driver.h"
+#include "layout.h"
#include "track.h"
+#include "tracktype.h"
using namespace std;
using namespace Msp;
-#include <iostream>
-
namespace Marklin {
-Track::Track(unsigned a):
- art_nr(a),
+Track::Track(Layout &l, const TrackType &t):
+ layout(l),
+ type(t),
+ block(0),
rot(0),
slope(0),
flex(false),
- turnout_id(0),
- sensor_id(0)
-{ }
+ turnout_id(type.is_turnout() ? layout.allocate_turnout_id(type.is_double_address()) : 0),
+ sensor_id(0),
+ links(type.get_endpoints().size()),
+ active_path(0)
+{
+ layout.add_track(*this);
+
+ if(layout.has_driver())
+ layout.get_driver().signal_turnout.connect(sigc::mem_fun(this, &Track::turnout_event));
+
+ for(unsigned paths = type.get_paths(); !(paths&1); ++active_path, paths>>=1) ;
+}
Track::~Track()
{
- for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if(i->link)
- {
- Track *trk=i->link;
- i->link=0;
- trk->break_link(*this);
- }
+ break_links();
+ layout.remove_track(*this);
+}
+
+void Track::set_block(Block *b)
+{
+ if(b && !b->has_track(*this))
+ throw InvalidParameterValue("Track is not in the Block");
+ if(!b && block && block->has_track(*this))
+ throw InvalidState("Track is still in a Block");
+
+ block = b;
+}
+
+Block &Track::get_block() const
+{
+ if(!block)
+ throw InvalidState("No Block");
+
+ return *block;
}
void Track::set_position(const Point &p)
{
- pos=p;
+ pos = p;
}
void Track::set_rotation(float r)
{
- rot=r;
+ rot = r;
while(rot<0)
- rot+=M_PI*2;
+ rot += M_PI*2;
while(rot>M_PI*2)
- rot-=M_PI*2;
+ rot -= M_PI*2;
}
void Track::set_slope(float s)
{
- if(endpoints.size()!=2) return;
+ if(links.size()!=2)
+ return;
+
+ slope = s;
+}
+
+void Track::set_flex(bool f)
+{
+ flex = f;
+}
+
+void Track::check_slope()
+{
+ if(links.size()!=2)
+ return;
+
+ if(links[0] && links[1])
+ {
+ Point epp0 = links[0]->get_endpoint_position(links[0]->get_endpoint_by_link(*this));
+ Point epp1 = links[1]->get_endpoint_position(links[1]->get_endpoint_by_link(*this));
+ pos.z = epp0.z;
+ slope = epp1.z-pos.z;
+ }
+ else
+ {
+ slope = 0;
+ if(links[0])
+ {
+ Point epp = links[0]->get_endpoint_position(links[0]->get_endpoint_by_link(*this));
+ pos.z = epp.z;
+ }
+ else if(links[1])
+ {
+ Point epp = links[1]->get_endpoint_position(links[1]->get_endpoint_by_link(*this));
+ pos.z = epp.z;
+ }
+ }
+}
+
+void Track::set_turnout_id(unsigned i)
+{
+ if(!type.is_turnout())
+ throw InvalidState("Not a turnout");
- slope=s;
- endpoints.back().pos.z=slope;
+ turnout_id = i;
+ layout.create_blocks(*this);
+ layout.update_routes();
+ if(layout.has_driver() && turnout_id)
+ {
+ layout.get_driver().add_turnout(turnout_id);
+ if(type.is_double_address())
+ layout.get_driver().add_turnout(turnout_id+1);
+ }
}
-const Track::Endpoint *Track::get_endpoint_by_link(Track *other) const
+void Track::set_sensor_id(unsigned i)
{
- for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if(i->link==other)
- return &*i;
+ if(type.is_turnout())
+ throw InvalidState("Can't set sensor on a turnout");
- return 0;
+ sensor_id = i;
+ layout.create_blocks(*this);
+ if(layout.has_driver() && sensor_id)
+ layout.get_driver().add_sensor(sensor_id);
}
-Point Track::get_endpoint_position(const Endpoint &ep) const
+void Track::set_active_path(unsigned p)
{
- float c=cos(rot);
- float s=sin(rot);
- return Point(pos.x+c*ep.pos.x-s*ep.pos.y, pos.y+s*ep.pos.x+c*ep.pos.y, pos.z+ep.pos.z);
+ if(!turnout_id)
+ throw InvalidState("Not a turnout");
+ if(!(type.get_paths()&(1<<p)))
+ throw InvalidParameterValue("Invalid path");
+
+ layout.get_driver().set_turnout(turnout_id, p&1);
+ if(type.is_double_address())
+ layout.get_driver().set_turnout(turnout_id+1, p&2);
+ else if(type.get_n_paths()>2)
+ active_path = (active_path&1) | (p&2);
}
-float Track::get_length() const
+int Track::get_endpoint_by_link(Track &other) const
{
- float len=parts.front().length;
- if(parts.front().radius)
- len*=parts.front().radius;
- return len;
+ for(unsigned i=0; i<links.size(); ++i)
+ if(links[i]==&other)
+ return i;
+
+ return -1;
}
-float Track::get_total_length() const
+Point Track::get_endpoint_position(unsigned epi) const
{
- float len=0;
- for(PartSeq::const_iterator i=parts.begin(); i!=parts.end(); ++i)
- {
- float l=i->length;
- if(i->radius)
- l*=i->radius;
- len+=l;
- }
- return len;
+ const vector<TrackType::Endpoint> &eps = type.get_endpoints();
+ if(epi>=eps.size())
+ throw InvalidParameterValue("TrackType::Endpoint index out of range");
+
+ const TrackType::Endpoint &ep = eps[epi];
+
+ float c = cos(rot);
+ float s = sin(rot);
+
+ Point p(pos.x+c*ep.pos.x-s*ep.pos.y, pos.y+s*ep.pos.x+c*ep.pos.y, pos.z);
+ if(eps.size()==2 && epi==1)
+ p.z += slope;
+ return p;
}
-unsigned Track::get_n_routes() const
+float Track::get_endpoint_direction(unsigned epi) const
{
- unsigned n=1;
- for(PartSeq::const_iterator i=parts.begin(); i!=parts.end(); ++i)
- if(i->route>=n)
- n=i->route+1;
- return n;
+ const vector<TrackType::Endpoint> &eps = type.get_endpoints();
+ if(epi>=eps.size())
+ throw InvalidParameterValue("TrackType::Endpoint index out of range");
+
+ const TrackType::Endpoint &ep = eps[epi];
+
+ return rot+ep.dir;
}
-bool Track::snap_to(Track &other, bool link)
+bool Track::snap_to(Track &other, bool link, float limit)
{
- float limit=(link && !flex) ? 1e-6 : 1e-4;
- for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+ if(!limit || link)
{
- Point epp=get_endpoint_position(*i);
- for(EndpointSeq::iterator j=other.endpoints.begin(); j!=other.endpoints.end(); ++j)
+ limit = layout.get_catalogue().get_gauge();
+ if(link && !flex && !other.get_flex())
+ limit /= 10;
+ }
+ limit *= limit;
+
+ const vector<TrackType::Endpoint> &eps = type.get_endpoints();
+ const vector<TrackType::Endpoint> &other_eps = other.get_type().get_endpoints();
+
+ for(unsigned i=0; i<eps.size(); ++i)
+ {
+ Point epp = get_endpoint_position(i);
+
+ for(unsigned j=0; j<other_eps.size(); ++j)
{
- if(j->link)
+ if(other.get_link(j))
continue;
- Point epp2=other.get_endpoint_position(*j);
- float dx=epp2.x-epp.x;
- float dy=epp2.y-epp.y;
- if(dx*dx+dy*dy<limit)
+ Point epp2 = other.get_endpoint_position(j);
+ float dx = epp2.x-epp.x;
+ float dy = epp2.y-epp.y;
+ float dz = epp2.z-epp.z;
+ if(dx*dx+dy*dy<limit && dz*dz<limit)
{
- set_rotation(other.rot+j->rot-i->rot+M_PI);
- set_position(Point(epp2.x-(i->pos.x*cos(rot)-i->pos.y*sin(rot)), epp2.y-(i->pos.y*cos(rot)+i->pos.x*sin(rot)), other.pos.z+j->pos.z-i->pos.z));
+ if(!link || (!flex && !other.get_flex()))
+ {
+ set_rotation(other.rot+other_eps[j].dir-eps[i].dir+M_PI);
+ Point p(epp2.x-(eps[i].pos.x*cos(rot)-eps[i].pos.y*sin(rot)),
+ epp2.y-(eps[i].pos.y*cos(rot)+eps[i].pos.x*sin(rot)),
+ epp2.z);
+ if(eps.size()==2 && i==1)
+ p.z -= slope;
+ set_position(p);
+ }
+
if(link)
{
- if(i->link)
- break_link(*i->link);
- i->link=&other;
- j->link=this;
+ if(links[i])
+ break_link(*links[i]);
+ links[i] = &other;
+ other.links[j] = this;
+ layout.create_blocks(*this);
}
+
return true;
}
}
bool Track::snap(Point &pt, float &d) const
{
- for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+ const vector<TrackType::Endpoint> &eps = type.get_endpoints();
+
+ for(unsigned i=0; i<eps.size(); ++i)
{
- Point epp=get_endpoint_position(*i);
- float dx=pt.x-epp.x;
- float dy=pt.y-epp.y;
+ Point epp = get_endpoint_position(i);
+ float dx = pt.x-epp.x;
+ float dy = pt.y-epp.y;
if(dx*dx+dy*dy<1e-4)
{
- pt=epp;
- d=rot+i->rot;
+ pt = epp;
+ d = rot+eps[i].dir;
return true;
}
}
void Track::break_link(Track &trk)
{
- for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if(i->link==&trk)
+ for(vector<Track *>::iterator i=links.begin(); i!=links.end(); ++i)
+ if(*i==&trk)
{
- i->link=0;
+ *i = 0;
trk.break_link(*this);
+ // XXX Creates the blocks twice
+ layout.create_blocks(*this);
return;
}
}
void Track::break_links()
{
- for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if(i->link)
+ for(vector<Track *>::iterator i=links.begin(); i!=links.end(); ++i)
+ if(Track *trk=*i)
{
- Track *trk=i->link;
- i->link=0;
+ *i = 0;
trk->break_link(*this);
}
}
-void Track::check_slope()
+Track *Track::get_link(unsigned i) const
{
- if(endpoints.size()!=2)
- return;
+ if(i>links.size())
+ throw InvalidParameterValue("Link index out of range");
- Track *link1=endpoints.front().link;
- Track *link2=endpoints.back().link;
- if(link1 && link2)
- {
- const Endpoint *ep1=link1->get_endpoint_by_link(this);
- const Endpoint *ep2=link2->get_endpoint_by_link(this);
- pos.z=link1->pos.z+ep1->pos.z;
- slope=(link2->pos.z+ep2->pos.z)-pos.z;
- }
- else
+ return links[i];
+}
+
+TrackPoint Track::get_point(unsigned epi, unsigned path, float d) const
+{
+ TrackPoint p = type.get_point(epi, path, d);
+ float c = cos(rot);
+ float s = sin(rot);
+
+ p.pos = Point(pos.x+c*p.pos.x-s*p.pos.y, pos.y+s*p.pos.x+c*p.pos.y, pos.z);
+ p.dir += rot;
+ if(type.get_endpoints().size()==2)
{
- slope=0;
- if(link1)
+ float len = type.get_path_length(path);
+ float grade = slope/len;
+ if(epi==0)
{
- const Endpoint *ep=link1->get_endpoint_by_link(this);
- pos.z=link1->pos.z+ep->pos.z;
+ p.pos.z += grade*d;
+ p.grade = grade;
}
- else if(link2)
+ else
{
- const Endpoint *ep=link2->get_endpoint_by_link(this);
- pos.z=link2->pos.z+ep->pos.z;
+ p.pos.z += slope-grade*d;
+ p.grade = -grade;
}
}
- endpoints.back().pos.z=slope;
+ return p;
}
-const Track::Endpoint *Track::traverse(const Endpoint *ep, unsigned route) const
+TrackPoint Track::get_point(unsigned epi, float d) const
{
- if(ep->routes&(1<<route))
- {
- // Find the other endpoint for this route
- for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if((i->routes&(1<<route)) && &*i!=ep)
- return &*i;
- }
- else
- {
- // Find an endpoint that's connected to this one and has the requested route
- for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
- if((i->routes&(1<<route)) && (i->routes&ep->routes))
- return &*i;
- }
-
- return 0;
+ return get_point(epi, active_path, d);
}
-Track *Track::copy() const
+void Track::save(list<DataFile::Statement> &st) const
{
- Track *trk=new Track(*this);
- for(EndpointSeq::iterator i=trk->endpoints.begin(); i!=trk->endpoints.end(); ++i)
- i->link=0;
- trk->turnout_id=0;
- trk->sensor_id=0;
-
- return trk;
+ st.push_back((DataFile::Statement("position"), pos.x, pos.y, pos.z));
+ st.push_back((DataFile::Statement("rotation"), rot));
+ st.push_back((DataFile::Statement("slope"), slope));
+ if(turnout_id)
+ st.push_back((DataFile::Statement("turnout_id"), turnout_id));
+ if(sensor_id)
+ st.push_back((DataFile::Statement("sensor_id"), sensor_id));
+ if(flex)
+ st.push_back((DataFile::Statement("flex"), true));
}
-/*** private ***/
-
-void Track::collect_endpoints()
+void Track::turnout_event(unsigned addr, bool state)
{
- endpoints.clear();
+ if(!turnout_id)
+ return;
- for(PartSeq::iterator i=parts.begin(); i!=parts.end(); ++i)
- i->collect_endpoints(endpoints);
- endpoints.back().pos.z=slope;
+ if(addr==turnout_id)
+ active_path = (active_path&2) | (state ? 1 : 0);
+ else if(type.is_double_address() && addr==turnout_id+1)
+ active_path = (active_path&1) | (state ? 2 : 0);
+ else
+ return;
- for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end();)
- {
- bool rm=false;
- for(EndpointSeq::iterator j=i; j!=endpoints.end();)
- {
- if(j==i)
- {
- ++j;
- continue;
- }
- float dx=i->pos.x-j->pos.x;
- float dy=i->pos.y-j->pos.y;
- if(dx*dx+dy*dy<0.0001)
- {
- float da=i->rot-j->rot;
- if(da<-M_PI)
- da+=M_PI*2;
- if(da>M_PI)
- da-=M_PI*2;
- if(da<-3 || da>3)
- rm=true;
- i->routes|=j->routes;
- j=endpoints.erase(j);
- }
- else
- ++j;
- }
- if(rm)
- i=endpoints.erase(i);
- else
- ++i;
- }
+ signal_path_changed.emit(active_path);
}
-/*******************
-** Track::Loader
-*/
Track::Loader::Loader(Track &t):
- track(t)
+ DataFile::BasicLoader<Track>(t)
{
- add("description", &Track::description);
- add("part", &Loader::part);
- add("position", &Loader::position);
- add("rotation", &Track::rot);
- add("slope", &Track::slope);
- add("turnout_id", &Track::turnout_id);
- add("sensor_id", &Track::sensor_id);
- add("flex", &Track::flex);
-}
-
-Track::Loader::~Loader()
-{
- track.collect_endpoints();
-}
-
-void Track::Loader::part()
-{
- Part p;
- load_sub(p);
- track.parts.push_back(p);
+ add("position", &Loader::position);
+ add("rotation", &Track::rot);
+ add("slope", &Track::slope);
+ add("turnout_id", &Loader::turnout_id);
+ add("sensor_id", &Loader::sensor_id);
+ add("flex", &Track::flex);
}
void Track::Loader::position(float x, float y, float z)
{
- track.pos=Point(x, y, z);
+ obj.pos = Point(x, y, z);
}
-/*******************
-** Track::Part
-*/
-
-Track::Part::Part():
- x(0),
- y(0),
- dir(0),
- length(0),
- radius(0),
- route(0),
- dead_end(false)
-{ }
-
-void Track::Part::collect_endpoints(EndpointSeq &epl)
+void Track::Loader::sensor_id(unsigned id)
{
- epl.push_back(Endpoint(Point(x, y, 0), dir+M_PI, 1<<route));
- if(dead_end)
- return;
- else if(radius)
- {
- float a=(radius<0)?-length:length;
- float c=cos(a);
- float s=sin(a);
- float rx=radius*sin(dir);
- float ry=-radius*cos(dir);
- epl.push_back(Endpoint(Point(x+c*rx-s*ry-rx, y+c*ry+s*rx-ry, 0), dir+a, 1<<route));
- }
- else
- epl.push_back(Endpoint(Point(x+cos(dir)*length, y+sin(dir)*length, 0), dir, 1<<route));
-}
-
-Track::Part::Loader::Loader(Part &p):
- part(p)
-{
- add("start", &Loader::start);
- add("length", &Part::length);
- add("radius", &Part::radius);
- add("route", &Part::route);
- add("dead_end", &Part::dead_end);
-}
-
-Track::Part::Loader::~Loader()
-{
- if(part.radius)
- {
- part.length*=M_PI/180;
- part.radius/=1000;
- }
- else
- part.length/=1000;
-
- part.x/=1000;
- part.y/=1000;
- part.dir*=M_PI/180;
+ obj.set_sensor_id(id);
}
-void Track::Part::Loader::start(float x, float y, float d)
+void Track::Loader::turnout_id(unsigned id)
{
- part.x=x;
- part.y=y;
- part.dir=d;
+ obj.set_turnout_id(id);
}
} // namespace Marklin