1 package org.djutils.draw;
2
3 import java.util.Arrays;
4 import java.util.Iterator;
5
6 import org.djutils.draw.bounds.Bounds2d;
7 import org.djutils.draw.point.Point2d;
8
9 /**
10 * Transform2d contains a MUTABLE transformation object that can transform points (x,y) based on e.g, rotation and translation.
11 * It uses an affine transform matrix that can be built up from different components (translation, rotation, scaling,
12 * reflection, shearing).
13 * <p>
14 * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
15 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
16 * </p>
17 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
18 * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
19 */
20 public class Transform2d implements Cloneable
21 {
22 /** The 3x3 transformation matrix, initialized as the Identity matrix. */
23 private double[] mat = new double[] {1, 0, 0, 0, 1, 0, 0, 0, 1};
24
25 /**
26 * Multiply a 3x3 matrix (stored as a 9-value array by row) with a 4-value vector.
27 * @param m double[]; the matrix
28 * @param v double[]; the vector
29 * @return double[3]; the result of m x v
30 */
31 protected static double[] mulMatVec(final double[] m, final double[] v)
32 {
33 double[] result = new double[3];
34 for (int i = 0; i < 3; i++)
35 {
36 result[i] = m[3 * i] * v[0] + m[3 * i + 1] * v[1] + m[3 * i + 2] * v[2];
37 }
38 return result;
39 }
40
41 /**
42 * Multiply a 3x3 matrix (stored as a 9-value array by row) with a 3-value vector and a 1 for the 3rd value.
43 * @param m double[]; the matrix
44 * @param v double[]; the vector
45 * @return double[2]; the result of m x (v1, v2, 1), with the last value left out
46 */
47 protected static double[] mulMatVec2(final double[] m, final double[] v)
48 {
49 double[] result = new double[2];
50 for (int i = 0; i < 2; i++)
51 {
52 result[i] = m[3 * i] * v[0] + m[3 * i + 1] * v[1] + m[3 * i + 2];
53 }
54 return result;
55 }
56
57 /**
58 * Multiply a 3x3 matrix (stored as a 9-value array by row) with another 3x3-matrix.
59 * @param m1 double[]; the first matrix
60 * @param m2 double[]; the second matrix
61 * @return double[9]; the result of m1 x m2
62 */
63 protected static double[] mulMatMat(final double[] m1, final double[] m2)
64 {
65 double[] result = new double[9];
66 for (int i = 0; i < 3; i++)
67 {
68 for (int j = 0; j < 3; j++)
69 {
70 result[3 * i + j] = m1[3 * i] * m2[j] + m1[3 * i + 1] * m2[j + 3] + +m1[3 * i + 2] * m2[j + 6];
71 }
72 }
73 return result;
74 }
75
76 /**
77 * Get a safe copy of the affine transformation matrix.
78 * @return double[]; a safe copy of the affine transformation matrix
79 */
80 public double[] getMat()
81 {
82 return this.mat.clone();
83 }
84
85 /**
86 * Transform coordinates by a vector (tx, ty).
87 * @param tx double; the translation value for the x-coordinates
88 * @param ty double; the translation value for the y-coordinates
89 * @return Transform2d; the new transformation matrix after applying this transform
90 */
91 public Transform2d translate(final double tx, final double ty)
92 {
93 if (tx == 0.0 && ty == 0.0)
94 {
95 return this;
96 }
97 this.mat = mulMatMat(this.mat, new double[] {1, 0, tx, 0, 1, ty, 0, 0, 1});
98 return this;
99 }
100
101 /**
102 * Translate coordinates by a the x and y values contained in a Point2d.
103 * @param point Point2d; the point containing the x and y translation values
104 * @return Transform2d; the new transformation matrix after applying this transform
105 */
106 public Transform2d translate(final Point2d point)
107 {
108 if (point.x == 0.0 && point.y == 0.0)
109 {
110 return this;
111 }
112 this.mat = mulMatMat(this.mat, new double[] {1, 0, point.x, 0, 1, point.y, 0, 0, 1});
113 return this;
114 }
115
116 /**
117 * Scale all coordinates with a factor for x, and y. A scale factor of 1 leaves the coordinate unchanged.
118 * @param sx double; the scale factor for the x-coordinates
119 * @param sy double; the scale factor for the y-coordinates
120 * @return Transform2d; the new transformation matrix after applying this transform
121 */
122 public Transform2d scale(final double sx, final double sy)
123 {
124 if (sx == 1.0 && sy == 1.0)
125 {
126 return this;
127 }
128 this.mat = mulMatMat(this.mat, new double[] {sx, 0, 0, 0, sy, 0, 0, 0, 1});
129 return this;
130 }
131
132 /**
133 * The rotation around the origin with an angle in radians.
134 * @param angle double; the angle to rotate the coordinates with with around the origin
135 * @return Transform2d; the new transformation matrix after applying this transform
136 */
137 public Transform2d rotation(final double angle)
138 {
139 if (angle == 0.0)
140 {
141 return this;
142 }
143 double c = Math.cos(angle);
144 double s = Math.sin(angle);
145 this.mat = mulMatMat(this.mat, new double[] {c, -s, 0, s, c, 0, 0, 0, 1});
146 return this;
147 }
148
149 /**
150 * The 2d shear leaves the xy-coordinate plane for z=0 untouched. An x-coordinate with a value of 1 is translated by sx, and
151 * an x-coordinate with another value is translated by x*sx. Similarly, a y-coordinate with a value of 1 is translated by xy
152 * and a y-coordinate with another value is translated by y*sy.
153 * @param sx double; the shear factor in the x-direction
154 * @param sy double; the shear factor in the y-direction
155 * @return Transform2d; the new transformation matrix after applying this transform
156 */
157 public Transform2d shear(final double sx, final double sy)
158 {
159 if (sx == 0.0 && sy == 0.0)
160 {
161 return this;
162 }
163 this.mat = mulMatMat(this.mat, new double[] {1, sx, 0, sy, 1, 0, 0, 0, 1});
164 return this;
165 }
166
167 /**
168 * The reflection of the x-coordinate, by mirroring it in the yz-plane (the plane with x=0).
169 * @return Transform2d; the new transformation matrix after applying this transform
170 */
171 public Transform2d reflectX()
172 {
173 this.mat = mulMatMat(this.mat, new double[] {-1, 0, 0, 0, 1, 0, 0, 0, 1});
174 return this;
175 }
176
177 /**
178 * The reflection of the y-coordinate, by mirroring it in the xz-plane (the plane with y=0).
179 * @return Transform2d; the new transformation matrix after applying this transform
180 */
181 public Transform2d reflectY()
182 {
183 this.mat = mulMatMat(this.mat, new double[] {1, 0, 0, 0, -1, 0, 0, 0, 1});
184 return this;
185 }
186
187 /**
188 * Apply the stored transform on the xy-vector and return the transformed vector. For speed reasons, no checks on correct
189 * size of the vector is done.
190 * @param xy double[]; double[2] the provided vector
191 * @return double[2]; the transformed vector
192 */
193 public double[] transform(final double[] xy)
194 {
195 return mulMatVec2(this.mat, xy);
196 }
197
198 /**
199 * Apply the stored transform on the provided point and return a point with the transformed coordinate.
200 * @param point Point2d; the point to be transformed
201 * @return Point2d; a point with the transformed coordinates
202 */
203 public Point2d#Point2d">Point2d transform(final Point2d point)
204 {
205 return new Point2d(mulMatVec2(this.mat, new double[] {point.x, point.y}));
206 }
207
208 /**
209 * Apply the stored transform on the provided point and return a point with the transformed coordinate.
210 * @param pointIterator Iterator<Point2d>; generates the points to be transformed
211 * @return Iterator<Point2d>; an iterator that will generator all transformed points
212 */
213 public Iterator<Point2d> transform(final Iterator<Point2d> pointIterator)
214 {
215 return new Iterator<Point2d>()
216 {
217
218 @Override
219 public boolean hasNext()
220 {
221 return pointIterator.hasNext();
222 }
223
224 @Override
225 public Point2d next()
226 {
227 return transform(pointIterator.next());
228 }
229 };
230 }
231
232 /**
233 * Apply the stored transform on the provided Bounds2d and return a new Bounds2d with the bounds of the transformed
234 * coordinates. All 4 corner points have to be transformed, since we do not know which of the 4 points will result in the
235 * lowest and highest x and y coordinates.
236 * @param boundingRectangle Bounds2d; the bounds to be transformed
237 * @return Bounds2d; the new bounds based on the transformed coordinates
238 */
239 public Bounds2dl#Bounds2d">Bounds2d transform(final Bounds2d boundingRectangle)
240 {
241 return new Bounds2d(transform(boundingRectangle.getPoints()));
242 }
243
244 /** {@inheritDoc} */
245 @Override
246 public int hashCode()
247 {
248 final int prime = 31;
249 int result = 1;
250 result = prime * result + Arrays.hashCode(this.mat);
251 return result;
252 }
253
254 /** {@inheritDoc} */
255 @Override
256 @SuppressWarnings("checkstyle:needbraces")
257 public boolean equals(final Object obj)
258 {
259 if (this == obj)
260 return true;
261 if (obj == null)
262 return false;
263 if (getClass() != obj.getClass())
264 return false;
265 Transform2d./../org/djutils/draw/Transform2d.html#Transform2d">Transform2d other = (Transform2d) obj;
266 if (!Arrays.equals(this.mat, other.mat))
267 return false;
268 return true;
269 }
270
271 /** {@inheritDoc} */
272 @Override
273 public String toString()
274 {
275 return "Transform2d [mat=" + Arrays.toString(this.mat) + "]";
276 }
277
278 }