Browse code

Share the line drawing code between StampBrush and TerrainBrush

I've introduced a new module called 'geometry' which now contains
rasterization of ellipse and line, as well as the 'coherent regions'
function which existed in the 'utils' module.

Thorbjørn Lindeijer authored on 09/06/2012 20:01:12
Showing 9 changed files
... ...
@@ -25,6 +25,7 @@
25 25
 #include "addremovetileset.h"
26 26
 #include "automappingutils.h"
27 27
 #include "changeproperties.h"
28
+#include "geometry.h"
28 29
 #include "layermodel.h"
29 30
 #include "map.h"
30 31
 #include "mapdocument.h"
... ...
@@ -35,13 +36,11 @@
35 36
 #include "tilelayer.h"
36 37
 #include "tileset.h"
37 38
 #include "tilesetmanager.h"
38
-#include "utils.h"
39 39
 
40 40
 #include <QDebug>
41 41
 
42 42
 using namespace Tiled;
43 43
 using namespace Tiled::Internal;
44
-using namespace Tiled::Utils;
45 44
 
46 45
 /*
47 46
  * About the order of the methods in this file.
48 47
new file mode 100644
... ...
@@ -0,0 +1,195 @@
1
+/*
2
+ * geometry.cpp
3
+ * Copyright 2010-2011, Stefan Beller <stefanbeller@googlemail.com>
4
+ *
5
+ * This file is part of Tiled.
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify it
8
+ * under the terms of the GNU General Public License as published by the Free
9
+ * Software Foundation; either version 2 of the License, or (at your option)
10
+ * any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful, but WITHOUT
13
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15
+ * more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along with
18
+ * this program. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+#include "geometry.h"
22
+
23
+namespace Tiled {
24
+
25
+/**
26
+ * Returns a lists of points on an ellipse.
27
+ * (x0,y0) is the midpoint
28
+ * (x1,y1) determines the radius.
29
+ *
30
+ * It is adapted from http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
31
+ * here is the orginal: http://homepage.smc.edu/kennedy_john/belipse.pdf
32
+ */
33
+QVector<QPoint> pointsOnEllipse(int x0, int y0, int x1, int y1)
34
+{
35
+    QVector<QPoint> ret;
36
+    int x, y;
37
+    int xChange, yChange;
38
+    int ellipseError;
39
+    int twoXSquare, twoYSquare;
40
+    int stoppingX, stoppingY;
41
+    int radiusX = x0 > x1 ? x0 - x1 : x1 - x0;
42
+    int radiusY = y0 > y1 ? y0 - y1 : y1 - y0;
43
+
44
+    if (radiusX == 0 && radiusY == 0)
45
+        return ret;
46
+
47
+    twoXSquare = 2 * radiusX * radiusX;
48
+    twoYSquare = 2 * radiusY * radiusY;
49
+    x = radiusX;
50
+    y = 0;
51
+    xChange = radiusY * radiusY * (1 - 2 * radiusX);
52
+    yChange = radiusX * radiusX;
53
+    ellipseError = 0;
54
+    stoppingX = twoYSquare*radiusX;
55
+    stoppingY = 0;
56
+    while (stoppingX >= stoppingY) {
57
+        ret += QPoint(x0 + x, y0 + y);
58
+        ret += QPoint(x0 - x, y0 + y);
59
+        ret += QPoint(x0 + x, y0 - y);
60
+        ret += QPoint(x0 - x, y0 - y);
61
+        y++;
62
+        stoppingY += twoXSquare;
63
+        ellipseError += yChange;
64
+        yChange += twoXSquare;
65
+        if ((2 * ellipseError + xChange) > 0) {
66
+            x--;
67
+            stoppingX -= twoYSquare;
68
+            ellipseError += xChange;
69
+            xChange += twoYSquare;
70
+        }
71
+    }
72
+    x = 0;
73
+    y = radiusY;
74
+    xChange = radiusY * radiusY;
75
+    yChange = radiusX * radiusX * (1 - 2 * radiusY);
76
+    ellipseError = 0;
77
+    stoppingX = 0;
78
+    stoppingY = twoXSquare * radiusY;
79
+    while (stoppingX <= stoppingY) {
80
+        ret += QPoint(x0 + x, y0 + y);
81
+        ret += QPoint(x0 - x, y0 + y);
82
+        ret += QPoint(x0 + x, y0 - y);
83
+        ret += QPoint(x0 - x, y0 - y);
84
+        x++;
85
+        stoppingX += twoYSquare;
86
+        ellipseError += xChange;
87
+        xChange += twoYSquare;
88
+        if ((2 * ellipseError + yChange) > 0) {
89
+            y--;
90
+            stoppingY -= twoXSquare;
91
+            ellipseError += yChange;
92
+            yChange += twoXSquare;
93
+        }
94
+    }
95
+
96
+    return ret;
97
+}
98
+
99
+/**
100
+ * Returns the lists of points on a line from (x0,y0) to (x1,y1).
101
+ *
102
+ * This is an implementation of bresenhams line algorithm, initially copied
103
+ * from http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Optimization
104
+ * changed to C++ syntax.
105
+ */
106
+QVector<QPoint> pointsOnLine(int x0, int y0, int x1, int y1)
107
+{
108
+    QVector<QPoint> ret;
109
+
110
+    bool steep = qAbs(y1 - y0) > qAbs(x1 - x0);
111
+    if (steep) {
112
+        qSwap(x0, y0);
113
+        qSwap(x1, y1);
114
+    }
115
+    if (x0 > x1) {
116
+        qSwap(x0, x1);
117
+        qSwap(y0, y1);
118
+    }
119
+    const int deltax = x1 - x0;
120
+    const int deltay = qAbs(y1 - y0);
121
+    int error = deltax / 2;
122
+    int ystep;
123
+    int y = y0;
124
+
125
+    if (y0 < y1)
126
+        ystep = 1;
127
+    else
128
+        ystep = -1;
129
+
130
+    for (int x = x0; x < x1 + 1 ; x++) {
131
+        if (steep)
132
+            ret += QPoint(y, x);
133
+        else
134
+            ret += QPoint(x, y);
135
+        error = error - deltay;
136
+        if (error < 0) {
137
+             y = y + ystep;
138
+             error = error + deltax;
139
+        }
140
+    }
141
+
142
+    return ret;
143
+}
144
+
145
+/**
146
+ * Checks if a given rectangle \a rect is coherent to another given \a region.
147
+ * 'coherent' means that either the rectangle is overlapping the region or
148
+ * the rectangle contains at least one tile, which is a direct neighbour
149
+ * to a tile, which belongs to the region.
150
+ */
151
+static bool isCoherentTo(const QRect &rect, const QRegion &region)
152
+{
153
+    // check if the region is coherent at top or bottom
154
+    if (region.intersects(rect.adjusted(0, -1, 0, 1)))
155
+        return true;
156
+
157
+    // check if the region is coherent at left or right side
158
+    if (region.intersects(rect.adjusted(-1, 0, 1, 0)))
159
+        return true;
160
+
161
+    return false;
162
+}
163
+
164
+/**
165
+ * Calculates all coherent regions occupied by the given \a region.
166
+ * Returns a list of regions, where each region is coherent in itself.
167
+ */
168
+QList<QRegion> coherentRegions(const QRegion &region)
169
+{
170
+    QList<QRegion> result;
171
+    QVector<QRect> rects = region.rects();
172
+
173
+    while (!rects.isEmpty()) {
174
+        QRegion newCoherentRegion = rects.last();
175
+        rects.pop_back();
176
+
177
+        // Add up all coherent rects until there is no rect left which is
178
+        // coherent to the newly created region.
179
+        bool foundRect = true;
180
+        while (foundRect) {
181
+            foundRect = false;
182
+            for (int i = rects.size() - 1; i >= 0; --i) {
183
+                if (isCoherentTo(rects.at(i), newCoherentRegion)) {
184
+                    newCoherentRegion += rects.at(i);
185
+                    rects.remove(i);
186
+                    foundRect = true;
187
+                }
188
+            }
189
+        }
190
+        result += newCoherentRegion;
191
+    }
192
+    return result;
193
+}
194
+
195
+} // namespace Tiled
0 196
new file mode 100644
... ...
@@ -0,0 +1,37 @@
1
+/*
2
+ * geometry.h
3
+ * Copyright 2010-2011, Stefan Beller <stefanbeller@googlemail.com>
4
+ *
5
+ * This file is part of Tiled.
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify it
8
+ * under the terms of the GNU General Public License as published by the Free
9
+ * Software Foundation; either version 2 of the License, or (at your option)
10
+ * any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful, but WITHOUT
13
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15
+ * more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along with
18
+ * this program. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+#ifndef GEOMETRY_H
22
+#define GEOMETRY_H
23
+
24
+#include <QPoint>
25
+#include <QRegion>
26
+#include <QVector>
27
+
28
+namespace Tiled {
29
+
30
+QVector<QPoint> pointsOnEllipse(int x0, int y0, int x1, int y1);
31
+QVector<QPoint> pointsOnLine(int x0, int y0, int x1, int y1);
32
+
33
+QList<QRegion> coherentRegions(const QRegion &region);
34
+
35
+} // namespace Tiled
36
+
37
+#endif // GEOMETRY_H
... ...
@@ -22,6 +22,7 @@
22 22
 #include "stampbrush.h"
23 23
 
24 24
 #include "brushitem.h"
25
+#include "geometry.h"
25 26
 #include "map.h"
26 27
 #include "mapdocument.h"
27 28
 #include "mapscene.h"
... ...
@@ -54,126 +55,6 @@ StampBrush::~StampBrush()
54 55
     delete mStamp;
55 56
 }
56 57
 
57
-
58
-/**
59
- * Returns a lists of points on an ellipse.
60
- * (x0,y0) is the midpoint
61
- * (x1,y1) to determines the radius.
62
- * It is adapted from http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
63
- * here is the orginal: http://homepage.smc.edu/kennedy_john/belipse.pdf
64
- */
65
-static QVector<QPoint> rasterEllipse(int x0, int y0, int x1, int y1)
66
-{
67
-    QVector<QPoint> ret;
68
-    int x, y;
69
-    int xChange, yChange;
70
-    int ellipseError;
71
-    int twoXSquare, twoYSquare;
72
-    int stoppingX, stoppingY;
73
-    int radiusX = x0 > x1 ? x0 - x1 : x1 - x0;
74
-    int radiusY = y0 > y1 ? y0 - y1 : y1 - y0;
75
-
76
-    if (radiusX == 0 && radiusY == 0)
77
-        return ret;
78
-
79
-    twoXSquare = 2 * radiusX * radiusX;
80
-    twoYSquare = 2 * radiusY * radiusY;
81
-    x = radiusX;
82
-    y = 0;
83
-    xChange = radiusY * radiusY * (1 - 2 * radiusX);
84
-    yChange = radiusX * radiusX;
85
-    ellipseError = 0;
86
-    stoppingX = twoYSquare*radiusX;
87
-    stoppingY = 0;
88
-    while ( stoppingX >= stoppingY ) {
89
-        ret += QPoint(x0 + x, y0 + y);
90
-        ret += QPoint(x0 - x, y0 + y);
91
-        ret += QPoint(x0 + x, y0 - y);
92
-        ret += QPoint(x0 - x, y0 - y);
93
-        y++;
94
-        stoppingY += twoXSquare;
95
-        ellipseError += yChange;
96
-        yChange += twoXSquare;
97
-        if ((2 * ellipseError + xChange) > 0 ) {
98
-            x--;
99
-            stoppingX -= twoYSquare;
100
-            ellipseError += xChange;
101
-            xChange += twoYSquare;
102
-        }
103
-    }
104
-    x = 0;
105
-    y = radiusY;
106
-    xChange = radiusY * radiusY;
107
-    yChange = radiusX * radiusX * (1 - 2 * radiusY);
108
-    ellipseError = 0;
109
-    stoppingX = 0;
110
-    stoppingY = twoXSquare * radiusY;
111
-    while ( stoppingX <= stoppingY ) {
112
-        ret += QPoint(x0 + x, y0 + y);
113
-        ret += QPoint(x0 - x, y0 + y);
114
-        ret += QPoint(x0 + x, y0 - y);
115
-        ret += QPoint(x0 - x, y0 - y);
116
-        x++;
117
-        stoppingX += twoYSquare;
118
-        ellipseError += xChange;
119
-        xChange += twoYSquare;
120
-        if ((2 * ellipseError + yChange) > 0 ) {
121
-            y--;
122
-            stoppingY -= twoXSquare;
123
-            ellipseError += yChange;
124
-            yChange += twoXSquare;
125
-        }
126
-    }
127
-
128
-    return ret;
129
-}
130
-
131
-/**
132
- * Returns the lists of points on a line from (x0,y0) to (x1,y1).
133
- *
134
- * This is an implementation of bresenhams line algorithm, initially copied
135
- * from http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Optimization
136
- * changed to C++ syntax.
137
- */
138
-static QVector<QPoint> calculateLine(int x0, int y0, int x1, int y1)
139
-{
140
-    QVector<QPoint> ret;
141
-
142
-    bool steep = qAbs(y1 - y0) > qAbs(x1 - x0);
143
-    if (steep) {
144
-        qSwap(x0, y0);
145
-        qSwap(x1, y1);
146
-    }
147
-    if (x0 > x1) {
148
-        qSwap(x0, x1);
149
-        qSwap(y0, y1);
150
-    }
151
-    const int deltax = x1 - x0;
152
-    const int deltay = qAbs(y1 - y0);
153
-    int error = deltax / 2;
154
-    int ystep;
155
-    int y = y0;
156
-
157
-    if (y0 < y1)
158
-        ystep = 1;
159
-    else
160
-        ystep = -1;
161
-
162
-    for (int x = x0; x < x1 + 1 ; x++) {
163
-        if (steep)
164
-            ret += QPoint(y, x);
165
-        else
166
-            ret += QPoint(x, y);
167
-        error = error - deltay;
168
-        if (error < 0) {
169
-             y = y + ystep;
170
-             error = error + deltax;
171
-        }
172
-    }
173
-
174
-    return ret;
175
-}
176
-
177 58
 void StampBrush::tilePositionChanged(const QPoint &)
178 59
 {
179 60
     const int x = mStampX;
... ...
@@ -181,16 +62,16 @@ void StampBrush::tilePositionChanged(const QPoint &)
181 62
     updatePosition();
182 63
     switch (mBrushBehavior) {
183 64
     case Paint:
184
-        foreach (const QPoint &p, calculateLine(x, y, mStampX, mStampY))
65
+        foreach (const QPoint &p, pointsOnLine(x, y, mStampX, mStampY))
185 66
             doPaint(true, p.x(), p.y());
186 67
         break;
187 68
     case LineStartSet:
188
-        configureBrush(calculateLine(mStampReferenceX, mStampReferenceY,
189
-                                     mStampX, mStampY));
69
+        configureBrush(pointsOnLine(mStampReferenceX, mStampReferenceY,
70
+                                    mStampX, mStampY));
190 71
         break;
191 72
     case CircleMidSet:
192
-        configureBrush(rasterEllipse(mStampReferenceX, mStampReferenceY,
193
-                                     mStampX, mStampY));
73
+        configureBrush(pointsOnEllipse(mStampReferenceX, mStampReferenceY,
74
+                                       mStampX, mStampY));
194 75
         break;
195 76
     case Capture:
196 77
         brushItem()->setTileRegion(capturedArea());
... ...
@@ -23,6 +23,7 @@
23 23
 #include "terrainbrush.h"
24 24
 
25 25
 #include "brushitem.h"
26
+#include "geometry.h"
26 27
 #include "map.h"
27 28
 #include "mapdocument.h"
28 29
 #include "mapscene.h"
... ...
@@ -59,53 +60,6 @@ TerrainBrush::~TerrainBrush()
59 60
 {
60 61
 }
61 62
 
62
-
63
-/**
64
- * Returns the lists of points on a line from (x0,y0) to (x1,y1).
65
- *
66
- * This is an implementation of bresenhams line algorithm, initially copied
67
- * from http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Optimization
68
- * changed to C++ syntax.
69
- */
70
-static QVector<QPoint> calculateLine(int x0, int y0, int x1, int y1)
71
-{
72
-    QVector<QPoint> ret;
73
-
74
-    bool steep = qAbs(y1 - y0) > qAbs(x1 - x0);
75
-    if (steep) {
76
-        qSwap(x0, y0);
77
-        qSwap(x1, y1);
78
-    }
79
-    if (x0 > x1) {
80
-        qSwap(x0, x1);
81
-        qSwap(y0, y1);
82
-    }
83
-    const int deltax = x1 - x0;
84
-    const int deltay = qAbs(y1 - y0);
85
-    int error = deltax / 2;
86
-    int ystep;
87
-    int y = y0;
88
-
89
-    if (y0 < y1)
90
-        ystep = 1;
91
-    else
92
-        ystep = -1;
93
-
94
-    for (int x = x0; x < x1 + 1 ; x++) {
95
-        if (steep)
96
-            ret += QPoint(y, x);
97
-        else
98
-            ret += QPoint(x, y);
99
-        error = error - deltay;
100
-        if (error < 0) {
101
-             y = y + ystep;
102
-             error = error + deltax;
103
-        }
104
-    }
105
-
106
-    return ret;
107
-}
108
-
109 63
 void TerrainBrush::tilePositionChanged(const QPoint &pos)
110 64
 {
111 65
     switch (mBrushBehavior) {
... ...
@@ -113,7 +67,7 @@ void TerrainBrush::tilePositionChanged(const QPoint &pos)
113 67
     {
114 68
         int x = mPaintX;
115 69
         int y = mPaintY;
116
-        foreach (const QPoint &p, calculateLine(x, y, pos.x(), pos.y())) {
70
+        foreach (const QPoint &p, pointsOnLine(x, y, pos.x(), pos.y())) {
117 71
             updateBrush(p);
118 72
             doPaint(true, p.x(), p.y());
119 73
         }
... ...
@@ -124,7 +78,8 @@ void TerrainBrush::tilePositionChanged(const QPoint &pos)
124 78
     }
125 79
     case LineStartSet:
126 80
     {
127
-        QVector<QPoint> lineList = calculateLine(mLineReferenceX, mLineReferenceY, pos.x(), pos.y());
81
+        QVector<QPoint> lineList = pointsOnLine(mLineReferenceX, mLineReferenceY,
82
+                                                pos.x(), pos.y());
128 83
         updateBrush(pos, &lineList);
129 84
         break;
130 85
     }
... ...
@@ -110,7 +110,7 @@ private:
110 110
     void updateBrush(QPoint cursorPos, const QVector<QPoint> *list = NULL);
111 111
 
112 112
     /**
113
-     * mTerrain is the terrain we are currently painting
113
+     * The terrain we are currently painting.
114 114
      */
115 115
     const Terrain *mTerrain;
116 116
     int mPaintX, mPaintY;
... ...
@@ -72,6 +72,7 @@ SOURCES += aboutdialog.cpp \
72 72
     erasetiles.cpp \
73 73
     filesystemwatcher.cpp \
74 74
     filltiles.cpp \
75
+    geometry.cpp \
75 76
     imagelayeritem.cpp \
76 77
     imagelayerpropertiesdialog.cpp \
77 78
     languagemanager.cpp \
... ...
@@ -172,6 +173,7 @@ HEADERS += aboutdialog.h \
172 173
     erasetiles.h \
173 174
     filesystemwatcher.h \
174 175
     filltiles.h \
176
+    geometry.h \
175 177
     imagelayeritem.h \
176 178
     imagelayerpropertiesdialog.h \
177 179
     languagemanager.h \
... ...
@@ -42,25 +42,6 @@ static QString toImageFileFilter(const QList<QByteArray> &formats)
42 42
     return filter;
43 43
 }
44 44
 
45
-/**
46
- * Checks if a given rectangle \a rect is coherent to another given \a region.
47
- * 'coherent' means that either the rectangle is overlapping the region or
48
- * the rectangle contains at least one tile, which is a direct neighbour
49
- * to a tile, which belongs to the region.
50
- */
51
-static bool isCoherentTo(const QRect &rect, const QRegion &region)
52
-{
53
-    // check if the region is coherent at top or bottom
54
-    if (region.intersects(rect.adjusted(0, -1, 0, 1)))
55
-        return true;
56
-
57
-    // check if the region is coherent at left or right side
58
-    if (region.intersects(rect.adjusted(-1, 0, 1, 0)))
59
-        return true;
60
-
61
-    return false;
62
-}
63
-
64 45
 namespace Tiled {
65 46
 namespace Utils {
66 47
 
... ...
@@ -74,32 +55,5 @@ QString writableImageFormatsFilter()
74 55
     return toImageFileFilter(QImageWriter::supportedImageFormats());
75 56
 }
76 57
 
77
-QList<QRegion> coherentRegions(const QRegion &region)
78
-{
79
-    QList<QRegion> result;
80
-    QVector<QRect> rects = region.rects();
81
-
82
-    while (!rects.isEmpty()) {
83
-        QRegion newCoherentRegion = rects.last();
84
-        rects.pop_back();
85
-
86
-        // Add up all coherent rects until there is no rect left which is
87
-        // coherent to the newly created region.
88
-        bool foundRect = true;
89
-        while (foundRect) {
90
-            foundRect = false;
91
-            for (int i = rects.size() - 1; i >= 0; --i) {
92
-                if (isCoherentTo(rects.at(i), newCoherentRegion)) {
93
-                    newCoherentRegion += rects.at(i);
94
-                    rects.remove(i);
95
-                    foundRect = true;
96
-                }
97
-            }
98
-        }
99
-        result += newCoherentRegion;
100
-    }
101
-    return result;
102
-}
103
-
104 58
 } // namespace Utils
105 59
 } // namespace Tiled
... ...
@@ -41,12 +41,6 @@ QString readableImageFormatsFilter();
41 41
 QString writableImageFormatsFilter();
42 42
 
43 43
 /**
44
- * Calculates all coherent regions occupied by the given \a region.
45
- * Returns a list of regions, where each region is coherent in itself.
46
- */
47
-QList<QRegion> coherentRegions(const QRegion &region);
48
-
49
-/**
50 44
  * Looks up the icon with the specified \a name from the system theme and set
51 45
  * it on the instance \a t when found.
52 46
  *