Forward declarations stink / Path is now serializable / Forward declarations stink
[supertux.git] / src / object / path.cpp
1 //  $Id$
2 // 
3 //  SuperTux Path
4 //  Copyright (C) 2005 Philipp <balinor@pnxs.de>
5 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 // 
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 //  02111-1307, USA.
21
22 #include "path.hpp"
23
24 #include "lisp/lisp.hpp"
25 #include "lisp/list_iterator.hpp"
26 #include "object_factory.hpp"
27
28 #include <assert.h>
29 #include <iostream>
30 #include <stdexcept>
31
32 // snap to destination if within EPSILON pixels
33 #define EPSILON 1.5
34
35 Path::Path(const lisp::Lisp& reader)
36 {
37   circular = true;
38   forward = true;
39
40   if (!reader.get("name", name)) throw std::runtime_error("Path without name");
41   reader.get("circular", circular);
42   reader.get("forward", forward);
43
44   const lisp::Lisp* nodes_lisp = reader.get_lisp("nodes");
45   if(!nodes_lisp) throw std::runtime_error("Path without nodes");
46
47   lisp::ListIterator iter(nodes_lisp);
48
49   PathNode node;
50   node.time = 1;
51
52   while(iter.next()) {
53     if(iter.item() != "node") {
54       std::cerr << "Warning: unknown token '" << iter.item() << "' in Path nodes list. Ignored." << std::endl;
55       continue;
56     }
57     const lisp::Lisp* node_lisp = iter.lisp();
58
59     // each new node will inherit all values from the last one
60     node_lisp->get("x", node.position.x);
61     node_lisp->get("y", node.position.y);
62     node_lisp->get("time", node.time);
63
64     if(node.time <= 0) throw std::runtime_error("Path node with non-positive time");
65
66     pathNodes.push_back(node);
67   }
68
69   if (pathNodes.size() < 1) throw std::runtime_error("Path with zero nodes");
70
71   // initial position and velocity will be set with the first update, as timeToGo is initialized to 0.
72   destinationNode = 0;
73
74   // register this path for lookup:
75   registry[name] = this;
76 }
77
78 Path::~Path()
79 {
80   registry.erase(name);
81 }
82
83         void
84 Path::update(float elapsed_time)
85 {
86
87   // TODO: carry excess time over to next node? This is how it was done in camera.cpp:
88   /*
89   if(auto_t - elapsed_time >= 0) {
90     translation += current_dir * elapsed_time;
91     auto_t -= elapsed_time;
92   } else {
93     // do the rest of the old movement
94     translation += current_dir * auto_t;
95     elapsed_time -= auto_t;
96     auto_t = 0;
97
98     // construct path for next point
99     if(auto_idx+1 >= scrollpoints.size()) {
100       keep_in_bounds(translation);
101       return;
102     }
103     Vector distance = scrollpoints[auto_idx+1].position 
104                       - scrollpoints[auto_idx].position;
105     current_dir = distance.unit() * scrollpoints[auto_idx].speed;
106     auto_t = distance.norm() / scrollpoints[auto_idx].speed;
107
108     // do movement for the remaining time
109     translation += current_dir * elapsed_time;
110     auto_t -= elapsed_time;
111     auto_idx++;
112   }
113   */
114
115   // advance to next node at scheduled time
116   if (timeToGo <= 0) {
117     position = pathNodes[destinationNode].position;
118
119     // set destinationNode to next node
120     if (forward) {
121       destinationNode++;
122       if (destinationNode >= (int)pathNodes.size()) {
123         if (circular) {
124           destinationNode = 0;
125         } else {
126           destinationNode = (int)pathNodes.size()-1;
127         }
128       }
129     } else {
130       destinationNode--;
131       if (destinationNode < 0) {
132         if (circular) {
133           destinationNode = (int)pathNodes.size()-1;
134         } else {
135           destinationNode = 0;
136         }
137       }
138     }
139
140     PathNode dn = pathNodes[destinationNode];
141     timeToGo = dn.time;
142     velocity = (dn.position - position) / timeToGo;
143   }
144
145   // move according to stored velocity
146   last_movement = velocity * elapsed_time;
147   position += last_movement;
148   timeToGo -= elapsed_time;
149
150   // stop when we arrive at our destination
151   PathNode dn = pathNodes[destinationNode];
152   if ((position - dn.position).norm() < EPSILON) {
153     velocity = Vector(0,0);
154   }
155
156 }
157
158 void
159 Path::draw(DrawingContext& )
160 {
161   // TODO: Add a visible flag, draw the path if true
162 }
163
164 void
165 Path::write(lisp::Writer& writer)
166 {
167   writer.start_list("path");
168
169   writer.write_string("name", name);
170   writer.write_bool("circular", circular);
171   writer.write_bool("forward", forward);
172
173   for (int i=0; i < (int)pathNodes.size(); i++) {
174     PathNode node = pathNodes[i];
175
176     writer.start_list("node");
177     writer.write_float("x", node.position.x);
178     writer.write_float("y", node.position.y);
179     writer.write_float("time", node.time);
180
181     writer.end_list("node");
182   }
183
184   writer.end_list("path");
185 }
186
187 const Vector&
188 Path::GetPosition() {
189   return position;
190 }
191
192 const Vector&
193 Path::GetLastMovement() {
194   return last_movement;
195 }
196
197 const std::string
198 Path::GetName() {
199   return name;
200 }
201
202 //////////////////////////////////////////////////////////////////////////////
203 // static stuff
204
205 std::map<std::string,Path*> Path::registry;
206
207 Path*
208 Path::GetByName(const std::string& name) {
209   return registry[name];
210 }
211
212 IMPLEMENT_FACTORY(Path, "path");