Mercurial > projects > doodle
comparison doodle/tk/geometry.d @ 33:157b4ad5615d
Added intersection code for lines and segments.
Wrote my first unit test to for geometry stuff.
author | David Bryant <bagnose@gmail.com> |
---|---|
date | Sun, 30 Aug 2009 01:34:14 +0930 |
parents | 1754cb773d41 |
children | c2f11e1d7470 |
comparison
equal
deleted
inserted
replaced
32:705817d8514a | 33:157b4ad5615d |
---|---|
4 import std.stdio; | 4 import std.stdio; |
5 import std.math; | 5 import std.math; |
6 import doodle.tk.misc; | 6 import doodle.tk.misc; |
7 } | 7 } |
8 | 8 |
9 // In doodle x and y increase right/east and up/north respectively. | |
10 | |
11 // TODO explain the strategy for ensuring numerical stability | |
12 // and the division of responsibility between users of these | |
13 // types and the types themselves. | |
14 // | |
15 // Explain how numerical instability is handled. The current policy | |
16 // is to correct bad user input (eg a gradient with miniscule length) and | |
17 // print warnings rather than have assertions and cause crashes. | |
18 | |
19 // A location in 2D space | |
20 | |
9 struct Point { | 21 struct Point { |
10 static immutable Point DEFAULT; | 22 static immutable Point DEFAULT; |
11 | 23 |
12 static this() { | 24 static this() { |
13 DEFAULT = Point(0.0, 0.0); | 25 DEFAULT = Point(0.0, 0.0); |
28 | 40 |
29 Vector opSub(in Point p) const { | 41 Vector opSub(in Point p) const { |
30 return Vector(_x - p._x, _y - p._y); | 42 return Vector(_x - p._x, _y - p._y); |
31 } | 43 } |
32 | 44 |
33 string toString() { | 45 string toString() const { |
34 return std.string.format("(%f, %f)", _x, _y); | 46 return std.string.format("(%f, %f)", _x, _y); |
35 } | 47 } |
36 | 48 |
37 double x() const { return _x; } | 49 double x() const { return _x; } |
38 double y() const { return _y; } | 50 double y() const { return _y; } |
47 } | 59 } |
48 | 60 |
49 Point max_extents(in Point a, in Point b) { | 61 Point max_extents(in Point a, in Point b) { |
50 return Point(max(a.x, b.x), max(a.y, b.y)); | 62 return Point(max(a.x, b.x), max(a.y, b.y)); |
51 } | 63 } |
64 | |
65 // The displacement between two locations in 2D space | |
52 | 66 |
53 struct Vector { | 67 struct Vector { |
54 static Vector DEFAULT; | 68 static Vector DEFAULT; |
55 | 69 |
56 static this() { | 70 static this() { |
84 | 98 |
85 double length() const { | 99 double length() const { |
86 return sqrt(_x * _x + _y * _y); | 100 return sqrt(_x * _x + _y * _y); |
87 } | 101 } |
88 | 102 |
89 string toString() { | 103 string toString() const { |
90 return std.string.format("[%f, %f]", _x, _y); | 104 return std.string.format("[%f, %f]", _x, _y); |
91 } | 105 } |
92 | 106 |
93 double x() const { return _x; } | 107 double x() const { return _x; } |
94 double y() const { return _y; } | 108 double y() const { return _y; } |
96 private { | 110 private { |
97 double _x, _y; | 111 double _x, _y; |
98 } | 112 } |
99 } | 113 } |
100 | 114 |
115 // A rectangle in 2D space. | |
116 // Internally represented by: | |
117 // a point defining the bottom left corner | |
118 // a vector defining the displacement to the upper right corner | |
119 | |
101 struct Rectangle { | 120 struct Rectangle { |
102 static Rectangle DEFAULT; | 121 static Rectangle DEFAULT; |
103 | 122 |
104 static this() { | 123 static this() { |
105 DEFAULT = Rectangle(Point.DEFAULT, Vector.DEFAULT); | 124 DEFAULT = Rectangle(Point.DEFAULT, Vector.DEFAULT); |
116 | 135 |
117 this(in Point corner1, in Point corner) { | 136 this(in Point corner1, in Point corner) { |
118 this(corner1.x, corner1.y, corner.x - corner1.x, corner.y - corner1.y); | 137 this(corner1.x, corner1.y, corner.x - corner1.x, corner.y - corner1.y); |
119 } | 138 } |
120 | 139 |
121 Point position() const { | |
122 return _position; | |
123 } | |
124 | |
125 Vector size() const { | |
126 return _size; | |
127 } | |
128 | |
129 alias position min_corner; | 140 alias position min_corner; |
130 | 141 Point position() const { return _position; } |
131 Point max_corner() const { | 142 |
132 return _position + _size; | 143 Vector size() const { return _size; } |
133 } | 144 |
134 | 145 Point max_corner() const { return _position + _size; } |
135 bool valid() const { | 146 |
136 return _size.x > 0.0 & _size.y > 0.0; | 147 bool valid() const { return _size.x > 0.0 & _size.y > 0.0; } |
137 } | 148 |
138 | 149 bool invalid() const { return !valid(); } |
139 bool invalid() const { | 150 |
140 return !valid(); | 151 double area() const { return _size.x * _size.y; } |
141 } | |
142 | |
143 double area() const { | |
144 return _size.x * _size.y; | |
145 } | |
146 | 152 |
147 // Intersection | 153 // Intersection |
148 Rectangle opAnd(in Rectangle r) const { | 154 Rectangle opAnd(in Rectangle r) const { |
149 if (invalid() || r.invalid()) { | 155 if (invalid() || r.invalid()) { |
150 return DEFAULT; | 156 return DEFAULT; |
170 else if (r.invalid()) { | 176 else if (r.invalid()) { |
171 return this; | 177 return this; |
172 } | 178 } |
173 else { | 179 else { |
174 return Rectangle(min_extents(min_corner(), r.min_corner()), | 180 return Rectangle(min_extents(min_corner(), r.min_corner()), |
175 max_extents(max_corner(), r.max_corner())); | 181 max_extents(max_corner(), r.max_corner())); |
176 } | 182 } |
177 } | 183 } |
178 | 184 |
179 // TODO make these free functions | 185 // TODO review these functions. |
186 // Want a clear and simple set. | |
187 // Consider making them free functions | |
180 | 188 |
181 Rectangle moved(in Vector displacement) const { | 189 Rectangle moved(in Vector displacement) const { |
182 return Rectangle(_position + displacement, _size); | 190 return Rectangle(_position + displacement, _size); |
183 } | 191 } |
184 | 192 |
193 Rectangle repositioned(in Point new_position) const { | |
194 return Rectangle(new_position, _size); | |
195 } | |
196 | |
197 // Operations about the bottom left corner | |
198 | |
185 Rectangle expanded(in Vector expand_amount) const { | 199 Rectangle expanded(in Vector expand_amount) const { |
186 return Rectangle(_position, _size + expand_amount); | 200 return Rectangle(_position, _size + expand_amount); |
187 } | 201 } |
202 | |
203 Rectangle shrunk(in Vector shrink_amount) const { | |
204 return Rectangle(_position, _size - shrink_amount); | |
205 } | |
206 | |
207 // Operations about the centre | |
188 | 208 |
189 Rectangle feathered(double amount) const { | 209 Rectangle feathered(double amount) const { |
190 assert(amount >= 0.0); | 210 assert(amount >= 0.0); |
191 return Rectangle(Point(_position.x - amount, _position.y - amount), | 211 return Rectangle(Point(_position.x - amount, _position.y - amount), |
192 Vector(_size.x + 2.0 * amount, _size.y + 2.0 * amount)); | 212 Vector(_size.x + 2.0 * amount, _size.y + 2.0 * amount)); |
193 } | 213 } |
194 | 214 |
195 Rectangle shrunk(in Vector shrink_amount) const { | |
196 return Rectangle(_position, _size - shrink_amount); | |
197 } | |
198 | |
199 Rectangle resized(in Vector new_size) const { | 215 Rectangle resized(in Vector new_size) const { |
200 return Rectangle(_position, new_size); | 216 return Rectangle(_position, new_size); |
201 } | 217 } |
202 | 218 |
203 Rectangle repositioned(in Point new_position) const { | 219 // |
204 return Rectangle(new_position, _size); | |
205 } | |
206 | 220 |
207 void get_quantised(out int x, out int y, out int w, out int h) const { | 221 void get_quantised(out int x, out int y, out int w, out int h) const { |
208 x = cast(int)floor(_position.x); | 222 x = cast(int)floor(_position.x); |
209 y = cast(int)floor(_position.y); | 223 y = cast(int)floor(_position.y); |
210 w = cast(int)ceil(_position.x + _size.x) - x; | 224 w = cast(int)ceil(_position.x + _size.x) - x; |
211 h = cast(int)ceil(_position.y + _size.y) - y; | 225 h = cast(int)ceil(_position.y + _size.y) - y; |
212 } | 226 } |
213 | 227 |
214 // | 228 // |
215 | 229 |
216 Point centre() const { | 230 Point centre() const { return _position + _size / 2.0; } |
217 return _position + _size / 2.0; | 231 |
218 } | 232 string toString() const { |
219 | |
220 string toString() { | |
221 return std.string.format("{%s, %s}", _position, _size); | 233 return std.string.format("{%s, %s}", _position, _size); |
222 } | 234 } |
223 | 235 |
224 private { | 236 private { |
225 this(double x, double y, double w, double h) { | 237 this(double x, double y, double w, double h) { |
232 Point _position; | 244 Point _position; |
233 Vector _size; | 245 Vector _size; |
234 } | 246 } |
235 } | 247 } |
236 | 248 |
237 /* | 249 private { |
250 // This is a commmon building block for intersection of lines and segments | |
251 // Two lines "a" and "b" are defined by two points each | |
252 // "ua" and "ub" define the intersection as a fraction alone | |
253 // the two respetive line segments (FIXME this comment sucks) | |
254 // Influenced by http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ | |
255 bool compute_intersection(in Point pa1, in Point pa2, out double ua, | |
256 in Point pb1, in Point pb2, out double ub) { | |
257 //writefln("pa1: %s, pa2: %s, pb1: %s, pb2: %s", pa1, pa2, pb1, pb2); | |
258 double den = (pb2.y - pb1.y) * (pa2.x - pa1.x) - (pb2.x - pb1.x) * (pa2.y - pa1.y); | |
259 | |
260 if (abs(den) < 1e-9) { // TODO consolidate constants used for numerical stability | |
261 // Lines are parallel or nearly so | |
262 writefln("Warning, parallel lines!"); | |
263 return false; | |
264 } | |
265 else { | |
266 // It will be safe to divide by den | |
267 double num_a = (pb2.x - pb1.x) * (pa1.y - pb1.y) - (pb2.y - pb1.y) * (pa1.x - pb1.x); | |
268 double num_b = (pa2.x - pa1.x) * (pa1.y - pb1.y) - (pa2.y - pa1.y) * (pa1.x - pb1.x); | |
269 | |
270 ua = num_a / den; | |
271 ub = num_b / den; | |
272 | |
273 { | |
274 // FIXME remove this debugging stuff | |
275 //writefln("ua: %s, ub: %s", ua, ub); | |
276 Point pa = pa1 + ua * (pa2 - pa1); | |
277 Point pb = pb1 + ub * (pb2 - pb1); | |
278 //writefln("pa: %s, pb: %s", pa, pb); | |
279 assert(pa1 + ua * (pa2 - pa1) == pb1 + ub * (pb2 - pb1)); | |
280 } | |
281 | |
282 return true; | |
283 } | |
284 } | |
285 } | |
286 | |
287 // A line (notionally infinitely extending in both directions) in 2D space. | |
288 // Internally represented by: | |
289 // a point at an arbitrary location along the line | |
290 // a vector defining the gradient of the line | |
291 | |
292 struct Line { | |
293 this(in Point p, in Vector g) { | |
294 _point = p; | |
295 _gradient = g; | |
296 assert(_gradient.length > 1e-6); // FIXME how to best deal with this | |
297 } | |
298 | |
299 this(in Point a, in Point b) { | |
300 _point = a; | |
301 _gradient = b - a; | |
302 assert(_gradient.length > 1e-6); // FIXME as above | |
303 } | |
304 | |
305 Point point() const { return _point; } | |
306 Vector gradient() const { return _gradient; } | |
307 | |
308 private { | |
309 Point _point; // Arbitrary point along line | |
310 Vector _gradient; | |
311 } | |
312 } | |
313 | |
314 // Calculate the point "p" where lines "a" and "b" intersect. | |
315 // Returns false if lines are parallel or too close for numerical stability | |
316 bool intersection(in Line a, in Line b, out Point p) { | |
317 Point pa = a.point; | |
318 Vector va = a.gradient; | |
319 double ua; | |
320 Point pb = b.point; | |
321 Vector vb = b.gradient; | |
322 double ub; | |
323 | |
324 if (compute_intersection(pa, pa + va, ua, pb, pb + vb, ub)) { | |
325 // We could just have easily evaluated for line b... | |
326 p = pa + ua * va; | |
327 // p = pb + ub * vb; | |
328 return true; | |
329 } | |
330 else { | |
331 return false; | |
332 } | |
333 } | |
334 | |
335 // A line segment (has a beginning and an end) in 2D space. | |
336 | |
238 struct Segment { | 337 struct Segment { |
338 this(in Point a, in Point b) { | |
339 _begin = a; | |
340 _end = b; | |
341 } | |
342 | |
343 Point begin() const { return _begin; } | |
344 Point end() const { return _end; } | |
345 | |
239 private { | 346 private { |
240 Point _begin, _end; | 347 Point _begin, _end; |
241 } | 348 } |
242 } | 349 } |
243 | 350 |
244 struct Line { | 351 bool intersection(in Segment a, in Segment b, out Point p) { |
245 private { | 352 Point pa1 = a.begin; |
246 Point _point; | 353 Point pa2 = a.end; |
247 Vector _gradien; | 354 double ua; |
248 } | 355 Point pb1 = b.begin; |
249 } | 356 Point pb2 = b.end; |
250 */ | 357 double ub; |
358 | |
359 if (compute_intersection(pa1, pa2, ua, pb1, pb2, ub)) { | |
360 if (ua >= 0.0 && ua <= 1.0 && // inside of segment a | |
361 ub >= 0.0 && ub <= 1.0) { // inside of segment b | |
362 // We could just have easily evaluated for line b... | |
363 p = pa1 + ua * (pa2 - pa1); | |
364 // p = pa2 + ub * (pb2 - pb1); | |
365 return true; | |
366 } | |
367 else { | |
368 return false; | |
369 } | |
370 } | |
371 else { | |
372 return false; | |
373 } | |
374 } |