]> Shamusworld >> Repos - architektonas/blob - src/dimension.cpp
Beginnings of visual feedback for editing shapes.
[architektonas] / src / dimension.cpp
1 // dimension.cpp: Dimension object
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  04/04/2011  Created this file, basic rendering
12 //
13
14 #include "dimension.h"
15
16 #include <QtGui>
17 #include "mathconstants.h"
18
19
20 Dimension::Dimension(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
21         dragging(false), draggingHandle1(false), draggingHandle2(false),
22         length(p2.Magnitude())
23 {
24 }
25
26 Dimension::~Dimension()
27 {
28 }
29
30 /*virtual*/ void Dimension::Draw(QPainter * painter)
31 {
32         if (state == OSSelected)
33                 painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
34         else
35                 painter->setPen(QPen(Qt::blue, 1.0, Qt::SolidLine));
36
37         // Draw an aligned dimension line
38         double angle = Vector(endpoint - position).Angle();
39         double orthoAngle = angle + (PI / 2.0);
40         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
41         Vector unit = Vector(endpoint - position).Unit();
42
43         // Get our line parallel to our points
44         Point p1 = position + (orthogonal * 10.0);
45         Point p2 = endpoint + (orthogonal * 10.0);
46
47         // Draw main dimension line
48         painter->drawLine(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
49
50         Point p3 = position + (orthogonal * 16.0);
51         Point p4 = endpoint + (orthogonal * 16.0);
52         Point p5 = position + (orthogonal * 4.0);
53         Point p6 = endpoint + (orthogonal * 4.0);
54
55         // Draw extension lines
56         painter->drawLine(QPointF(p3.x, p3.y), QPointF(p5.x, p5.y));
57         painter->drawLine(QPointF(p4.x, p4.y), QPointF(p6.x, p6.y));
58
59         // Draw length of dimension line...
60         painter->setFont(QFont("Arial", 10));
61         Vector v1((p1.x - p2.x) / 2.0, (p1.y - p2.y) / 2.0);
62         Point ctr = p2 + v1;
63         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
64         int textWidth = QFontMetrics(painter->font()).width(dimText);
65         int textHeight = QFontMetrics(painter->font()).height();
66 //We have to do transformation voodoo to make the text come out readable and in correct orientation...
67 //Some things to note here: if angle > 90 degrees, then we need to take the negative of the angle
68 //for our text.
69 painter->save();
70 painter->translate(ctr.x, ctr.y);
71 int yOffset = -8;
72 //16 : printf("textHeight: %d\n", textHeight);
73
74 //Fix text so it isn't upside down...
75 if ((angle > PI * 0.5) && (angle < PI * 1.5))
76 {
77         angle += PI;
78         yOffset = 18;
79 }
80
81 painter->rotate(angle * RADIANS_TO_DEGREES);
82 painter->scale(1.0, -1.0);
83 //painter->translate(-textWidth / 2, -24);
84 //      painter->drawText(0, 0, textWidth, 20, Qt::AlignCenter, dimText);
85         // This version draws the y-coord from the baseline of the font
86         painter->drawText(-textWidth / 2, yOffset, dimText);
87 //painter->setPen(QPen(QColor(0xFF, 0x20, 0x20), 1.0, Qt::SolidLine));
88 //painter->drawLine(20, 0, -20, 0);
89 //painter->drawLine(0, 20, 0, -20);
90 painter->restore();
91 }
92
93 /*virtual*/ Vector Dimension::Center(void)
94 {
95         // Technically, this is the midpoint but who are we to quibble? :-)
96         Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
97         return endpoint + v;
98 }
99
100 /*virtual*/ bool Dimension::Collided(Vector /*point*/)
101 {
102 #if 0
103         objectWasDragged = false;
104         Vector lineSegment = endpoint - position;
105         Vector v1 = point - position;
106         Vector v2 = point - endpoint;
107         double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
108
109         // Geometric interpretation:
110         // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
111         // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
112         // then the perpendicular lies beyond the 2nd endpoint.
113
114         if (parameterizedPoint < 0.0)
115                 distance = v1.Magnitude();
116         else if (parameterizedPoint > lineSegment.Magnitude())
117                 distance = v2.Magnitude();
118         else                                    // distance = ?Det?(ls, v1) / |ls|
119                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
120
121         // If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
122         // intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
123         // {e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
124         // computed by first computing the area of the triangle the three points form, then dividing by the
125         // length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
126         // triangle formed by three points is the determinant of the following matrix:
127         //
128         // sx sy 1
129         // ex ey 1
130         // px py 1
131         //
132         // By translating the start point to the origin, this can be rewritten as:
133         // By subtracting row 1 from all rows, you get the following:
134         // [because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
135         // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
136         //
137         // 0         0         0        0  0  0
138         // (ex - sx) (ey - sy) 0   ==>  ex ey 0
139         // (px - sx) (py - sy) 0        px py 0
140         //
141         // which greatly simplifies the calculation of the determinant.
142
143         if (state == OSInactive)
144         {
145 //printf("Line: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
146 //printf("      v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
147 //printf("      point = %lf,%lf,%lf; p1 = %lf,%lf,%lf; p2 = %lf,%lf,%lf\n", point.x, point.y, point.z, position.x, position.y, position.z, endpoint.x, endpoint.y, endpoint.z);
148 //printf("      \n", );
149 //How to translate this into pixels from Document space???
150 //Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
151 //the caller knows about the zoom factor and all that good kinda crap
152                 if (v1.Magnitude() < 10.0)
153                 {
154                         oldState = state;
155                         state = OSSelected;
156                         oldPoint = position; //maybe "position"?
157                         draggingHandle1 = true;
158                         return true;
159                 }
160                 else if (v2.Magnitude() < 10.0)
161                 {
162                         oldState = state;
163                         state = OSSelected;
164                         oldPoint = endpoint; //maybe "position"?
165                         draggingHandle2 = true;
166                         return true;
167                 }
168                 else if (distance < 2.0)
169                 {
170                         oldState = state;
171                         state = OSSelected;
172                         oldPoint = point;
173                         dragging = true;
174                         return true;
175                 }
176         }
177         else if (state == OSSelected)
178         {
179                 // Here we test for collision with handles as well! (SOON!)
180 /*
181 Like so:
182                 if (v1.Magnitude() < 2.0) // Handle #1
183                 else if (v2.Magnitude() < 2.0) // Handle #2
184 */
185                 if (distance < 2.0)
186                 {
187                         oldState = state;
188 //                      state = OSInactive;
189                         oldPoint = point;
190                         dragging = true;
191                         return true;
192                 }
193         }
194 #endif
195
196         state = OSInactive;
197         return false;
198 }
199
200 /*virtual*/ void Dimension::PointerMoved(Vector point)
201 {
202         // We know this is true because mouse move messages don't come here unless
203         // the object was actually clicked on--therefore we *know* we're being
204         // dragged...
205         objectWasDragged = true;
206
207         if (dragging)
208         {
209                 // Here we need to check whether or not we're dragging a handle or the object itself...
210                 Vector delta = point - oldPoint;
211
212                 position += delta;
213                 endpoint += delta;
214
215                 oldPoint = point;
216                 needUpdate = true;
217         }
218         else if (draggingHandle1)
219         {
220                 Vector delta = point - oldPoint;
221
222                 position += delta;
223
224                 oldPoint = point;
225                 needUpdate = true;
226         }
227         else if (draggingHandle2)
228         {
229                 Vector delta = point - oldPoint;
230
231                 endpoint += delta;
232
233                 oldPoint = point;
234                 needUpdate = true;
235         }
236         else
237                 needUpdate = false;
238 }
239
240 /*virtual*/ void Dimension::PointerReleased(void)
241 {
242         if (draggingHandle1 || draggingHandle2)
243         {
244                 // Set the length (in case the global state was set to fixed (or not))
245                 if (Object::fixedLength)
246                 {
247
248                         if (draggingHandle1)    // startpoint
249                         {
250                                 Vector v = Vector(position - endpoint).Unit() * length;
251                                 position = endpoint + v;
252                         }
253                         else                                    // endpoint
254                         {
255 //                              Vector v1 = endpoint - position;
256                                 Vector v = Vector(endpoint - position).Unit() * length;
257                                 endpoint = position + v;
258                         }
259                 }
260                 else
261                 {
262                         // Otherwise, we calculate the new length, just in case on the next move
263                         // it turns out to have a fixed length. :-)
264                         length = Vector(endpoint - position).Magnitude();
265                 }
266         }
267
268         dragging = false;
269         draggingHandle1 = false;
270         draggingHandle2 = false;
271
272         // Here we check for just a click: If object was clicked and dragged, then
273         // revert to the old state (OSInactive). Otherwise, keep the new state that
274         // we set.
275 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
276 about keeping track of old states...
277 */
278         if (objectWasDragged)
279                 state = oldState;
280 }
281
282 void Dimension::SetPoint1(Vector v)
283 {
284         position = v;
285         needUpdate = true;
286 }
287
288 void Dimension::SetPoint2(Vector v)
289 {
290         endpoint = v;
291         needUpdate = true;
292 }
293
294 void Dimension::FlipSides(void)
295 {
296         Vector tmp = position;
297         position = endpoint;
298         endpoint = tmp;
299         needUpdate = true;
300 }