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);
267 if (snapPointIsValid)
275 draggingHandle1 = true;
283 draggingHandle2 = true;
286 else if (hitFlipSwitch)
289 hitFlipSwitch = hitLine = false;
290 // state = OSInactive;
293 else if (hitChangeSwitch1)
295 // There are three cases here: aligned, horizontal, & vertical. Aligned
296 // and horizontal do the same thing, vertical goes back to linear.
297 if (dimensionType == DTLinearVert)
298 dimensionType = DTLinear;
300 dimensionType = DTLinearVert;
302 hitFlipSwitch = hitLine = false;
304 else if (hitChangeSwitch2)
306 // There are three cases here: aligned, horizontal, & vertical. Aligned
307 // and vertical do the same thing, horizontal goes back to linear.
308 if (dimensionType == DTLinearHorz)
309 dimensionType = DTLinear;
311 dimensionType = DTLinearHorz;
313 hitFlipSwitch = hitLine = false;
321 /*virtual*/ bool Dimension::PointerMoved(Vector point)
323 if (selectionInProgress)
325 // Check for whether or not the rect contains this line
326 if (selection.contains(position.x, position.y)
327 && selection.contains(endpoint.x, endpoint.y))
335 // Hit test tells us what we hit (if anything) through boolean variables. (It
336 // also tells us whether or not the state changed. --not any more)
338 bool hovered = HitTest(point);
339 needUpdate = HitStateChanged();
342 point = SnapPointToGrid(point);
344 if (snapPointIsValid)
347 objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
349 if (objectWasDragged)
351 Vector delta = point - oldPoint;
353 if (draggingHandle1)// || draggingLine)
356 if (draggingHandle2)// || draggingLine)
367 /*virtual*/ void Dimension::PointerReleased(void)
369 /* if (draggingHandle1 || draggingHandle2)
371 // Set the length (in case the global state was set to fixed (or not))
372 if (Object::fixedLength)
375 if (draggingHandle1) // startpoint
377 Vector v = Vector(endpoint, position).Unit() * length;
378 position = endpoint + v;
382 Vector v = Vector(position, endpoint).Unit() * length;
383 endpoint = position + v;
388 // Otherwise, we calculate the new length, just in case on the next move
389 // it turns out to have a fixed length. :-)
390 length = Vector(endpoint - position).Magnitude();
395 draggingHandle1 = false;
396 draggingHandle2 = false;
398 // Here we check for just a click: If object was clicked and dragged, then
399 // revert to the old state (OSInactive). Otherwise, keep the new state that
401 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
402 about keeping track of old states...
404 if (objectWasDragged)
409 /*virtual*/ bool Dimension::HitTest(Point point)
411 // Vector orthogonal = Vector::Normal(position, endpoint);
412 Vector orthogonal = Vector::Normal(linePt1, linePt2);
413 // Get our line parallel to our points
415 Point p1 = position + (orthogonal * 10.0 * size);
416 Point p2 = endpoint + (orthogonal * 10.0 * size);
418 Point p1 = linePt1 + (orthogonal * 10.0 * size);
419 Point p2 = linePt2 + (orthogonal * 10.0 * size);
423 hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = hitChangeSwitch1
424 = hitChangeSwitch2 = false;
425 Vector v1(position, point);
426 Vector v2(endpoint, point);
427 // Vector lineSegment(position, endpoint);
428 Vector lineSegment(p1, p2);
429 // double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
430 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
432 Point midpoint = (p1 + p2) / 2.0;
433 Point hFSPoint = Point(midpoint, point);
434 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
435 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
438 distance = v1.Magnitude();
440 distance = v2.Magnitude();
442 // distance = ?Det?(ls, v1) / |ls|
443 // distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
444 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
445 / lineSegment.Magnitude());
447 if ((v1.Magnitude() * Painter::zoom) < 8.0)
449 else if ((v2.Magnitude() * Painter::zoom) < 8.0)
451 else if ((distance * Painter::zoom) < 5.0)
454 if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
455 hitFlipSwitch = true;
456 else if ((hCS1Point.Magnitude() * Painter::zoom) < 8.0)
457 hitChangeSwitch1 = true;
458 else if ((hCS2Point.Magnitude() * Painter::zoom) < 8.0)
459 hitChangeSwitch2 = true;
461 return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
465 void Dimension::SaveHitState(void)
467 oldHitPoint1 = hitPoint1;
468 oldHitPoint2 = hitPoint2;
469 oldHitLine = hitLine;
470 oldHitFlipSwitch = hitFlipSwitch;
471 oldHitChangeSwitch1 = hitChangeSwitch1;
472 oldHitChangeSwitch2 = hitChangeSwitch2;
476 bool Dimension::HitStateChanged(void)
478 if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2)
479 || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch)
480 || (hitChangeSwitch1 != oldHitChangeSwitch1)
481 || (hitChangeSwitch2 != oldHitChangeSwitch2))
488 /*virtual*/ void Dimension::Translate(Vector amount)
495 /*virtual*/ void Dimension::Rotate(Point point, double angle)
497 Point l1 = Geometry::RotatePointAroundPoint(position, point, angle);
498 Point l2 = Geometry::RotatePointAroundPoint(endpoint, point, angle);
504 /*virtual*/ void Dimension::Mirror(Point p1, Point p2)
506 Point l1 = Geometry::MirrorPointAroundLine(position, p1, p2);
507 Point l2 = Geometry::MirrorPointAroundLine(endpoint, p1, p2);
513 /*virtual*/ void Dimension::Save(void)
516 oldEndpoint = endpoint;
520 /*virtual*/ void Dimension::Restore(void)
523 endpoint = oldEndpoint;
527 /*virtual*/ void Dimension::Enumerate(FILE * file)
529 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
533 /*virtual*/ Object * Dimension::Copy(void)
535 #warning "!!! This doesn't take care of attached Dimensions !!!"
537 This is a real problem. While having a pointer in the Dimension to this line's points
538 is fast & easy, it creates a huge problem when trying to replicate an object like this.
540 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
541 way, if you copy them, ... you might still have problems. Because you can't be sure if
542 a copy will be persistant or not, you then *definitely* do not want them to have the
543 same reference number.
546 Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
552 // Dimensions are special: they contain exactly *two* points. Here, we check
553 // only for zero/non-zero in returning the correct points.
554 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
563 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
567 else if (parameter == 1.0)
570 {} // Not sure how to handle this case :-P
574 /*virtual*/ void Dimension::Connect(Object * obj, double param)
576 // There are four possibilities here...
577 // The param is only looking for 0 or 1 here.
578 if (point1.object == NULL && point2.object == NULL)
583 else if (point1.object == NULL && point2.object != NULL)
585 if (point2.t == param)
593 else if (point1.object != NULL && point2.object == NULL)
595 if (point1.t == param)
603 else if (point1.object != NULL && point2.object != NULL)
605 if (point1.t == param)
613 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
615 if (point1.object == obj && point1.t == param)
616 point1.object = NULL;
617 else if (point2.object == obj && point2.t == param)
618 point2.object = NULL;
622 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
624 if (point1.object == obj)
625 point1.object = NULL;
627 if (point2.object == obj)
628 point2.object = NULL;
632 /*virtual*/ QRectF Dimension::Extents(void)
637 // if (point1.object)
638 // p1 = point1.object->GetPointAtParameter(point1.t);
640 // if (point2.object)
641 // p2 = point2.object->GetPointAtParameter(point2.t);
643 return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
648 /*virtual*/ ObjectType Dimension::Type(void)
655 void Dimension::FlipSides(void)
658 Vector tmp = position;
661 //Not sure this matters...
662 //#warning "!!! May need to swap parameter values on connected objects !!!"
664 Connection tmp = point1;
667 // double tmp = point1.t;
668 // point1.t = point2.t;
670 // Object * tmp = point1.object;
671 // point1.object = point2.object;
672 // point2.object = tmp;