1 // dimension.cpp: Dimension object
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 04/04/2011 Created this file, basic rendering
12 // JLH 03/14/2013 Updated to new connection system
15 #include "dimension.h"
19 #include "mathconstants.h"
23 Dimension::Dimension(Vector p1, Vector p2, DimensionType dt/*= DTLinear*/, Object * p/*= NULL*/):
24 Object(p1, p), endpoint(p2),
25 dragging(false), draggingHandle1(false), draggingHandle2(false),
26 length(p2.Magnitude()), dimensionType(dt), size(0.25)//, point1(NULL), point2(NULL)
28 // We set the size to 1/4 base unit. Could be anything.
30 // dimensionType = DTLinearHorz;
34 Dimension::~Dimension()
40 How to move: click once moves only the object/point clicked on, all connected
41 objects deform themselves accordingly. click twice selects ALL connected objects;
42 all objects move as a unified whole.
46 /*virtual*/ void Dimension::Draw(Painter * painter)
48 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
50 if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
51 painter->DrawHandle(position);
53 if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
54 painter->DrawHandle(endpoint);
56 if (state == OSSelected)
57 painter->SetPen(QPen(Qt::cyan, 1.0 * Painter::zoom * size, Qt::SolidLine));
59 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
61 painter->SetBrush(QBrush(QColor(Qt::blue)));
63 // Draw an aligned dimension line
64 Vector v(position, endpoint);
65 double angle = v.Angle();
66 // Vector orthogonal = Vector::Normal(position, endpoint);
67 Vector unit = v.Unit();
68 linePt1 = position, linePt2 = endpoint;
70 // Horizontally aligned display
73 double x1, y1, length;
75 if (dimensionType == DTLinearVert)
77 if ((angle < 0) || (angle > PI))
79 x1 = (position.x > endpoint.x ? position.x : endpoint.x);
80 y1 = (position.y > endpoint.y ? position.y : endpoint.y);
81 ortho = Vector(1.0, 0);
86 x1 = (position.x > endpoint.x ? endpoint.x : position.x);
87 y1 = (position.y > endpoint.y ? endpoint.y : position.y);
88 ortho = Vector(-1.0, 0);
92 linePt1.x = linePt2.x = x1;
93 length = fabs(position.y - endpoint.y);
95 else if (dimensionType == DTLinearHorz)
97 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
99 x1 = (position.x > endpoint.x ? position.x : endpoint.x);
100 y1 = (position.y > endpoint.y ? position.y : endpoint.y);
101 ortho = Vector(0, 1.0);
106 x1 = (position.x > endpoint.x ? endpoint.x : position.x);
107 y1 = (position.y > endpoint.y ? endpoint.y : position.y);
108 ortho = Vector(0, -1.0);
112 linePt1.y = linePt2.y = y1;
113 length = fabs(position.x - endpoint.x);
115 else if (dimensionType == DTLinear)
117 angle = Vector(linePt1, linePt2).Angle();
118 ortho = Vector::Normal(linePt1, linePt2);
119 length = v.Magnitude();
122 unit = Vector(linePt1, linePt2).Unit();
123 // angle = Vector(linePt1, linePt2).Angle();
124 // ortho = Vector::Normal(linePt1, linePt2);
126 Point p1 = linePt1 + (ortho * 10.0 * size);
127 Point p2 = linePt2 + (ortho * 10.0 * size);
128 Point p3 = linePt1 + (ortho * 16.0 * size);
129 Point p4 = linePt2 + (ortho * 16.0 * size);
130 Point p5 = position + (ortho * 4.0 * size);
131 Point p6 = endpoint + (ortho * 4.0 * size);
134 The numbers hardcoded into here, what are they?
135 I believe they are pixels.
138 // Get our line parallel to our points
139 Point p1 = position + (orthogonal * 10.0 * size);
140 Point p2 = endpoint + (orthogonal * 10.0 * size);
142 Point p3 = position + (orthogonal * 16.0 * size);
143 Point p4 = endpoint + (orthogonal * 16.0 * size);
144 Point p5 = position + (orthogonal * 4.0 * size);
145 Point p6 = endpoint + (orthogonal * 4.0 * size);
147 // Draw extension lines (if certain type)
148 painter->DrawLine(p3, p5);
149 painter->DrawLine(p4, p6);
151 // Calculate whether or not the arrowheads are too crowded to put inside
152 // the extension lines. 9.0 is the length of the arrowhead.
153 // double t = Geometry::ParameterOfLineAndPoint(position, endpoint, endpoint - (unit * 9.0 * size));
154 // double t = Geometry::ParameterOfLineAndPoint(pos, endp, endp - (unit * 9.0 * size));
155 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * size));
156 //printf("Dimension::Draw(): t = %lf\n", t);
158 // On the screen, it's acting like this is actually 58%...
159 // This is correct, we want it to happen at > 50%
162 // Draw main dimension line + arrowheads
163 painter->DrawLine(p1, p2);
164 painter->DrawArrowhead(p1, p2, size);
165 painter->DrawArrowhead(p2, p1, size);
169 // Draw outside arrowheads
170 Point p7 = p1 - (unit * 9.0 * size);
171 Point p8 = p2 + (unit * 9.0 * size);
172 painter->DrawArrowhead(p1, p7, size);
173 painter->DrawArrowhead(p2, p8, size);
174 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
175 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
178 // Draw length of dimension line...
179 painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
180 Point ctr = p2 + (Vector(p2, p1) / 2.0);
183 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
188 dimText = QString("%1\"").arg(length);
191 double feet = (double)((int)length / 12);
192 double inches = length - (feet * 12.0);
195 dimText = QString("%1'").arg(feet);
197 dimText = QString("%1' %2\"").arg(feet).arg(inches);
201 painter->DrawAngledText(ctr, angle, dimText, size);
205 Point hp1 = (p1 + p2) / 2.0;
206 Point hp2 = (p1 + hp1) / 2.0;
207 Point hp3 = (hp1 + p2) / 2.0;
211 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
212 painter->SetBrush(QBrush(QColor(Qt::magenta)));
213 painter->DrawArrowHandle(hp1, ortho.Angle() + PI);
214 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
217 painter->DrawHandle(hp1);
218 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
220 if (hitChangeSwitch1)
222 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
223 painter->SetBrush(QBrush(QColor(Qt::magenta)));
224 painter->DrawArrowToLineHandle(hp2, (dimensionType == DTLinearVert ? v.Angle() - PI_OVER_2 : (v.Angle() < PI ? PI : 0)));
225 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
228 painter->DrawHandle(hp2);
229 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
231 if (hitChangeSwitch2)
233 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
234 painter->SetBrush(QBrush(QColor(Qt::magenta)));
235 painter->DrawArrowToLineHandle(hp3, (dimensionType == DTLinearHorz ? v.Angle() - PI_OVER_2 : (v.Angle() > PI_OVER_2 && v.Angle() < PI3_OVER_2 ? PI3_OVER_2 : PI_OVER_2)));
236 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
239 painter->DrawHandle(hp3);
244 /*virtual*/ Vector Dimension::Center(void)
246 // Technically, this is the midpoint but who are we to quibble? :-)
247 Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
252 /*virtual*/ bool Dimension::Collided(Vector point)
254 // Someone told us to fuck off, so we'll fuck off. :-)
258 // We can assume this, since this is a mouse down event here.
259 objectWasDragged = false;
262 // Now that we've done our hit testing on the non-snapped point, snap it if
265 point = SnapPointToGrid(point);
272 draggingHandle1 = true;
280 draggingHandle2 = true;
283 else if (hitFlipSwitch)
286 hitFlipSwitch = hitLine = false;
287 // state = OSInactive;
290 else if (hitChangeSwitch1)
292 // There are three cases here: aligned, horizontal, & vertical. Aligned
293 // and horizontal do the same thing, vertical goes back to linear.
294 if (dimensionType == DTLinearVert)
295 dimensionType = DTLinear;
297 dimensionType = DTLinearVert;
299 hitFlipSwitch = hitLine = false;
301 else if (hitChangeSwitch2)
303 // There are three cases here: aligned, horizontal, & vertical. Aligned
304 // and vertical do the same thing, horizontal goes back to linear.
305 if (dimensionType == DTLinearHorz)
306 dimensionType = DTLinear;
308 dimensionType = DTLinearHorz;
310 hitFlipSwitch = hitLine = false;
318 /*virtual*/ bool Dimension::PointerMoved(Vector point)
320 if (selectionInProgress)
322 // Check for whether or not the rect contains this line
323 if (selection.contains(position.x, position.y)
324 && selection.contains(endpoint.x, endpoint.y))
332 // Hit test tells us what we hit (if anything) through boolean variables. (It
333 // also tells us whether or not the state changed. --not any more)
335 bool hovered = HitTest(point);
336 needUpdate = HitStateChanged();
338 objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
340 if (objectWasDragged)
342 Vector delta = point - oldPoint;
344 if (draggingHandle1)// || draggingLine)
347 if (draggingHandle2)// || draggingLine)
358 /*virtual*/ void Dimension::PointerReleased(void)
360 /* if (draggingHandle1 || draggingHandle2)
362 // Set the length (in case the global state was set to fixed (or not))
363 if (Object::fixedLength)
366 if (draggingHandle1) // startpoint
368 Vector v = Vector(endpoint, position).Unit() * length;
369 position = endpoint + v;
373 Vector v = Vector(position, endpoint).Unit() * length;
374 endpoint = position + v;
379 // Otherwise, we calculate the new length, just in case on the next move
380 // it turns out to have a fixed length. :-)
381 length = Vector(endpoint - position).Magnitude();
386 draggingHandle1 = false;
387 draggingHandle2 = false;
389 // Here we check for just a click: If object was clicked and dragged, then
390 // revert to the old state (OSInactive). Otherwise, keep the new state that
392 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
393 about keeping track of old states...
395 if (objectWasDragged)
400 /*virtual*/ bool Dimension::HitTest(Point point)
402 // Vector orthogonal = Vector::Normal(position, endpoint);
403 Vector orthogonal = Vector::Normal(linePt1, linePt2);
404 // Get our line parallel to our points
406 Point p1 = position + (orthogonal * 10.0 * size);
407 Point p2 = endpoint + (orthogonal * 10.0 * size);
409 Point p1 = linePt1 + (orthogonal * 10.0 * size);
410 Point p2 = linePt2 + (orthogonal * 10.0 * size);
414 hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = hitChangeSwitch1
415 = hitChangeSwitch2 = false;
416 Vector v1(position, point);
417 Vector v2(endpoint, point);
418 // Vector lineSegment(position, endpoint);
419 Vector lineSegment(p1, p2);
420 // double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
421 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
423 Point midpoint = (p1 + p2) / 2.0;
424 Point hFSPoint = Point(midpoint, point);
425 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
426 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
429 distance = v1.Magnitude();
431 distance = v2.Magnitude();
433 // distance = ?Det?(ls, v1) / |ls|
434 // distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
435 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
436 / lineSegment.Magnitude());
438 if ((v1.Magnitude() * Painter::zoom) < 8.0)
440 else if ((v2.Magnitude() * Painter::zoom) < 8.0)
442 else if ((distance * Painter::zoom) < 5.0)
445 if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
446 hitFlipSwitch = true;
447 else if ((hCS1Point.Magnitude() * Painter::zoom) < 8.0)
448 hitChangeSwitch1 = true;
449 else if ((hCS2Point.Magnitude() * Painter::zoom) < 8.0)
450 hitChangeSwitch2 = true;
452 return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
456 void Dimension::SaveHitState(void)
458 oldHitPoint1 = hitPoint1;
459 oldHitPoint2 = hitPoint2;
460 oldHitLine = hitLine;
461 oldHitFlipSwitch = hitFlipSwitch;
462 oldHitChangeSwitch1 = hitChangeSwitch1;
463 oldHitChangeSwitch2 = hitChangeSwitch2;
467 bool Dimension::HitStateChanged(void)
469 if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2)
470 || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch)
471 || (hitChangeSwitch1 != oldHitChangeSwitch1)
472 || (hitChangeSwitch2 != oldHitChangeSwitch2))
479 /*virtual*/ void Dimension::Translate(Vector amount)
486 /*virtual*/ void Dimension::Rotate(Point point, double angle)
488 Point l1 = Geometry::RotatePointAroundPoint(position, point, angle);
489 Point l2 = Geometry::RotatePointAroundPoint(endpoint, point, angle);
495 /*virtual*/ void Dimension::Mirror(Point p1, Point p2)
497 Point l1 = Geometry::MirrorPointAroundLine(position, p1, p2);
498 Point l2 = Geometry::MirrorPointAroundLine(endpoint, p1, p2);
504 /*virtual*/ void Dimension::Save(void)
507 oldEndpoint = endpoint;
511 /*virtual*/ void Dimension::Restore(void)
514 endpoint = oldEndpoint;
518 /*virtual*/ void Dimension::Enumerate(FILE * file)
520 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
524 /*virtual*/ Object * Dimension::Copy(void)
526 #warning "!!! This doesn't take care of attached Dimensions !!!"
528 This is a real problem. While having a pointer in the Dimension to this line's points
529 is fast & easy, it creates a huge problem when trying to replicate an object like this.
531 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
532 way, if you copy them, ... you might still have problems. Because you can't be sure if
533 a copy will be persistant or not, you then *definitely* do not want them to have the
534 same reference number.
537 Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
543 // Dimensions are special: they contain exactly *two* points. Here, we check
544 // only for zero/non-zero in returning the correct points.
545 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
554 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
558 else if (parameter == 1.0)
561 {} // Not sure how to handle this case :-P
565 /*virtual*/ void Dimension::Connect(Object * obj, double param)
567 // There are four possibilities here...
568 // The param is only looking for 0 or 1 here.
569 if (point1.object == NULL && point2.object == NULL)
574 else if (point1.object == NULL && point2.object != NULL)
576 if (point2.t == param)
584 else if (point1.object != NULL && point2.object == NULL)
586 if (point1.t == param)
594 else if (point1.object != NULL && point2.object != NULL)
596 if (point1.t == param)
604 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
606 if (point1.object == obj && point1.t == param)
607 point1.object = NULL;
608 else if (point2.object == obj && point2.t == param)
609 point2.object = NULL;
613 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
615 if (point1.object == obj)
616 point1.object = NULL;
618 if (point2.object == obj)
619 point2.object = NULL;
623 /*virtual*/ QRectF Dimension::Extents(void)
628 // if (point1.object)
629 // p1 = point1.object->GetPointAtParameter(point1.t);
631 // if (point2.object)
632 // p2 = point2.object->GetPointAtParameter(point2.t);
634 return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
639 /*virtual*/ ObjectType Dimension::Type(void)
646 void Dimension::FlipSides(void)
649 Vector tmp = position;
652 //Not sure this matters...
653 //#warning "!!! May need to swap parameter values on connected objects !!!"
655 Connection tmp = point1;
658 // double tmp = point1.t;
659 // point1.t = point2.t;
661 // Object * tmp = point1.object;
662 // point1.object = point2.object;
663 // point2.object = tmp;