forked from grow-graphics/xy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
transform2d.go
277 lines (244 loc) · 10.2 KB
/
transform2d.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package xy
/*
Transform2D is a 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations. It can represent transformations such as translation,
rotation, and scaling. It consists of three Vector2 values: x, y, and the origin.
For more information, read the "Matrices and transforms" documentation article.
*/
type Transform2D [3]Vector2
// NewTransform2D constructs a new Transform2D from the given rotation and position.
func NewTransform2D(rotation Radians, scale Vector2, skew Radians, position Vector2) Transform2D { //Transform2D(float,Vector2,float,Vector2)
r := float(rotation)
s := scale
k := float(skew)
p := position
return Transform2D{
Vector2{s[X] * Cos(r), s[Y] * Sin(r)},
Vector2{s[X] * -Sin(r+k), s[Y] * Cos(r+k)},
p,
}
}
// "Fields"
func (t Transform2D) X() Vector2 { return t[0] }
func (t Transform2D) Y() Vector2 { return t[1] }
func (t *Transform2D) SetX(x Vector2) { t[0] = x }
func (t *Transform2D) SetY(y Vector2) { t[1] = y }
func (t *Transform2D) SetOrigin(o Vector2) {
t[2] = o
}
// "Constants"
func (t Transform2D) IDENTITY() Transform2D {
return Transform2D{
Vector2{1, 0},
Vector2{0, 1},
Vector2{0, 0},
}
}
func (t Transform2D) FLIP_X() Transform2D {
return Transform2D{
Vector2{-1, 0},
Vector2{0, 1},
Vector2{0, 0},
}
}
func (t Transform2D) FLIP_Y() Transform2D {
return Transform2D{
Vector2{1, 0},
Vector2{0, -1},
Vector2{0, 0},
}
}
func (t Transform2D) tdotx(v Vector2) float { return t[0][x]*v[x] + t[1][x]*v[y] }
func (t Transform2D) tdoty(v Vector2) float { return t[0][y]*v[x] + t[1][y]*v[y] }
// AffineInverse returns the inverse of the transform, under the assumption that the basis is
// invertible (must have non-zero determinant).
func (t Transform2D) AffineInverse() Transform2D { //Transform2D.affine_inverse
det := t.Determinant()
idet := float(1 / det)
t[0][0], t[1][1] = t[1][1], t[0][0]
t[0] = t[0].Mul(Vector2{idet, -idet})
t[1] = t[1].Mul(Vector2{-idet, idet})
t[2] = t.BasisTransform(t[2].Neg())
return t
}
// BasisTransform returns a vector transformed (multiplied) by the basis matrix.
//
// This method does not account for translation (the origin vector).
func (t Transform2D) BasisTransform(v Vector2) Vector2 { return Vector2{t.tdotx(v), t.tdoty(v)} } //Transform2D.basis_xform
// InverseBasisTransform returns a vector transformed (multiplied) by the inverse basis matrix,
// under the assumption that the basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
//
// This method does not account for translation (the origin vector).
//
// transform.InverseBasisTransform(vector) is equivalent to transform.Inverse().BasisTransform(vector).
// See [Transform2D.Inverse].
//
// For non-orthonormal transforms (e.g. with scaling) transform.AffineInverse().BasisTransform(vector)
// can be used instead. See [Transform2D.AffineInverse].
func (t Transform2D) InverseBasisTransform(v Vector2) Vector2 { //Transform2D.basis_xform_inv
return t.Inverse().BasisTransform(v)
}
// Determinant returns the determinant of the basis matrix. If the basis is uniformly scaled, then its
// determinant equals the square of the scale factor.
//
// A negative determinant means the basis was flipped, so one part of the scale is negative. A zero
// determinant means the basis isn't invertible, and is usually considered invalid.
func (t Transform2D) Determinant() float64 { //Transform2D.determinant
return float64(t[0][X]*t[1][Y] - t[0][Y]*t[1][X])
}
// Origin returns the transform's origin (translation).
func (t Transform2D) Origin() Vector2 { return t[2] } //Transform2D.get_origin
// Rotation returns the transform's rotation.
func (t Transform2D) Rotation() Radians { return Atan2(t[0][Y], t[0][X]) } //Transform2D.get_rotation
// Scale returns the transform's scale.
func (t Transform2D) Scale() Vector2 { //Transform2D.get_scale
var det_sign = Signf(t.Determinant())
return Vector2{float(t[0].Length()), float(det_sign * t[1].Length())}
}
// Skew returns the transform's skew (in radians)
func (t Transform2D) Skew() Radians { //Transform2D.get_skew
return Radians(Acos(t[0].Normalized().Dot(t[1].Normalized().Mulf(Signf(t.Determinant()))))) - Pi*0.5
}
// InterpolateWith returns a transform interpolated between this transform and
// another by a given weight (on the range of 0.0 to 1.0).
func (t Transform2D) InterpolateWith(b Transform2D, weight float64) Transform2D { //Transform2D.interpolate_with
return NewTransform2D(
LerpAngle(t.Rotation(), b.Rotation(), Radians(weight)),
t.Scale().Lerp(b.Scale(), weight),
LerpAngle(t.Skew(), b.Skew(), Radians(weight)),
t.Origin().Lerp(b.Origin(), weight),
)
}
// Inverse returns the inverse of the transform, under the assumption that the transformation basis is
// orthonormal (i.e. rotation/reflection is fine, scaling/skew is not). Use affine_inverse for
// non-orthonormal transforms (e.g. with scaling).
func (t Transform2D) Inverse() Transform2D { //Transform2D.inverse
t[0][1], t[1][0] = t[1][0], t[0][1]
t[2] = t.InverseBasisTransform(t[2].Neg())
return t
}
// IsConformal returns true if the transform's basis is conformal, meaning it preserves angles and distance
// ratios, and may only be composed of rotation and uniform scale. Returns false if the transform's basis
// has non-uniform scale or shear/skew. This can be used to validate if the transform is non-distorted,
// which is important for physics and other use cases.
func (t Transform2D) IsConformal() bool { //Transform2D.is_conformal
// Non-flipped case.
if IsApproximatelyEqual(t[0][0], t[1][1]) && IsApproximatelyEqual(t[0][1], -t[1][0]) {
return true
}
// Flipped case.
if IsApproximatelyEqual(t[0][0], -t[1][1]) && IsApproximatelyEqual(t[0][1], t[1][0]) {
return true
}
return false
}
// IsApproximatelyEqual returns true if this transform and xform are approximately equal, by running
// [Vector2.IsApproximatelyEqual] on each component.
func (t Transform2D) IsApproximatelyEqual(xform Transform2D) bool { //Transform2D.is_equal_approx
return t[0].IsApproximatelyEqual(xform[0]) && t[1].IsApproximatelyEqual(xform[1]) && t[2].IsApproximatelyEqual(xform[2])
}
// IsFinite returns true if this transform is finite, by calling [Vector2.IsFinite] on each component.
func (t Transform2D) IsFinite() bool { return t[0].IsFinite() && t[1].IsFinite() && t[2].IsFinite() } //Transform2D.is_finite
// LookingAt returns a copy of the transform rotated such that the rotated X-axis points towards the target position.
//
// Operations take place in global space.
func (t Transform2D) LookingAt(target Vector2) Transform2D { //Transform2D.looking_at
var return_trans = NewTransform2D(t.Rotation(), Vector2{1, 1}, 0, t.Origin())
var target_position = target.Transform(t.AffineInverse())
return return_trans.Rotated(return_trans.Rotation() + (target_position.Mul(t.Scale())).Angle())
}
// Orthonormalized returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors
// (scale of 1 or -1).
func (t Transform2D) Orthonormalized() Transform2D { //Transform2D.orthonormalized
var x = t[0]
var y = t[1]
x = x.Normalized()
y = y.Subf(x.Mul(x).Dot(y))
y = y.Normalized()
t[0] = x
t[1] = y
return t
}
// Rotated returns a copy of the transform rotated by the given angle (in radians).
//
// This method is an optimized version of multiplying the given transform X with a corresponding rotation
// transform R from the left, i.e., R * X.
//
// This can be seen as transforming with respect to the global/parent frame.
func (t Transform2D) Rotated(angle Radians) Transform2D { //Transform2D.rotated
return NewTransform2D(angle, Vector2{1, 1}, 0, Vector2{}).Mul(t)
}
// RotatedLocal returns a copy of the transform rotated by the given angle (in radians).
//
// This method is an optimized version of multiplying the given transform X with a corresponding
// rotation transform R from the right, i.e., X * R.
//
// This can be seen as transforming with respect to the local frame.
func (t Transform2D) RotatedLocal(angle Radians) Transform2D { //Transform2D.rotated_local
return t.Mul(NewTransform2D(angle, Vector2{1, 1}, 0, Vector2{}))
}
// Scaled returns a copy of the transform scaled by the given scale factor.
//
// This method is an optimized version of multiplying the given transform X
// with a corresponding scaling transform S from the left, i.e., S * X.
//
// This can be seen as transforming with respect to the global/parent frame.
func (t Transform2D) Scaled(scale Vector2) Transform2D { //Transform2D.scaled
t[0][0] *= scale[X]
t[0][1] *= scale[Y]
t[1][0] *= scale[X]
t[1][1] *= scale[Y]
t[2] = t[2].Mul(scale)
return t
}
// ScaledLocal returns a copy of the transform scaled by the given scale factor.
//
// This method is an optimized version of multiplying the given transform X with a
// corresponding scaling transform S from the right, i.e., X * S.
//
// This can be seen as transforming with respect to the local frame.
func (t Transform2D) ScaledLocal(scale Vector2) Transform2D { //Transform2D.scaled_local
return Transform2D{
t[0].Mulf(float64(scale[X])),
t[1].Mulf(float64(scale[Y])),
t[2],
}
}
// Translated returns a copy of the transform translated by the given offset.
//
// This method is an optimized version of multiplying the given transform X with a
// corresponding translation transform T from the left, i.e., T * X.
//
// This can be seen as transforming with respect to the global/parent frame.
func (t Transform2D) Translated(offset Vector2) Transform2D { //Transform2D.translated
return Transform2D{
t[0],
t[1],
t[2].Add(offset),
}
}
// TranslatedLocal returns a copy of the transform translated by the given offset.
//
// This method is an optimized version of multiplying the given transform X with a
// corresponding translation transform T from the right, i.e., X * T.
//
// This can be seen as transforming with respect to the local frame.
func (t Transform2D) TranslatedLocal(offset Vector2) Transform2D { //Transform2D.translated_local
return Transform2D{
t[0],
t[1],
t[2].Add(t.BasisTransform(offset)),
}
}
func (t Transform2D) Mul(other Transform2D) Transform2D { //Transform2D * Transform2D
t[2] = other[2].Transform(t)
var x0, x1, y0, y1 float
x0 = t.tdotx(other[0])
x1 = t.tdoty(other[0])
y0 = t.tdotx(other[1])
y1 = t.tdoty(other[1])
t[0][0] = x0
t[0][1] = x1
t[1][0] = y0
t[1][1] = y1
return t
}