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 }