]> Shamusworld >> Repos - architektonas/blob - src/painter.cpp
Added line-to-circle intersection code.
[architektonas] / src / painter.cpp
1 //
2 // painter.cpp: Paint abstraction layer between Archtektonas and Qt
3 //
4 // Part of the Architektonas Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // WHO  WHEN        WHAT
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  09/20/2011  Created this file
13 //
14
15 #include "painter.h"
16 #include "global.h"
17 #include "mathconstants.h"
18
19
20 // Set class variable defaults
21 //Vector Painter::origin(-10.0, -10.0);
22 //double Painter::zoom = 1.0;
23 //Vector Painter::screenSize(200.0, 200.0);
24
25
26 Painter::Painter(QPainter * p/*= NULL*/): painter(p)
27 {
28 }
29
30
31 Painter::~Painter()
32 {
33 }
34
35
36 Vector Painter::CartesianToQtCoords(Vector v)
37 {
38         // Convert regular Cartesian coordinates to the inverted Y-axis Qt coordinates
39         // at the current origin and zoom level.
40         return Vector((v.x - Global::origin.x) * Global::zoom, Global::screenSize.y - ((v.y - Global::origin.y) * Global::zoom));
41 }
42
43
44 Vector Painter::QtToCartesianCoords(Vector v)
45 {
46         // Convert screen location, with inverted Y-axis coordinates, to regular
47         // Cartesian coordinates at the current zoom level.
48         return Vector((v.x / Global::zoom) + Global::origin.x, ((Global::screenSize.y - v.y) / Global::zoom) + Global::origin.y);
49 /*
50 How to do it:
51
52 e.g., we have a point on the screen at Qt coords of 10, 10, screenSize is 100, 100.
53 origin is -10, -10 and zoom level is 2 (200%)
54
55 1st, invert the Y: 10, 10 -> 10, 90
56 2nd, add origin:   10, 90 ->  0, 80 (no, not right--err, yes, it is)
57 3rd, aply zoom:     0, 80 ->  0, 40
58
59 or, is it:
60
61 1st, invert the Y: 10, 10 -> 10, 90
62 2nd, aply zoom:    10, 90 ->  5, 45
63 3rd, add origin:    5, 45 -> -5, 35
64
65 it depends on whether or not origin is in Qt coords or cartesian. If Qt, then the 1st
66 is correct, otherwise, the 2nd is correct.
67
68 The way we calculate the Cartesian to Qt shows the 2nd (origin is cartesian) to be correct.
69 */
70 }
71
72
73 void Painter::SetRenderHint(int hint)
74 {
75         if (!painter)
76                 return;
77
78         painter->setRenderHint((QPainter::RenderHint)hint);
79 }
80
81
82 void Painter::SetPen(QPen pen)
83 {
84         if (!painter)
85                 return;
86
87         painter->setPen(pen);
88 }
89
90
91 void Painter::SetPen(uint32_t color, float size/*= 0*/, int style/*= 0*/)
92 {
93         if (!painter)
94                 return;
95
96         // We can cast style as Qt:PenStyle because they line up 1-to-1
97         painter->setPen(QPen(QColor(color >> 16, (color >> 8) & 0xFF, color & 0xFF, 255),
98                 size, (Qt::PenStyle)style));
99 }
100
101
102 void Painter::SetBrush(QBrush brush)
103 {
104         if (!painter)
105                 return;
106
107         painter->setBrush(brush);
108 }
109
110
111 void Painter::SetBrush(uint32_t color)
112 {
113         if (!painter)
114                 return;
115
116         painter->setBrush(QBrush(QColor(color >> 16, (color >> 8) & 0xFF, color & 0xFF, 255)));
117 }
118
119
120 void Painter::SetFont(QFont font)
121 {
122         if (!painter)
123                 return;
124
125         painter->setFont(font);
126 }
127
128
129 void Painter::DrawAngledText(Vector center, double angle, QString text, double size)
130 {
131         if (!painter)
132                 return;
133
134         // Strategy: Since Qt doesn't have any rotated text drawing functions,
135         // we instead translate the origin to the center of the text to be drawn and
136         // then rotate the frame to the desired angle.
137         center = CartesianToQtCoords(center);
138
139         // We may need this stuff... If dimension text is large enough.
140 //      int textWidth = QFontMetrics(painter->font()).width(text);
141 //      int textHeight = QFontMetrics(painter->font()).height();
142         QRectF textBox(-100.0 * Global::zoom * size, -100.0 * Global::zoom * size, 200.0 * Global::zoom * size, 200.0 * Global::zoom * size);   // x, y, w, h; x/y = upper left corner
143
144         // This is in pixels. Might not render correctly at all zoom levels.
145         // Need to figure out if dimensions are always rendered at one size
146         // regardless of zoom, or if they have a definite size, and are thus
147         // zoomable.
148         float yOffset = -12.0 * Global::zoom * size;
149
150         // Fix text so it isn't upside down...
151 #if 0
152         if ((angle > PI * 0.5) && (angle < PI * 1.5))
153         {
154                 angle += PI;
155                 yOffset = 12.0 * Global::zoom * size;
156         }
157 #else
158         if ((angle > QTR_TAU) && (angle < THREE_QTR_TAU))
159         {
160                 angle += HALF_TAU;
161                 yOffset = 12.0 * Global::zoom * size;
162         }
163 #endif
164
165         textBox.translate(0, yOffset);
166         painter->save();
167         painter->translate(center.x, center.y);
168         // Angles are backwards in the Qt coord system, so we flip ours...
169         painter->rotate(-angle * RADIANS_TO_DEGREES);
170 //Need to fix this so the text scales as well...
171         painter->drawText(textBox, Qt::AlignCenter, text);
172         painter->restore();
173 }
174
175
176 //
177 // Draw angled text. Draws text using point p as the upper left corner.
178 // Size is point size, angle is in radians (defaults to 0).
179 //
180 void Painter::DrawTextObject(Point p, QString text, double size, double angle/*= 0*/)
181 {
182         if (!painter)
183                 return;
184
185         p = CartesianToQtCoords(p);
186         painter->setFont(QFont("Arial", Global::zoom * size));
187         int textWidth = QFontMetrics(painter->font()).width(text);
188         int textHeight = QFontMetrics(painter->font()).height();
189
190         QRectF textBox(0, 0, textWidth, textHeight);
191         painter->save();
192         painter->translate(p.x, p.y);
193         // Angles are backwards in the Qt coord system, so we flip ours...
194         painter->rotate(-angle * RADIANS_TO_DEGREES);
195         painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop , text);
196         painter->restore();
197 }
198
199
200 void Painter::DrawArc(Vector center, double radius, double startAngle, double span)
201 {
202         center = CartesianToQtCoords(center);
203         // Need to multiply scalar quantities by the zoom factor as well...
204         radius *= Global::zoom;
205         QRectF rectangle(QPointF(center.x - radius, center.y - radius),
206                 QPointF(center.x + radius, center.y + radius));
207         int angle1 = (int)(startAngle * RADIANS_TO_DEGREES * 16.0);
208         int angle2 = (int)(span * RADIANS_TO_DEGREES * 16.0);
209         painter->drawArc(rectangle, angle1, angle2);
210 }
211
212
213 void Painter::DrawEllipse(Vector center, double axis1, double axis2)
214 {
215         // Need to multiply scalar quantities by the zoom factor as well...
216         center = CartesianToQtCoords(center);
217         painter->drawEllipse(QPointF(center.x, center.y), axis1 * Global::zoom, axis2 * Global::zoom);
218 }
219
220
221 // This function is for drawing object handles without regard for zoom level;
222 // we don't want our object handle size to depend on the zoom level!
223 void Painter::DrawHandle(Vector center)
224 {
225         center = CartesianToQtCoords(center);
226         painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
227         painter->setBrush(Qt::NoBrush);
228         painter->drawEllipse(QPointF(center.x, center.y), 4.0, 4.0);
229 }
230
231
232 // This function is for drawing object handles without regard for zoom level;
233 // we don't want our object handle size to depend on the zoom level!
234 void Painter::DrawArrowHandle(Vector center, double angle)
235 {
236         center = CartesianToQtCoords(center);
237         QPolygonF arrow;
238
239         // Since we're drawing directly on the screen, the Y is inverted. So we use
240         // the mirror of the angle.
241         double orthoAngle = -angle + QTR_TAU;//(PI / 2.0);
242         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
243         Vector unit = Vector(cos(-angle), sin(-angle));
244
245         Point p0 = center + (unit * 6.0);
246         Point p1 = center + (unit * 21.0);
247         Point p1b = center + (unit * 11.0);
248         Point p2 = p1b + (orthogonal * 5.0);
249         Point p3 = p1b - (orthogonal * 5.0);
250
251         painter->drawLine(p0.x, p0.y, p1.x, p1.y);
252         arrow << QPointF(p1.x, p1.y) << QPointF(p2.x, p2.y) << QPointF(p3.x, p3.y);
253
254         painter->drawPolygon(arrow);
255 }
256
257
258 // This function is for drawing object handles without regard for zoom level;
259 // we don't want our object handle size to depend on the zoom level!
260 void Painter::DrawArrowToLineHandle(Vector center, double angle)
261 {
262         DrawArrowHandle(center, angle);
263         center = CartesianToQtCoords(center);
264
265         // Since we're drawing directly on the screen, the Y is inverted. So we use
266         // the mirror of the angle.
267         double orthoAngle = -angle + QTR_TAU;//(PI / 2.0);
268         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
269         Vector unit = Vector(cos(-angle), sin(-angle));
270
271         Point p1 = center + (unit * 21.0);
272         Point p2 = p1 + (orthogonal * 7.0);
273         Point p3 = p1 - (orthogonal * 7.0);
274
275         painter->drawLine(p2.x, p2.y, p3.x, p3.y);
276 }
277
278
279 void Painter::DrawLine(int x1, int y1, int x2, int y2)
280 {
281         if (!painter)
282                 return;
283
284         Vector v1 = CartesianToQtCoords(Vector(x1, y1));
285         Vector v2 = CartesianToQtCoords(Vector(x2, y2));
286         painter->drawLine(v1.x, v1.y, v2.x, v2.y);
287 }
288
289
290 void Painter::DrawLine(Vector v1, Vector v2)
291 {
292         if (!painter)
293                 return;
294
295         v1 = CartesianToQtCoords(v1);
296         v2 = CartesianToQtCoords(v2);
297         painter->drawLine(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
298 }
299
300
301 void Painter::DrawPoint(int x, int y)
302 {
303         if (!painter)
304                 return;
305
306         Vector v = CartesianToQtCoords(Vector(x, y));
307         painter->drawPoint(v.x, v.y);
308 }
309
310
311 // The rect passed in is in Qt coordinates...
312 void Painter::DrawRoundedRect(QRectF rect, double radiusX, double radiusY)
313 {
314         if (!painter)
315                 return;
316
317         painter->drawRoundedRect(rect, radiusX, radiusY);
318 }
319
320
321 // The rect passed in is in Cartesian but we want to pad it by a set number of
322 // pixels (currently set at 8), so the pad looks the same regardless of zoom.
323 void Painter::DrawPaddedRect(QRectF rect)
324 {
325         if (!painter)
326                 return;
327
328         Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y()));
329         Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom()));
330         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
331         screenRect.adjust(-8, 8, 8, -8);        // Left/top, right/bottom
332         painter->drawRect(screenRect);
333 }
334
335
336 void Painter::DrawRect(QRectF rect)
337 {
338         if (!painter)
339                 return;
340
341         Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y()));
342         Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom()));
343         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
344         painter->drawRect(screenRect);
345 }
346
347
348 void Painter::DrawText(QRectF rect, int type, QString text)
349 {
350         if (!painter)
351                 return;
352
353         painter->drawText(rect, (Qt::AlignmentFlag)type, text);
354 }
355
356
357 void Painter::DrawArrowhead(Vector head, Vector tail, double size)
358 {
359         if (!painter)
360                 return;
361
362         QPolygonF arrow;
363
364         // We draw the arrowhead aligned along the line from tail to head
365         double angle = Vector(head - tail).Angle();
366         double orthoAngle = angle + QTR_TAU;//(PI / 2.0);
367         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
368         Vector unit = Vector(head - tail).Unit();
369
370         Point p1 = head - (unit * 9.0 * size);
371         Point p2 = p1 + (orthogonal * 3.0 * size);
372         Point p3 = p1 - (orthogonal * 3.0 * size);
373
374         Point p4 = CartesianToQtCoords(head);
375         Point p5 = CartesianToQtCoords(p2);
376         Point p6 = CartesianToQtCoords(p3);
377
378         arrow << QPointF(p4.x, p4.y) << QPointF(p5.x, p5.y) << QPointF(p6.x, p6.y);
379
380         painter->drawPolygon(arrow);
381 }
382
383
384 // Point is given in Cartesian coordinates
385 void Painter::DrawCrosshair(Vector point)
386 {
387         if (!painter)
388                 return;
389
390         Vector screenPoint = CartesianToQtCoords(point);
391         painter->drawLine(0, screenPoint.y, Global::screenSize.x, screenPoint.y);
392         painter->drawLine(screenPoint.x, 0, screenPoint.x, Global::screenSize.y);
393 }
394
395
396 void Painter::DrawInformativeText(QString text)
397 {
398         painter->setFont(*Global::font);
399         QRectF bounds = painter->boundingRect(QRectF(), Qt::AlignVCenter, text);
400         bounds.moveTo(17.0, 17.0);
401         QRectF textRect = bounds;
402         textRect.adjust(-7.0, -7.0, 7.0, 7.0);
403
404         QPen pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
405         painter->setPen(pen);
406         painter->setBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
407         painter->drawRoundedRect(textRect, 7.0, 7.0);
408
409         pen = QPen(QColor(0x00, 0x5F, 0xDF));
410         painter->setPen(pen);
411         painter->drawText(bounds, Qt::AlignVCenter, text);
412 }
413