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))
78 // if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
80 x1 = (position.x > endpoint.x ? position.x : endpoint.x);
81 y1 = (position.y > endpoint.y ? position.y : endpoint.y);
82 ortho = Vector(1.0, 0);
83 // ortho = Vector(0, 1.0);
89 x1 = (position.x > endpoint.x ? endpoint.x : position.x);
90 y1 = (position.y > endpoint.y ? endpoint.y : position.y);
91 ortho = Vector(-1.0, 0);
92 // ortho = Vector(0, -1.0);
97 linePt1.x = linePt2.x = x1;
98 length = fabs(position.y - endpoint.y);
100 else if (dimensionType == DTLinearHorz)
102 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
104 x1 = (position.x > endpoint.x ? position.x : endpoint.x);
105 y1 = (position.y > endpoint.y ? position.y : endpoint.y);
106 ortho = Vector(0, 1.0);
111 x1 = (position.x > endpoint.x ? endpoint.x : position.x);
112 y1 = (position.y > endpoint.y ? endpoint.y : position.y);
113 ortho = Vector(0, -1.0);
117 linePt1.y = linePt2.y = y1;
118 length = fabs(position.x - endpoint.x);
120 else if (dimensionType == DTLinear)
122 angle = Vector(linePt1, linePt2).Angle();
123 ortho = Vector::Normal(linePt1, linePt2);
124 length = v.Magnitude();
127 unit = Vector(linePt1, linePt2).Unit();
128 // angle = Vector(linePt1, linePt2).Angle();
129 // ortho = Vector::Normal(linePt1, linePt2);
131 Point p1 = linePt1 + (ortho * 10.0 * size);
132 Point p2 = linePt2 + (ortho * 10.0 * size);
133 Point p3 = linePt1 + (ortho * 16.0 * size);
134 Point p4 = linePt2 + (ortho * 16.0 * size);
135 Point p5 = position + (ortho * 4.0 * size);
136 Point p6 = endpoint + (ortho * 4.0 * size);
139 The numbers hardcoded into here, what are they?
140 I believe they are pixels.
143 // Get our line parallel to our points
144 Point p1 = position + (orthogonal * 10.0 * size);
145 Point p2 = endpoint + (orthogonal * 10.0 * size);
147 Point p3 = position + (orthogonal * 16.0 * size);
148 Point p4 = endpoint + (orthogonal * 16.0 * size);
149 Point p5 = position + (orthogonal * 4.0 * size);
150 Point p6 = endpoint + (orthogonal * 4.0 * size);
152 // Draw extension lines (if certain type)
153 painter->DrawLine(p3, p5);
154 painter->DrawLine(p4, p6);
156 // Calculate whether or not the arrowheads are too crowded to put inside
157 // the extension lines. 9.0 is the length of the arrowhead.
158 // double t = Geometry::ParameterOfLineAndPoint(position, endpoint, endpoint - (unit * 9.0 * size));
159 // double t = Geometry::ParameterOfLineAndPoint(pos, endp, endp - (unit * 9.0 * size));
160 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * size));
161 //printf("Dimension::Draw(): t = %lf\n", t);
163 // On the screen, it's acting like this is actually 58%...
164 // This is correct, we want it to happen at > 50%
167 // Draw main dimension line + arrowheads
168 painter->DrawLine(p1, p2);
169 painter->DrawArrowhead(p1, p2, size);
170 painter->DrawArrowhead(p2, p1, size);
174 // Draw outside arrowheads
175 Point p7 = p1 - (unit * 9.0 * size);
176 Point p8 = p2 + (unit * 9.0 * size);
177 painter->DrawArrowhead(p1, p7, size);
178 painter->DrawArrowhead(p2, p8, size);
179 painter->DrawLine(p1, p1 - (unit * 14.0 * size));
180 painter->DrawLine(p2, p2 + (unit * 14.0 * size));
183 // Draw length of dimension line...
184 painter->SetFont(QFont("Arial", 8.0 * Painter::zoom * size));
185 Point ctr = p2 + (Vector(p2, p1) / 2.0);
188 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
193 dimText = QString("%1\"").arg(length);
196 double feet = (double)((int)length / 12);
197 double inches = length - (feet * 12.0);
200 dimText = QString("%1'").arg(feet);
202 dimText = QString("%1' %2\"").arg(feet).arg(inches);
206 painter->DrawAngledText(ctr, angle, dimText, size);
210 Point hp1 = (p1 + p2) / 2.0;
211 Point hp2 = (p1 + hp1) / 2.0;
212 Point hp3 = (hp1 + p2) / 2.0;
216 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
217 painter->SetBrush(QBrush(QColor(Qt::magenta)));
218 painter->DrawArrowHandle(hp1, ortho.Angle() + PI);
219 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
222 painter->DrawHandle(hp1);
223 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
225 if (hitChangeSwitch1)
227 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
228 painter->SetBrush(QBrush(QColor(Qt::magenta)));
229 painter->DrawArrowToLineHandle(hp2, (dimensionType == DTLinearVert ? ortho.Angle() : 0));
230 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
233 painter->DrawHandle(hp2);
234 painter->SetPen(QPen(Qt::blue, 1.0 * Painter::zoom * size, Qt::SolidLine));
236 if (hitChangeSwitch2)
238 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
239 painter->SetBrush(QBrush(QColor(Qt::magenta)));
240 painter->DrawArrowToLineHandle(hp3, PI_OVER_2);
241 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
244 painter->DrawHandle(hp3);
249 /*virtual*/ Vector Dimension::Center(void)
251 // Technically, this is the midpoint but who are we to quibble? :-)
252 Vector v((position.x - endpoint.x) / 2.0, (position.y - endpoint.y) / 2.0);
257 /*virtual*/ bool Dimension::Collided(Vector point)
259 // Someone told us to fuck off, so we'll fuck off. :-)
263 // We can assume this, since this is a mouse down event here.
264 objectWasDragged = false;
267 // Now that we've done our hit testing on the non-snapped point, snap it if
270 point = SnapPointToGrid(point);
277 draggingHandle1 = true;
285 draggingHandle2 = true;
288 else if (hitFlipSwitch)
291 hitFlipSwitch = hitLine = false;
292 // state = OSInactive;
295 else if (hitChangeSwitch1)
297 // There are three cases here: aligned, horizontal, & vertical. Aligned
298 // and horizontal do the same thing, vertical goes back to linear.
299 if (dimensionType == DTLinearVert)
300 dimensionType = DTLinear;
302 dimensionType = DTLinearVert;
304 hitFlipSwitch = hitLine = false;
306 else if (hitChangeSwitch2)
308 // There are three cases here: aligned, horizontal, & vertical. Aligned
309 // and vertical do the same thing, horizontal goes back to linear.
310 if (dimensionType == DTLinearHorz)
311 dimensionType = DTLinear;
313 dimensionType = DTLinearHorz;
315 hitFlipSwitch = hitLine = false;
323 /*virtual*/ bool Dimension::PointerMoved(Vector point)
325 if (selectionInProgress)
327 // Check for whether or not the rect contains this line
328 if (selection.contains(position.x, position.y)
329 && selection.contains(endpoint.x, endpoint.y))
337 // Hit test tells us what we hit (if anything) through boolean variables. (It
338 // also tells us whether or not the state changed. --not any more)
340 bool hovered = HitTest(point);
341 needUpdate = HitStateChanged();
343 objectWasDragged = (/*draggingLine |*/ draggingHandle1 | draggingHandle2);
345 if (objectWasDragged)
347 Vector delta = point - oldPoint;
349 if (draggingHandle1)// || draggingLine)
352 if (draggingHandle2)// || draggingLine)
363 /*virtual*/ void Dimension::PointerReleased(void)
365 /* if (draggingHandle1 || draggingHandle2)
367 // Set the length (in case the global state was set to fixed (or not))
368 if (Object::fixedLength)
371 if (draggingHandle1) // startpoint
373 Vector v = Vector(endpoint, position).Unit() * length;
374 position = endpoint + v;
378 Vector v = Vector(position, endpoint).Unit() * length;
379 endpoint = position + v;
384 // Otherwise, we calculate the new length, just in case on the next move
385 // it turns out to have a fixed length. :-)
386 length = Vector(endpoint - position).Magnitude();
391 draggingHandle1 = false;
392 draggingHandle2 = false;
394 // Here we check for just a click: If object was clicked and dragged, then
395 // revert to the old state (OSInactive). Otherwise, keep the new state that
397 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
398 about keeping track of old states...
400 if (objectWasDragged)
405 /*virtual*/ bool Dimension::HitTest(Point point)
407 // Vector orthogonal = Vector::Normal(position, endpoint);
408 Vector orthogonal = Vector::Normal(linePt1, linePt2);
409 // Get our line parallel to our points
411 Point p1 = position + (orthogonal * 10.0 * size);
412 Point p2 = endpoint + (orthogonal * 10.0 * size);
414 Point p1 = linePt1 + (orthogonal * 10.0 * size);
415 Point p2 = linePt2 + (orthogonal * 10.0 * size);
419 hitPoint1 = hitPoint2 = hitLine = hitFlipSwitch = hitChangeSwitch1
420 = hitChangeSwitch2 = false;
421 Vector v1(position, point);
422 Vector v2(endpoint, point);
423 // Vector lineSegment(position, endpoint);
424 Vector lineSegment(p1, p2);
425 // double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
426 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
428 Point midpoint = (p1 + p2) / 2.0;
429 Point hFSPoint = Point(midpoint, point);
430 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
431 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
434 distance = v1.Magnitude();
436 distance = v2.Magnitude();
438 // distance = ?Det?(ls, v1) / |ls|
439 // distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
440 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
441 / lineSegment.Magnitude());
443 if ((v1.Magnitude() * Painter::zoom) < 8.0)
445 else if ((v2.Magnitude() * Painter::zoom) < 8.0)
447 else if ((distance * Painter::zoom) < 5.0)
450 if ((hFSPoint.Magnitude() * Painter::zoom) < 8.0)
451 hitFlipSwitch = true;
452 else if ((hCS1Point.Magnitude() * Painter::zoom) < 8.0)
453 hitChangeSwitch1 = true;
454 else if ((hCS2Point.Magnitude() * Painter::zoom) < 8.0)
455 hitChangeSwitch2 = true;
457 return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
461 void Dimension::SaveHitState(void)
463 oldHitPoint1 = hitPoint1;
464 oldHitPoint2 = hitPoint2;
465 oldHitLine = hitLine;
466 oldHitFlipSwitch = hitFlipSwitch;
467 oldHitChangeSwitch1 = hitChangeSwitch1;
468 oldHitChangeSwitch2 = hitChangeSwitch2;
472 bool Dimension::HitStateChanged(void)
474 if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2)
475 || (hitLine != oldHitLine) || (hitFlipSwitch != oldHitFlipSwitch)
476 || (hitChangeSwitch1 != oldHitChangeSwitch1)
477 || (hitChangeSwitch2 != oldHitChangeSwitch2))
484 /*virtual*/ void Dimension::Enumerate(FILE * file)
486 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, type);
490 /*virtual*/ Object * Dimension::Copy(void)
492 #warning "!!! This doesn't take care of attached Dimensions !!!"
494 This is a real problem. While having a pointer in the Dimension to this line's points
495 is fast & easy, it creates a huge problem when trying to replicate an object like this.
497 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
498 way, if you copy them, ... you might still have problems. Because you can't be sure if
499 a copy will be persistant or not, you then *definitely* do not want them to have the
500 same reference number.
503 Dimension * d = new Dimension(position, endpoint, dimensionType, parent);
509 // Dimensions are special: they contain exactly *two* points. Here, we check
510 // only for zero/non-zero in returning the correct points.
511 /*virtual*/ Vector Dimension::GetPointAtParameter(double parameter)
520 /*virtual*/ void Dimension::MovePointAtParameter(double parameter, Vector v)
524 else if (parameter == 1.0)
527 {} // Not sure how to handle this case :-P
531 /*virtual*/ void Dimension::Connect(Object * obj, double param)
533 // There are four possibilities here...
534 // The param is only looking for 0 or 1 here.
535 if (point1.object == NULL && point2.object == NULL)
540 else if (point1.object == NULL && point2.object != NULL)
542 if (point2.t == param)
550 else if (point1.object != NULL && point2.object == NULL)
552 if (point1.t == param)
560 else if (point1.object != NULL && point2.object != NULL)
562 if (point1.t == param)
570 /*virtual*/ void Dimension::Disconnect(Object * obj, double param)
572 if (point1.object == obj && point1.t == param)
573 point1.object = NULL;
574 else if (point2.object == obj && point2.t == param)
575 point2.object = NULL;
579 /*virtual*/ void Dimension::DisconnectAll(Object * obj)
581 if (point1.object == obj)
582 point1.object = NULL;
584 if (point2.object == obj)
585 point2.object = NULL;
589 /*virtual*/ QRectF Dimension::Extents(void)
594 // if (point1.object)
595 // p1 = point1.object->GetPointAtParameter(point1.t);
597 // if (point2.object)
598 // p2 = point2.object->GetPointAtParameter(point2.t);
600 return QRectF(QPointF(p1.x, p1.y), QPointF(p2.x, p2.y));
605 /*virtual*/ ObjectType Dimension::Type(void)
612 void Dimension::FlipSides(void)
615 Vector tmp = position;
618 //Not sure this matters...
619 //#warning "!!! May need to swap parameter values on connected objects !!!"
621 Connection tmp = point1;
624 // double tmp = point1.t;
625 // point1.t = point2.t;
627 // Object * tmp = point1.object;
628 // point1.object = point2.object;
629 // point2.object = tmp;